Merge "Make DPM#checkDeviceIdentifierAccess a SystemApi"
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..03af56d
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: Google
+
+AccessModifierOffset: -4
+AlignOperands: false
+AllowShortFunctionsOnASingleLine: Inline
+AlwaysBreakBeforeMultilineStrings: false
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ConstructorInitializerIndentWidth: 6
+ContinuationIndentWidth: 8
+IndentWidth: 4
+PenaltyBreakBeforeFirstCallParameter: 100000
+SpacesBeforeTrailingComments: 1
diff --git a/Android.bp b/Android.bp
index 7b6d145..08b5b4a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -200,19 +200,9 @@
}
filegroup {
- name: "framework-wifi-sources",
- srcs: [
- "wifi/java/**/*.java",
- "wifi/java/**/*.aidl",
- ],
- path: "wifi/java",
-}
-
-filegroup {
name: "framework-non-updatable-sources",
srcs: [
// Java/AIDL sources under frameworks/base
- ":framework-appsearch-sources",
":framework-blobstore-sources",
":framework-core-sources",
":framework-drm-sources",
@@ -232,7 +222,8 @@
":framework-telecomm-sources",
":framework-telephony-common-sources",
":framework-telephony-sources",
- ":framework-wifi-sources",
+ ":framework-wifi-annotations",
+ ":framework-wifi-non-updatable-sources",
":PacProcessor-aidl-sources",
":ProxyHandler-aidl-sources",
@@ -254,7 +245,7 @@
":libcamera_client_framework_aidl",
":libupdate_engine_aidl",
// TODO: this needs to be removed when statsd-framework.jar is separated out
- ":statsd_aidl",
+ ":statsd_java_aidl",
":storaged_aidl",
":vold_aidl",
@@ -270,8 +261,13 @@
filegroup {
name: "framework-updatable-sources",
srcs: [
+ ":framework-appsearch-sources",
":framework-sdkext-sources",
+ ":framework-statsd-sources",
+ ":framework-tethering-srcs",
":updatable-media-srcs",
+ ":framework-mediaprovider-sources",
+ ":framework-wifi-updatable-sources",
]
}
@@ -379,8 +375,12 @@
sdk_version: "core_platform",
libs: [
+ "app-compat-annotations",
"ext",
+ "unsupportedappusage",
"updatable_media_stubs",
+ "framework_mediaprovider_stubs",
+ "framework-tethering",
],
jarjar_rules: ":framework-jarjar-rules",
@@ -402,7 +402,7 @@
filegroup {
name: "framework-jarjar-rules",
- srcs: ["jarjar_rules_hidl.txt"],
+ srcs: ["framework-jarjar-rules.txt"],
}
filegroup {
@@ -427,7 +427,12 @@
name: "framework-minus-apex",
defaults: ["framework-defaults"],
srcs: [":framework-non-updatable-sources"],
- libs: ["app-compat-annotations"],
+ libs: [
+ "framework-appsearch-stubs",
+ // TODO(b/146167933): Use framework-statsd-stubs
+ "framework-statsd",
+ "framework-wifi-stubs",
+ ],
installable: true,
javac_shard_size: 150,
required: [
@@ -445,6 +450,7 @@
],
// For backwards compatibility.
stem: "framework",
+ apex_available: ["//apex_available:platform"],
}
// This "framework" module is NOT installed to the device. It's
@@ -462,9 +468,18 @@
installable: false, // this lib is a build-only library
static_libs: [
"framework-minus-apex",
- // TODO(jiyong): add stubs for APEXes here
+ "updatable_media_stubs",
+ "framework_mediaprovider_stubs",
+ "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
+ "framework-sdkext-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
],
sdk_version: "core_platform",
+ apex_available: ["//apex_available:platform"],
}
java_library {
@@ -472,15 +487,18 @@
defaults: ["framework-defaults"],
srcs: [":framework-all-sources"],
installable: false,
- libs: ["app-compat-annotations"],
- static_libs: ["exoplayer2-core"]
+ static_libs: ["exoplayer2-core"],
+ apex_available: ["//apex_available:platform"],
}
java_library {
name: "framework-annotation-proc",
defaults: ["framework-aidl-export-defaults"],
srcs: [":framework-all-sources"],
- libs: ["app-compat-annotations"],
+ libs: [
+ "app-compat-annotations",
+ "unsupportedappusage",
+ ],
installable: false,
plugins: [
"unsupportedappusage-annotation-processor",
@@ -567,10 +585,14 @@
filegroup {
name: "framework-annotations",
srcs: [
- "core/java/android/annotation/NonNull.java",
- "core/java/android/annotation/Nullable.java",
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/IntRange.java",
+ "core/java/android/annotation/NonNull.java",
+ "core/java/android/annotation/Nullable.java",
+ "core/java/android/annotation/RequiresPermission.java",
+ "core/java/android/annotation/SdkConstant.java",
+ "core/java/android/annotation/SystemApi.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",
@@ -580,7 +602,7 @@
java_library {
name: "framework-annotations-lib",
srcs: [ ":framework-annotations" ],
- sdk_version: "current",
+ sdk_version: "core_current",
}
filegroup {
@@ -604,10 +626,26 @@
],
}
+// keep these files in sync with the package/Tethering/jarjar-rules.txt for the tethering module.
filegroup {
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",
+ ],
+}
+
+filegroup {
+ name: "framework-tethering-annotations",
+ srcs: [
+ "core/java/android/annotation/NonNull.java",
+ "core/java/android/annotation/SystemApi.java",
],
}
// Build ext.jar
@@ -658,7 +696,7 @@
"core/proto/android/privacy.proto",
"core/proto/android/section.proto",
],
- sdk_version: "current",
+ sdk_version: "9",
srcs: [
"core/proto/**/*.proto",
"libs/incident/proto/android/os/**/*.proto",
@@ -681,6 +719,7 @@
"core/proto/android/privacy.proto",
"core/proto/android/section.proto",
],
+ sdk_version: "core_current",
// Protos have lots of MissingOverride and similar.
errorprone: {
javacflags: ["-XepDisableAllChecks"],
@@ -783,11 +822,7 @@
filegroup {
name: "incremental_aidl",
srcs: [
- "core/java/android/os/incremental/IIncrementalManager.aidl",
- "core/java/android/os/incremental/IIncrementalServiceProxy.aidl",
- "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
"core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
- "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
],
path: "core/java",
}
@@ -795,7 +830,21 @@
filegroup {
name: "dataloader_aidl",
srcs: [
+ "core/java/android/content/pm/DataLoaderParamsParcel.aidl",
+ "core/java/android/content/pm/DataLoaderType.aidl",
+ "core/java/android/content/pm/FileSystemControlParcel.aidl",
"core/java/android/content/pm/IDataLoaderStatusListener.aidl",
+ "core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl",
+ "core/java/android/content/pm/NamedParcelFileDescriptor.aidl",
+ ],
+ path: "core/java",
+}
+
+filegroup {
+ name: "incremental_manager_aidl",
+ srcs: [
+ "core/java/android/os/incremental/IIncrementalManager.aidl",
+ "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
],
path: "core/java",
}
@@ -805,9 +854,6 @@
srcs: [
":incremental_aidl",
],
- imports: [
- "libdataloader_aidl",
- ],
backend: {
java: {
sdk_version: "28",
@@ -826,6 +872,9 @@
srcs: [
":dataloader_aidl",
],
+ imports: [
+ "libincremental_aidl",
+ ],
backend: {
java: {
sdk_version: "28",
@@ -834,8 +883,30 @@
enabled: true,
},
ndk: {
+ enabled: false,
+ },
+ },
+}
+
+aidl_interface {
+ name: "libincremental_manager_aidl",
+ srcs: [
+ ":incremental_manager_aidl",
+ ],
+ imports: [
+ "libincremental_aidl",
+ "libdataloader_aidl",
+ ],
+ backend: {
+ java: {
+ sdk_version: "28",
+ },
+ cpp: {
enabled: true,
},
+ ndk: {
+ enabled: false,
+ },
},
}
@@ -919,6 +990,7 @@
"core/java/android/os/RemoteException.java",
"core/java/android/util/AndroidException.java",
],
+ libs: [ "unsupportedappusage" ],
dxflags: ["--core-library"],
installable: false,
@@ -950,645 +1022,6 @@
],
}
-// Make the api/current.txt file available for use by modules in other
-// directories.
-filegroup {
- name: "frameworks-base-api-current.txt",
- srcs: [
- "api/current.txt",
- ],
-}
-
-// Make the api/system-current.txt file available for use by modules in other
-// directories.
-filegroup {
- name: "frameworks-base-api-system-current.txt",
- srcs: [
- "api/system-current.txt",
- ],
-}
-
-// Make the api/system-removed.txt file available for use by modules in other
-// directories.
-filegroup {
- name: "frameworks-base-api-system-removed.txt",
- srcs: [
- "api/system-removed.txt",
- ],
-}
-
-framework_docs_only_args = " -android -manifest $(location core/res/AndroidManifest.xml) " +
- "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
- "-overview $(location core/java/overview.html) " +
- // Federate Support Library references against local API file.
- "-federate SupportLib https://developer.android.com " +
- "-federationapi SupportLib $(location :current-support-api) " +
- // Federate Support Library references against local API file.
- "-federate AndroidX https://developer.android.com " +
- "-federationapi AndroidX $(location :current-androidx-api) "
-
-framework_docs_only_libs = [
- "voip-common",
- "android.test.mock",
- "android-support-annotations",
- "android-support-compat",
- "android-support-core-ui",
- "android-support-core-utils",
- "android-support-design",
- "android-support-dynamic-animation",
- "android-support-exifinterface",
- "android-support-fragment",
- "android-support-media-compat",
- "android-support-percent",
- "android-support-transition",
- "android-support-v7-cardview",
- "android-support-v7-gridlayout",
- "android-support-v7-mediarouter",
- "android-support-v7-palette",
- "android-support-v7-preference",
- "android-support-v13",
- "android-support-v14-preference",
- "android-support-v17-leanback",
- "android-support-vectordrawable",
- "android-support-animatedvectordrawable",
- "android-support-v7-appcompat",
- "android-support-v7-recyclerview",
- "android-support-v8-renderscript",
- "android-support-multidex",
- "android-support-multidex-instrumentation",
-]
-
-metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
- "--ignore-classes-on-classpath " +
- "--hide-package com.android.server " +
- "--error UnhiddenSystemApi " +
- "--hide RequiresPermission " +
- "--hide CallbackInterface " +
- "--hide MissingPermission --hide BroadcastBehavior " +
- "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
- "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
- "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*"
-
-packages_to_document = [
- "android",
- "dalvik",
- "java",
- "javax",
- "junit",
- "org.apache.http",
- "org.json",
- "org.w3c.dom",
- "org.xml.sax",
- "org.xmlpull",
-]
-
-stubs_defaults {
- name: "framework-doc-stubs-default",
- srcs: [
- ":framework-mime-sources",
- ":framework-non-updatable-sources",
- ":framework-updatable-sources",
- "core/java/**/*.logtags",
- "test-base/src/**/*.java",
- ":opt-telephony-srcs",
- ":opt-net-voip-srcs",
- ":core-current-stubs-source",
- ":core_public_api_files",
- "test-mock/src/**/*.java",
- "test-runner/src/**/*.java",
- ],
- libs: framework_docs_only_libs,
- create_doc_stubs: true,
- annotations_enabled: true,
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
- previous_api: ":last-released-public-api",
- merge_annotations_dirs: [
- "metalava-manual",
- ],
-}
-
-doc_defaults {
- name: "framework-docs-default",
- libs: framework_docs_only_libs +
- ["stub-annotations"],
- html_dirs: [
- "docs/html",
- ],
- knowntags: [
- "docs/knowntags.txt",
- ":known-oj-tags",
- ],
- custom_template: "droiddoc-templates-sdk",
- resourcesdir: "docs/html/reference/images/",
- resourcesoutdir: "reference/android/images/",
- hdf: [
- "dac true",
- "sdk.codename O",
- "sdk.preview.version 1",
- "sdk.version 7.0",
- "sdk.rel.id 1",
- "sdk.preview 0",
- ],
- arg_files: [
- "core/res/AndroidManifest.xml",
- "core/java/overview.html",
- ":current-support-api",
- ":current-androidx-api",
- ],
- create_stubs: false,
-}
-
-doc_defaults {
- name: "framework-dokka-docs-default",
- create_stubs: false,
-}
-
-stubs_defaults {
- name: "metalava-api-stubs-default",
- srcs: [
- ":framework-non-updatable-sources",
- ":framework-updatable-sources",
- "core/java/**/*.logtags",
- ":opt-telephony-srcs",
- ":opt-net-voip-srcs",
- ":core-current-stubs-source",
- ":core_public_api_files",
- ":ike-api-srcs",
- ],
- libs: ["framework-internal-utils"],
- installable: false,
- annotations_enabled: true,
- previous_api: ":last-released-public-api",
- merge_annotations_dirs: [
- "metalava-manual",
- ],
- api_levels_annotations_enabled: true,
- api_levels_annotations_dirs: [
- "sdk-dir",
- "api-versions-jars-dir",
- ],
- sdk_version: "core_platform",
- filter_packages: packages_to_document,
-}
-
-droidstubs {
- name: "framework-doc-stubs",
- defaults: ["framework-doc-stubs-default"],
- arg_files: [
- "core/res/AndroidManifest.xml",
- ],
- args: metalava_framework_docs_args,
- write_sdk_values: true,
-}
-
-droidstubs {
- name: "framework-doc-system-stubs",
- defaults: ["framework-doc-stubs-default"],
- arg_files: [
- "core/res/AndroidManifest.xml",
- ],
- args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi ",
- write_sdk_values: true,
-}
-
-droiddoc {
- name: "doc-comment-check-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- args: framework_docs_only_args + " -referenceonly -parsecomments",
- installable: false,
-}
-
-droiddoc {
- name: "offline-sdk-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- hdf: [
- "android.whichdoc offline",
- ],
- proofread_file: "offline-sdk-docs-proofrerad.txt",
- args: framework_docs_only_args + " -offlinemode -title \"Android SDK\"",
- static_doc_index_redirect: "docs/docs-preview-index.html",
-}
-
-droiddoc {
- // Please sync with android-api-council@ before making any changes for the name property below.
- // Since there's cron jobs that fetch offline-sdk-referenceonly-docs-docs.zip periodically.
- // See b/116221385 for reference.
- name: "offline-sdk-referenceonly-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- hdf: [
- "android.whichdoc offline",
- ],
- proofread_file: "offline-sdk-referenceonly-docs-proofrerad.txt",
- args: framework_docs_only_args + " -offlinemode -title \"Android SDK\" -referenceonly",
- static_doc_index_redirect: "docs/docs-documentation-redirect.html",
- static_doc_properties: "docs/source.properties",
-}
-
-droiddoc {
- // Please sync with android-api-council@ before making any changes for the name property below.
- // Since there's cron jobs that fetch offline-system-sdk-referenceonly-docs-docs.zip periodically.
- // See b/116221385 for reference.
- name: "offline-system-sdk-referenceonly-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-system-stubs",
- ],
- hdf: [
- "android.whichdoc offline",
- ],
- proofread_file: "offline-system-sdk-referenceonly-docs-proofrerad.txt",
- args: framework_docs_only_args + " -hide 101 -hide 104 -hide 108" +
- " -offlinemode -title \"Android System SDK\" -referenceonly",
- static_doc_index_redirect: "docs/docs-documentation-redirect.html",
- static_doc_properties: "docs/source.properties",
-}
-
-droiddoc {
- name: "online-sdk-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- hdf: [
- "android.whichdoc online",
- "android.hasSamples true",
- ],
- proofread_file: "online-sdk-docs-proofrerad.txt",
- args: framework_docs_only_args +
- " -toroot / -samplegroup Admin " +
- " -samplegroup Background " +
- " -samplegroup Connectivity " +
- " -samplegroup Content " +
- " -samplegroup Input " +
- " -samplegroup Media " +
- " -samplegroup Notification " +
- " -samplegroup RenderScript " +
- " -samplegroup Security " +
- " -samplegroup Sensors " +
- " -samplegroup System " +
- " -samplegroup Testing " +
- " -samplegroup UI " +
- " -samplegroup Views " +
- " -samplegroup Wearable -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
- name: "online-system-api-sdk-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-system-stubs",
- ],
- hdf: [
- "android.whichdoc online",
- "android.hasSamples true",
- ],
- proofread_file: "online-system-api-sdk-docs-proofrerad.txt",
- args: framework_docs_only_args +
- " -referenceonly " +
- " -title \"Android SDK - Including system APIs.\" " +
- " -hide 101 " +
- " -hide 104 " +
- " -hide 108 " +
- " -toroot / -samplegroup Admin " +
- " -samplegroup Background " +
- " -samplegroup Connectivity " +
- " -samplegroup Content " +
- " -samplegroup Input " +
- " -samplegroup Media " +
- " -samplegroup Notification " +
- " -samplegroup RenderScript " +
- " -samplegroup Security " +
- " -samplegroup Sensors " +
- " -samplegroup System " +
- " -samplegroup Testing " +
- " -samplegroup UI " +
- " -samplegroup Views " +
- " -samplegroup Wearable -samplesdir development/samples/browseable ",
- installable: false,
-}
-
-droiddoc {
- name: "ds-docs-java",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- hdf: [
- "android.whichdoc online",
- "android.hasSamples true",
- ],
- proofread_file: "ds-docs-proofrerad.txt",
- args: framework_docs_only_args +
- " -toroot / -yamlV2 -metalavaApiSince -samplegroup Admin " +
- " -samplegroup Background " +
- " -samplegroup Connectivity " +
- " -samplegroup Content " +
- " -samplegroup Input " +
- " -samplegroup Media " +
- " -samplegroup Notification " +
- " -samplegroup RenderScript " +
- " -samplegroup Security " +
- " -samplegroup Sensors " +
- " -samplegroup System " +
- " -samplegroup Testing " +
- " -samplegroup UI " +
- " -samplegroup Views " +
- " -samplegroup Wearable -devsite -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
- name: "ds-docs-kt",
- defaults: ["framework-dokka-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- args: "-noJdkLink -links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list " +
- "-noStdlibLink",
- proofread_file: "ds-dokka-proofread.txt",
- dokka_enabled: true,
-}
-
-java_genrule {
- name: "ds-docs",
- tools: [
- "zip2zip",
- "merge_zips",
- ],
- srcs: [
- ":ds-docs-java{.docs.zip}",
- ":ds-docs-kt{.docs.zip}",
- ],
- out: ["ds-docs.zip"],
- dist: {
- targets: ["docs"],
- },
- cmd: "$(location zip2zip) -i $(location :ds-docs-kt{.docs.zip}) -o $(genDir)/ds-docs-kt-moved.zip **/*:en/reference/kotlin && " +
- "$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
-}
-
-java_genrule {
- name: "ds-docs-switched",
- tools: [
- "switcher4",
- "soong_zip",
- ],
- srcs: [
- ":ds-docs-java{.docs.zip}",
- ":ds-docs-kt{.docs.zip}",
- ],
- out: ["ds-docs-switched.zip"],
- dist: {
- targets: ["docs"],
- },
- cmd: "unzip $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
- "unzip $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
- "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
- "(cd $(genDir)/en/reference && $$SWITCHER --work platform) && " +
- "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
-}
-
-
-droiddoc {
- name: "ds-static-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- hdf: [
- "android.whichdoc online",
- ],
- proofread_file: "ds-static-docs-proofrerad.txt",
- args: framework_docs_only_args +
- " -staticonly " +
- " -toroot / " +
- " -devsite " +
- " -ignoreJdLinks ",
-}
-
-droiddoc {
- name: "ds-ref-navtree-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- hdf: [
- "android.whichdoc online",
- ],
- proofread_file: "ds-ref-navtree-docs-proofrerad.txt",
- args: framework_docs_only_args +
- " -toroot / " +
- " -atLinksNavtree " +
- " -navtreeonly ",
-}
-
-droiddoc {
- name: "online-sdk-dev-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- hdf: [
- "android.whichdoc online",
- "android.hasSamples true",
- ],
- proofread_file: "online-sdk-dev-docs-proofrerad.txt",
- args: framework_docs_only_args +
- " -toroot / -samplegroup Admin " +
- " -samplegroup Background " +
- " -samplegroup Connectivity " +
- " -samplegroup Content " +
- " -samplegroup Input " +
- " -samplegroup Media " +
- " -samplegroup Notification " +
- " -samplegroup RenderScript " +
- " -samplegroup Security " +
- " -samplegroup Sensors " +
- " -samplegroup System " +
- " -samplegroup Testing " +
- " -samplegroup UI " +
- " -samplegroup Views " +
- " -samplegroup Wearable -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
- name: "hidden-docs",
- defaults: ["framework-docs-default"],
- srcs: [
- ":framework-doc-stubs",
- ],
- proofread_file: "hidden-docs-proofrerad.txt",
- args: framework_docs_only_args +
- " -referenceonly " +
- " -title \"Android SDK - Including hidden APIs.\"",
-}
-
-droidstubs {
- name: "hwbinder-stubs-docs",
- srcs: [
- "core/java/android/os/HidlSupport.java",
- "core/java/android/annotation/IntDef.java",
- "core/java/android/annotation/IntRange.java",
- "core/java/android/annotation/NonNull.java",
- "core/java/android/annotation/SystemApi.java",
- "core/java/android/os/HidlMemory.java",
- "core/java/android/os/HwBinder.java",
- "core/java/android/os/HwBlob.java",
- "core/java/android/os/HwParcel.java",
- "core/java/android/os/IHwBinder.java",
- "core/java/android/os/IHwInterface.java",
- "core/java/android/os/DeadObjectException.java",
- "core/java/android/os/DeadSystemException.java",
- "core/java/android/os/NativeHandle.java",
- "core/java/android/os/RemoteException.java",
- "core/java/android/util/AndroidException.java",
- ],
- installable: false,
- sdk_version: "core_platform",
- annotations_enabled: true,
- previous_api: ":last-released-public-api",
- merge_annotations_dirs: [
- "metalava-manual",
- ],
- args: " --show-annotation android.annotation.SystemApi",
-}
-
-java_library_static {
- name: "hwbinder.stubs",
- sdk_version: "core_current",
- srcs: [
- ":hwbinder-stubs-docs",
- ],
-}
-
-droidstubs {
- name: "hiddenapi-lists-docs",
- defaults: ["metalava-api-stubs-default"],
- arg_files: [
- "core/res/AndroidManifest.xml",
- ],
- dex_api_filename: "public-dex.txt",
- private_dex_api_filename: "private-dex.txt",
- removed_dex_api_filename: "removed-dex.txt",
- args: metalava_framework_docs_args +
- " --show-unannotated " +
- " --show-annotation android.annotation.SystemApi " +
- " --show-annotation android.annotation.TestApi ",
-}
-
-droidstubs {
- name: "hiddenapi-mappings",
- defaults: ["metalava-api-stubs-default"],
- srcs: [
- ":opt-telephony-common-srcs",
- ],
-
- arg_files: [
- "core/res/AndroidManifest.xml",
- ],
- dex_mapping_filename: "dex-mapping.txt",
- args: metalava_framework_docs_args +
- " --hide ReferencesHidden " +
- " --hide UnhiddenSystemApi " +
- " --show-unannotated " +
- " --show-annotation android.annotation.SystemApi " +
- " --show-annotation android.annotation.TestApi ",
-}
-
-droidstubs {
- name: "api-stubs-docs",
- defaults: ["metalava-api-stubs-default"],
- api_filename: "public_api.txt",
- private_api_filename: "private.txt",
- removed_api_filename: "removed.txt",
- arg_files: [
- "core/res/AndroidManifest.xml",
- ],
- args: metalava_framework_docs_args,
- check_api: {
- current: {
- api_file: "api/current.txt",
- removed_api_file: "api/removed.txt",
- },
- last_released: {
- api_file: ":last-released-public-api",
- removed_api_file: "api/removed.txt",
- baseline_file: ":public-api-incompatibilities-with-last-released",
- },
- api_lint: {
- enabled: true,
- new_since: ":last-released-public-api",
- baseline_file: "api/lint-baseline.txt",
- },
- },
- jdiff_enabled: true,
-}
-
-droidstubs {
- name: "system-api-stubs-docs",
- defaults: ["metalava-api-stubs-default"],
- api_tag_name: "SYSTEM",
- api_filename: "system-api.txt",
- private_api_filename: "system-private.txt",
- private_dex_api_filename: "system-private-dex.txt",
- removed_api_filename: "system-removed.txt",
- arg_files: [
- "core/res/AndroidManifest.xml",
- ],
- args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi",
- check_api: {
- current: {
- api_file: "api/system-current.txt",
- removed_api_file: "api/system-removed.txt",
- },
- last_released: {
- api_file: ":last-released-system-api",
- removed_api_file: "api/system-removed.txt",
- baseline_file: ":system-api-incompatibilities-with-last-released"
- },
- api_lint: {
- enabled: true,
- new_since: ":last-released-system-api",
- baseline_file: "api/system-lint-baseline.txt",
- },
- },
- jdiff_enabled: true,
-}
-
-droidstubs {
- name: "test-api-stubs-docs",
- defaults: ["metalava-api-stubs-default"],
- api_tag_name: "TEST",
- api_filename: "test-api.txt",
- removed_api_filename: "test-removed.txt",
- arg_files: [
- "core/res/AndroidManifest.xml",
- ],
- args: metalava_framework_docs_args + " --show-annotation android.annotation.TestApi",
- check_api: {
- current: {
- api_file: "api/test-current.txt",
- removed_api_file: "api/test-removed.txt",
- },
- api_lint: {
- enabled: true,
- baseline_file: "api/test-lint-baseline.txt",
- },
- },
-}
-
filegroup {
name: "framework-annotation-nonnull-srcs",
srcs: [
@@ -1599,20 +1032,23 @@
filegroup {
name: "framework-media-annotation-srcs",
srcs: [
+ ":framework-annotations",
"core/java/android/annotation/CallbackExecutor.java",
"core/java/android/annotation/CallSuper.java",
"core/java/android/annotation/DrawableRes.java",
- "core/java/android/annotation/IntDef.java",
"core/java/android/annotation/LongDef.java",
- "core/java/android/annotation/NonNull.java",
- "core/java/android/annotation/Nullable.java",
- "core/java/android/annotation/RequiresPermission.java",
- "core/java/android/annotation/SdkConstant.java",
"core/java/android/annotation/StringDef.java",
- "core/java/android/annotation/SystemApi.java",
- "core/java/android/annotation/TestApi.java",
- "core/java/android/annotation/UnsupportedAppUsage.java",
- "core/java/com/android/internal/annotations/GuardedBy.java",
+ ],
+}
+
+filegroup {
+ name: "framework-mediaprovider-annotation-sources",
+ srcs: [
+ ":framework-annotations",
+ "core/java/android/annotation/BytesLong.java",
+ "core/java/android/annotation/CurrentTimeMillisLong.java",
+ "core/java/android/annotation/CurrentTimeSecondsLong.java",
+ "core/java/android/annotation/DurationMillisLong.java",
],
}
@@ -1644,6 +1080,8 @@
"core/java/android/util/LocalLog.java",
"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/BitwiseInputStream.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
"core/java/com/android/internal/util/HexDump.java",
"core/java/com/android/internal/util/IState.java",
@@ -1683,7 +1121,8 @@
name: "framework-wifi-service-shared-srcs",
srcs: [
":framework-annotations",
- "core/java/android/os/HandlerExecutor.java",
+ "core/java/android/net/InterfaceConfiguration.java",
+ "core/java/android/os/HandlerExecutor.java",
"core/java/android/util/BackupUtils.java",
"core/java/android/util/LocalLog.java",
"core/java/android/util/Rational.java",
@@ -1698,3 +1137,20 @@
"core/java/com/android/internal/util/XmlUtils.java",
],
}
+
+// TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp
+metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
+ "--ignore-classes-on-classpath " +
+ "--hide-package com.android.server " +
+ "--error UnhiddenSystemApi " +
+ "--hide RequiresPermission " +
+ "--hide CallbackInterface " +
+ "--hide MissingPermission --hide BroadcastBehavior " +
+ "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
+ "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
+ "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*"
+
+build = [
+ "StubLibraries.bp",
+ "ApiDocs.bp",
+]
diff --git a/ApiDocs.bp b/ApiDocs.bp
new file mode 100644
index 0000000..e373db6
--- /dev/null
+++ b/ApiDocs.bp
@@ -0,0 +1,436 @@
+// 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.
+
+// How API docs are generated:
+//
+// raw source files --(metalava)--> stub source files --(doclava)--> API doc
+//
+// The metalava conversion is done by droidstub modules framework-doc-*-stubs.
+// The API doc generation is done by the various droiddoc modules each of which
+// is for different format.
+
+/////////////////////////////////////////////////////////////////////
+// stub source files are generated using metalava
+/////////////////////////////////////////////////////////////////////
+
+framework_docs_only_libs = [
+ "voip-common",
+ "android.test.mock",
+ "android-support-annotations",
+ "android-support-compat",
+ "android-support-core-ui",
+ "android-support-core-utils",
+ "android-support-design",
+ "android-support-dynamic-animation",
+ "android-support-exifinterface",
+ "android-support-fragment",
+ "android-support-media-compat",
+ "android-support-percent",
+ "android-support-transition",
+ "android-support-v7-cardview",
+ "android-support-v7-gridlayout",
+ "android-support-v7-mediarouter",
+ "android-support-v7-palette",
+ "android-support-v7-preference",
+ "android-support-v13",
+ "android-support-v14-preference",
+ "android-support-v17-leanback",
+ "android-support-vectordrawable",
+ "android-support-animatedvectordrawable",
+ "android-support-v7-appcompat",
+ "android-support-v7-recyclerview",
+ "android-support-v8-renderscript",
+ "android-support-multidex",
+ "android-support-multidex-instrumentation",
+]
+
+stubs_defaults {
+ name: "framework-doc-stubs-default",
+ srcs: [
+ ":framework-mime-sources",
+ ":framework-non-updatable-sources",
+ ":framework-updatable-sources",
+ "core/java/**/*.logtags",
+ "test-base/src/**/*.java",
+ ":opt-telephony-srcs",
+ ":opt-net-voip-srcs",
+ ":core-current-stubs-source",
+ ":core_public_api_files",
+ "test-mock/src/**/*.java",
+ "test-runner/src/**/*.java",
+ ],
+ libs: framework_docs_only_libs,
+ create_doc_stubs: true,
+ annotations_enabled: true,
+ api_levels_annotations_enabled: true,
+ api_levels_annotations_dirs: [
+ "sdk-dir",
+ "api-versions-jars-dir",
+ ],
+ previous_api: ":last-released-public-api",
+ merge_annotations_dirs: [
+ "metalava-manual",
+ ],
+}
+
+droidstubs {
+ name: "framework-doc-stubs",
+ defaults: ["framework-doc-stubs-default"],
+ arg_files: [
+ "core/res/AndroidManifest.xml",
+ ],
+ args: metalava_framework_docs_args,
+ write_sdk_values: true,
+}
+
+droidstubs {
+ name: "framework-doc-system-stubs",
+ defaults: ["framework-doc-stubs-default"],
+ 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\\) ",
+ write_sdk_values: true,
+}
+
+/////////////////////////////////////////////////////////////////////
+// API docs are created from the generated stub source files
+// using droiddoc
+/////////////////////////////////////////////////////////////////////
+
+framework_docs_only_args = " -android -manifest $(location core/res/AndroidManifest.xml) " +
+ "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
+ "-overview $(location core/java/overview.html) " +
+ // Federate Support Library references against local API file.
+ "-federate SupportLib https://developer.android.com " +
+ "-federationapi SupportLib $(location :current-support-api) " +
+ // Federate Support Library references against local API file.
+ "-federate AndroidX https://developer.android.com " +
+ "-federationapi AndroidX $(location :current-androidx-api) "
+
+doc_defaults {
+ name: "framework-docs-default",
+ libs: framework_docs_only_libs +
+ ["stub-annotations"],
+ html_dirs: [
+ "docs/html",
+ ],
+ knowntags: [
+ "docs/knowntags.txt",
+ ":known-oj-tags",
+ ],
+ custom_template: "droiddoc-templates-sdk",
+ resourcesdir: "docs/html/reference/images/",
+ resourcesoutdir: "reference/android/images/",
+ hdf: [
+ "dac true",
+ "sdk.codename O",
+ "sdk.preview.version 1",
+ "sdk.version 7.0",
+ "sdk.rel.id 1",
+ "sdk.preview 0",
+ ],
+ arg_files: [
+ "core/res/AndroidManifest.xml",
+ "core/java/overview.html",
+ ":current-support-api",
+ ":current-androidx-api",
+ ],
+ create_stubs: false,
+}
+
+doc_defaults {
+ name: "framework-dokka-docs-default",
+ create_stubs: false,
+}
+
+droiddoc {
+ name: "doc-comment-check-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ args: framework_docs_only_args + " -referenceonly -parsecomments",
+ installable: false,
+}
+
+droiddoc {
+ name: "offline-sdk-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ hdf: [
+ "android.whichdoc offline",
+ ],
+ proofread_file: "offline-sdk-docs-proofrerad.txt",
+ args: framework_docs_only_args + " -offlinemode -title \"Android SDK\"",
+ static_doc_index_redirect: "docs/docs-preview-index.html",
+}
+
+droiddoc {
+ // Please sync with android-api-council@ before making any changes for the name property below.
+ // Since there's cron jobs that fetch offline-sdk-referenceonly-docs-docs.zip periodically.
+ // See b/116221385 for reference.
+ name: "offline-sdk-referenceonly-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ hdf: [
+ "android.whichdoc offline",
+ ],
+ proofread_file: "offline-sdk-referenceonly-docs-proofrerad.txt",
+ args: framework_docs_only_args + " -offlinemode -title \"Android SDK\" -referenceonly",
+ static_doc_index_redirect: "docs/docs-documentation-redirect.html",
+ static_doc_properties: "docs/source.properties",
+}
+
+droiddoc {
+ // Please sync with android-api-council@ before making any changes for the name property below.
+ // Since there's cron jobs that fetch offline-system-sdk-referenceonly-docs-docs.zip periodically.
+ // See b/116221385 for reference.
+ name: "offline-system-sdk-referenceonly-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-system-stubs",
+ ],
+ hdf: [
+ "android.whichdoc offline",
+ ],
+ proofread_file: "offline-system-sdk-referenceonly-docs-proofrerad.txt",
+ args: framework_docs_only_args + " -hide 101 -hide 104 -hide 108" +
+ " -offlinemode -title \"Android System SDK\" -referenceonly",
+ static_doc_index_redirect: "docs/docs-documentation-redirect.html",
+ static_doc_properties: "docs/source.properties",
+}
+
+droiddoc {
+ name: "online-sdk-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ hdf: [
+ "android.whichdoc online",
+ "android.hasSamples true",
+ ],
+ proofread_file: "online-sdk-docs-proofrerad.txt",
+ args: framework_docs_only_args +
+ " -toroot / -samplegroup Admin " +
+ " -samplegroup Background " +
+ " -samplegroup Connectivity " +
+ " -samplegroup Content " +
+ " -samplegroup Input " +
+ " -samplegroup Media " +
+ " -samplegroup Notification " +
+ " -samplegroup RenderScript " +
+ " -samplegroup Security " +
+ " -samplegroup Sensors " +
+ " -samplegroup System " +
+ " -samplegroup Testing " +
+ " -samplegroup UI " +
+ " -samplegroup Views " +
+ " -samplegroup Wearable -samplesdir development/samples/browseable ",
+}
+
+droiddoc {
+ name: "online-system-api-sdk-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-system-stubs",
+ ],
+ hdf: [
+ "android.whichdoc online",
+ "android.hasSamples true",
+ ],
+ proofread_file: "online-system-api-sdk-docs-proofrerad.txt",
+ args: framework_docs_only_args +
+ " -referenceonly " +
+ " -title \"Android SDK - Including system APIs.\" " +
+ " -hide 101 " +
+ " -hide 104 " +
+ " -hide 108 " +
+ " -toroot / -samplegroup Admin " +
+ " -samplegroup Background " +
+ " -samplegroup Connectivity " +
+ " -samplegroup Content " +
+ " -samplegroup Input " +
+ " -samplegroup Media " +
+ " -samplegroup Notification " +
+ " -samplegroup RenderScript " +
+ " -samplegroup Security " +
+ " -samplegroup Sensors " +
+ " -samplegroup System " +
+ " -samplegroup Testing " +
+ " -samplegroup UI " +
+ " -samplegroup Views " +
+ " -samplegroup Wearable -samplesdir development/samples/browseable ",
+ installable: false,
+}
+
+droiddoc {
+ name: "ds-docs-java",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ hdf: [
+ "android.whichdoc online",
+ "android.hasSamples true",
+ ],
+ proofread_file: "ds-docs-proofrerad.txt",
+ args: framework_docs_only_args +
+ " -toroot / -yamlV2 -metalavaApiSince -samplegroup Admin " +
+ " -samplegroup Background " +
+ " -samplegroup Connectivity " +
+ " -samplegroup Content " +
+ " -samplegroup Input " +
+ " -samplegroup Media " +
+ " -samplegroup Notification " +
+ " -samplegroup RenderScript " +
+ " -samplegroup Security " +
+ " -samplegroup Sensors " +
+ " -samplegroup System " +
+ " -samplegroup Testing " +
+ " -samplegroup UI " +
+ " -samplegroup Views " +
+ " -samplegroup Wearable -devsite -samplesdir development/samples/browseable ",
+}
+
+droiddoc {
+ name: "ds-docs-kt",
+ defaults: ["framework-dokka-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ args: "-noJdkLink -links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list " +
+ "-noStdlibLink",
+ proofread_file: "ds-dokka-proofread.txt",
+ dokka_enabled: true,
+}
+
+java_genrule {
+ name: "ds-docs",
+ tools: [
+ "zip2zip",
+ "merge_zips",
+ ],
+ srcs: [
+ ":ds-docs-java{.docs.zip}",
+ ":ds-docs-kt{.docs.zip}",
+ ],
+ out: ["ds-docs.zip"],
+ dist: {
+ targets: ["docs"],
+ },
+ cmd: "$(location zip2zip) -i $(location :ds-docs-kt{.docs.zip}) -o $(genDir)/ds-docs-kt-moved.zip **/*:en/reference/kotlin && " +
+ "$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
+}
+
+java_genrule {
+ name: "ds-docs-switched",
+ tools: [
+ "switcher4",
+ "soong_zip",
+ ],
+ srcs: [
+ ":ds-docs-java{.docs.zip}",
+ ":ds-docs-kt{.docs.zip}",
+ ],
+ out: ["ds-docs-switched.zip"],
+ dist: {
+ targets: ["docs"],
+ },
+ cmd: "unzip $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
+ "unzip $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
+ "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
+ "(cd $(genDir)/en/reference && $$SWITCHER --work platform) && " +
+ "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+}
+
+droiddoc {
+ name: "ds-static-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ hdf: [
+ "android.whichdoc online",
+ ],
+ proofread_file: "ds-static-docs-proofrerad.txt",
+ args: framework_docs_only_args +
+ " -staticonly " +
+ " -toroot / " +
+ " -devsite " +
+ " -ignoreJdLinks ",
+}
+
+droiddoc {
+ name: "ds-ref-navtree-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ hdf: [
+ "android.whichdoc online",
+ ],
+ proofread_file: "ds-ref-navtree-docs-proofrerad.txt",
+ args: framework_docs_only_args +
+ " -toroot / " +
+ " -atLinksNavtree " +
+ " -navtreeonly ",
+}
+
+droiddoc {
+ name: "online-sdk-dev-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ hdf: [
+ "android.whichdoc online",
+ "android.hasSamples true",
+ ],
+ proofread_file: "online-sdk-dev-docs-proofrerad.txt",
+ args: framework_docs_only_args +
+ " -toroot / -samplegroup Admin " +
+ " -samplegroup Background " +
+ " -samplegroup Connectivity " +
+ " -samplegroup Content " +
+ " -samplegroup Input " +
+ " -samplegroup Media " +
+ " -samplegroup Notification " +
+ " -samplegroup RenderScript " +
+ " -samplegroup Security " +
+ " -samplegroup Sensors " +
+ " -samplegroup System " +
+ " -samplegroup Testing " +
+ " -samplegroup UI " +
+ " -samplegroup Views " +
+ " -samplegroup Wearable -samplesdir development/samples/browseable ",
+}
+
+droiddoc {
+ name: "hidden-docs",
+ defaults: ["framework-docs-default"],
+ srcs: [
+ ":framework-doc-stubs",
+ ],
+ proofread_file: "hidden-docs-proofrerad.txt",
+ args: framework_docs_only_args +
+ " -referenceonly " +
+ " -title \"Android SDK - Including hidden APIs.\"",
+}
+
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 8fac394..0b7ea28 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -256,6 +256,10 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/ext.jar)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/google/android/mms)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/*-service.jar)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/service-statsd.jar)
+$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_aidl-cpp-source/)
+$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_manager_aidl-cpp-source/)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 6831117..78e72bf 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,3 +1,13 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+# Only turn on clang-format check for the following subfolders.
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+ cmds/hid/
+ cmds/input/
+ libs/input/
+
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/StubLibraries.bp b/StubLibraries.bp
new file mode 100644
index 0000000..78f1b9c
--- /dev/null
+++ b/StubLibraries.bp
@@ -0,0 +1,340 @@
+// 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.
+
+// How stubs are generated:
+//
+// raw source files --(metalava)--> stub source files --(javac)--> stub jar files
+//
+// The metalava conversion is done by droidstub modules *-api-stubs-docs.
+// The javac compilation is done by java_library modules android_*_stubs_current.
+// The metalava conversion is also responsible for creating API signature files
+// and comparing them against the last API signature in api/*-current.txt files
+// and also against the latest frozen API signature in prebuilts/sdk/*/*/api/android.txt
+// files.
+
+/////////////////////////////////////////////////////////////////////
+// Common metalava configs
+/////////////////////////////////////////////////////////////////////
+
+packages_to_document = [
+ "android",
+ "dalvik",
+ "java",
+ "javax",
+ "junit",
+ "org.apache.http",
+ "org.json",
+ "org.w3c.dom",
+ "org.xml.sax",
+ "org.xmlpull",
+]
+
+stubs_defaults {
+ name: "metalava-api-stubs-default",
+ srcs: [
+ ":framework-non-updatable-sources",
+ ":framework-updatable-sources",
+ "core/java/**/*.logtags",
+ ":opt-telephony-srcs",
+ ":opt-net-voip-srcs",
+ ":core-current-stubs-source",
+ ":core_public_api_files",
+ ":ike-api-srcs",
+ ],
+ libs: ["framework-internal-utils"],
+ installable: false,
+ annotations_enabled: true,
+ previous_api: ":last-released-public-api",
+ merge_annotations_dirs: [
+ "metalava-manual",
+ ],
+ api_levels_annotations_enabled: true,
+ api_levels_annotations_dirs: [
+ "sdk-dir",
+ "api-versions-jars-dir",
+ ],
+ sdk_version: "core_platform",
+ filter_packages: packages_to_document,
+}
+
+/////////////////////////////////////////////////////////////////////
+// *-api-stubs-docs modules providing source files for the stub libraries
+/////////////////////////////////////////////////////////////////////
+
+droidstubs {
+ name: "api-stubs-docs",
+ defaults: ["metalava-api-stubs-default"],
+ api_filename: "public_api.txt",
+ private_api_filename: "private.txt",
+ removed_api_filename: "removed.txt",
+ arg_files: [
+ "core/res/AndroidManifest.xml",
+ ],
+ args: metalava_framework_docs_args,
+ check_api: {
+ current: {
+ api_file: "api/current.txt",
+ removed_api_file: "api/removed.txt",
+ },
+ last_released: {
+ api_file: ":last-released-public-api",
+ removed_api_file: "api/removed.txt",
+ baseline_file: ":public-api-incompatibilities-with-last-released",
+ },
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-public-api",
+ baseline_file: "api/lint-baseline.txt",
+ },
+ },
+ jdiff_enabled: true,
+}
+
+droidstubs {
+ name: "system-api-stubs-docs",
+ defaults: ["metalava-api-stubs-default"],
+ api_tag_name: "SYSTEM",
+ api_filename: "system-api.txt",
+ private_api_filename: "system-private.txt",
+ private_dex_api_filename: "system-private-dex.txt",
+ removed_api_filename: "system-removed.txt",
+ 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\\)",
+ check_api: {
+ current: {
+ api_file: "api/system-current.txt",
+ removed_api_file: "api/system-removed.txt",
+ },
+ last_released: {
+ api_file: ":last-released-system-api",
+ removed_api_file: "api/system-removed.txt",
+ baseline_file: ":system-api-incompatibilities-with-last-released"
+ },
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-system-api",
+ baseline_file: "api/system-lint-baseline.txt",
+ },
+ },
+ jdiff_enabled: true,
+}
+
+droidstubs {
+ name: "test-api-stubs-docs",
+ defaults: ["metalava-api-stubs-default"],
+ api_tag_name: "TEST",
+ api_filename: "test-api.txt",
+ removed_api_filename: "test-removed.txt",
+ arg_files: [
+ "core/res/AndroidManifest.xml",
+ ],
+ args: metalava_framework_docs_args + " --show-annotation android.annotation.TestApi",
+ check_api: {
+ current: {
+ api_file: "api/test-current.txt",
+ removed_api_file: "api/test-removed.txt",
+ },
+ api_lint: {
+ enabled: true,
+ baseline_file: "api/test-lint-baseline.txt",
+ },
+ },
+}
+
+/////////////////////////////////////////////////////////////////////
+// android_*_stubs_current modules are the stubs libraries compiled
+// from *-api-stubs-docs
+/////////////////////////////////////////////////////////////////////
+
+java_defaults {
+ name: "framework-stubs-default",
+ errorprone: {
+ javacflags: [
+ "-XepDisableAllChecks",
+ ],
+ },
+ java_resources: [
+ ":notices-for-framework-stubs",
+ ],
+ sdk_version: "core_current",
+ system_modules: "none",
+ java_version: "1.8",
+ compile_dex: true,
+}
+
+java_library_static {
+ name: "android_stubs_current",
+ srcs: [
+ ":api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+ name: "android_system_stubs_current",
+ srcs: [
+ ":system-api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+ name: "android_test_stubs_current",
+ srcs: [
+ ":test-api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
+}
+
+java_system_modules {
+ name: "android_stubs_current_system_modules",
+ libs: ["android_stubs_current"],
+}
+
+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"],
+}
+
+/////////////////////////////////////////////////////////////////////
+// hwbinder.stubs provides APIs required for building HIDL Java
+// libraries.
+/////////////////////////////////////////////////////////////////////
+
+droidstubs {
+ name: "hwbinder-stubs-docs",
+ srcs: [
+ "core/java/android/os/HidlSupport.java",
+ "core/java/android/annotation/IntDef.java",
+ "core/java/android/annotation/IntRange.java",
+ "core/java/android/annotation/NonNull.java",
+ "core/java/android/annotation/SystemApi.java",
+ "core/java/android/os/HidlMemory.java",
+ "core/java/android/os/HwBinder.java",
+ "core/java/android/os/HwBlob.java",
+ "core/java/android/os/HwParcel.java",
+ "core/java/android/os/IHwBinder.java",
+ "core/java/android/os/IHwInterface.java",
+ "core/java/android/os/DeadObjectException.java",
+ "core/java/android/os/DeadSystemException.java",
+ "core/java/android/os/NativeHandle.java",
+ "core/java/android/os/RemoteException.java",
+ "core/java/android/util/AndroidException.java",
+ ],
+ installable: false,
+ sdk_version: "core_platform",
+ annotations_enabled: true,
+ previous_api: ":last-released-public-api",
+ merge_annotations_dirs: [
+ "metalava-manual",
+ ],
+ args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+java_library_static {
+ name: "hwbinder.stubs",
+ sdk_version: "core_current",
+ srcs: [
+ ":hwbinder-stubs-docs",
+ ],
+}
+
+/////////////////////////////////////////////////////////////////////
+// Stubs for hiddenapi processing.
+/////////////////////////////////////////////////////////////////////
+
+droidstubs {
+ name: "hiddenapi-lists-docs",
+ defaults: ["metalava-api-stubs-default"],
+ arg_files: [
+ "core/res/AndroidManifest.xml",
+ ],
+ dex_api_filename: "public-dex.txt",
+ private_dex_api_filename: "private-dex.txt",
+ removed_dex_api_filename: "removed-dex.txt",
+ args: metalava_framework_docs_args +
+ " --show-unannotated " +
+ " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.TestApi ",
+}
+
+droidstubs {
+ name: "hiddenapi-mappings",
+ defaults: ["metalava-api-stubs-default"],
+ srcs: [
+ ":opt-telephony-common-srcs",
+ ],
+
+ arg_files: [
+ "core/res/AndroidManifest.xml",
+ ],
+ dex_mapping_filename: "dex-mapping.txt",
+ args: metalava_framework_docs_args +
+ " --hide ReferencesHidden " +
+ " --hide UnhiddenSystemApi " +
+ " --show-unannotated " +
+ " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.TestApi ",
+}
+
+/////////////////////////////////////////////////////////////////////
+// api/*-current.txt files for use by modules in other directories
+// like the CTS test
+/////////////////////////////////////////////////////////////////////
+
+filegroup {
+ name: "frameworks-base-api-current.txt",
+ srcs: [
+ "api/current.txt",
+ ],
+}
+
+filegroup {
+ name: "frameworks-base-api-system-current.txt",
+ srcs: [
+ "api/system-current.txt",
+ ],
+}
+
+filegroup {
+ name: "frameworks-base-api-system-removed.txt",
+ srcs: [
+ "api/system-removed.txt",
+ ],
+}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index b1c4cad..2b12da2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -23,6 +23,34 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
}
],
"postsubmit-managedprofile-stress": [
diff --git a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
index c096cd2..4ed3b4e 100644
--- a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
@@ -95,7 +95,7 @@
mTraceMarkParser.forAllSlices((key, slices) -> {
for (TraceMarkSlice slice : slices) {
- state.addExtraResult(key, (long) (slice.getDurarionInSeconds() * NANOS_PER_S));
+ state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S));
}
});
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 278a786..661f32f 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -146,7 +146,8 @@
final CountDownLatch latch = new CountDownLatch(1);
registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
- // Don't use this.startUserInBackground() since only waiting until ACTION_USER_STARTED.
+ // Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
+ // ACTION_USER_STARTED.
mIam.startUserInBackground(userId);
latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
@@ -156,6 +157,48 @@
}
}
+ /**
+ * Measures the time until ACTION_USER_STARTED is received.
+ */
+ @Test
+ public void startUser() throws Exception {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createUserNoFlags();
+ final CountDownLatch latch = new CountDownLatch(1);
+ registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
+ mRunner.resumeTiming();
+
+ mIam.startUserInBackground(userId);
+ latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
+
+ mRunner.pauseTiming();
+ removeUser(userId);
+ mRunner.resumeTiming();
+ }
+ }
+
+ /**
+ * Measures the time until unlock listener is triggered and user is unlocked.
+ */
+ @Test
+ public void startAndUnlockUser() throws Exception {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int userId = createUserNoFlags();
+ mRunner.resumeTiming();
+
+ // Waits for UserState.mUnlockProgress.finish().
+ startUserInBackgroundAndWaitForUnlock(userId);
+
+ mRunner.pauseTiming();
+ removeUser(userId);
+ mRunner.resumeTiming();
+ }
+ }
+
+
+
@Test
public void switchUser() throws Exception {
while (mRunner.keepRunning()) {
@@ -309,7 +352,7 @@
final int userId = createManagedProfile();
mRunner.resumeTiming();
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userId);
@@ -326,11 +369,11 @@
mRunner.pauseTiming();
final int userId = createManagedProfile();
// Start the profile initially, then stop it. Similar to setQuietModeEnabled.
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
stopUser(userId, true);
mRunner.resumeTiming();
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userId);
@@ -352,7 +395,7 @@
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
mRunner.resumeTiming();
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
mRunner.pauseTiming();
@@ -376,13 +419,13 @@
final int userId = createManagedProfile();
WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
stopUser(userId, true);
TimeUnit.SECONDS.sleep(1); // Brief cool-down before re-starting profile.
mRunner.resumeTiming();
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
mRunner.pauseTiming();
@@ -423,7 +466,7 @@
mRunner.resumeTiming();
final int userId = createManagedProfile();
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startApp(userId, DUMMY_PACKAGE_NAME);
@@ -441,7 +484,7 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createManagedProfile();
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
mRunner.resumeTiming();
stopUser(userId, true);
@@ -467,7 +510,7 @@
final int userId = createManagedProfile();
mRunner.resumeTiming();
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userId);
@@ -490,7 +533,7 @@
final int userId = createManagedProfile();
mRunner.resumeTiming();
- startUserInBackground(userId);
+ startUserInBackgroundAndWaitForUnlock(userId);
mRunner.pauseTiming();
removeUser(userId);
@@ -526,18 +569,19 @@
}
/**
- * Start user in background and wait for it to unlock (equivalent to ACTION_USER_UNLOCKED).
- * To start in foreground instead, see {@link #switchUser(int)}.
- * This should always be used for profiles since profiles cannot be started in foreground.
+ * Start user in background and wait for it to unlock by waiting for
+ * UserState.mUnlockProgress.finish().
+ * <p> To start in foreground instead, see {@link #switchUser(int)}.
+ * <p> This should always be used for profiles since profiles cannot be started in foreground.
*/
- private void startUserInBackground(int userId) {
+ private void startUserInBackgroundAndWaitForUnlock(int userId) {
final ProgressWaiter waiter = new ProgressWaiter();
try {
mIam.startUserInBackgroundWithListener(userId, waiter);
boolean success = waiter.waitForFinish(TIMEOUT_IN_SECOND);
attestTrue("Failed to start user " + userId + " in background.", success);
} catch (RemoteException e) {
- Log.e(TAG, "startUserInBackground failed", e);
+ Log.e(TAG, "startUserInBackgroundAndWaitForUnlock failed", e);
}
}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index b075239..fe2b1f6 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -23,11 +23,15 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -140,6 +144,8 @@
/** @see #addExtraResult(String, long) */
private ArrayMap<String, ArrayList<Long>> mExtraResults;
+ private final List<Long> mTmpDurations = Arrays.asList(0L);
+
// Statistics. These values will be filled when the benchmark has finished.
// The computation needs double precision, but long int is fine for final reporting.
private Stats mStats;
@@ -188,14 +194,25 @@
if (duration < 0) {
throw new RuntimeException("duration is negative: " + duration);
}
+ mTmpDurations.set(0, duration);
+ return keepRunning(mTmpDurations);
+ }
+
+ /**
+ * Similar to the {@link #keepRunning(long)} but accepts a list of durations
+ */
+ public boolean keepRunning(List<Long> durations) {
switch (mState) {
case NOT_STARTED:
mState = WARMUP;
mWarmupStartTime = System.nanoTime();
return true;
case WARMUP: {
+ if (ArrayUtils.isEmpty(durations)) {
+ return true;
+ }
final long timeSinceStartingWarmup = System.nanoTime() - mWarmupStartTime;
- ++mWarmupIterations;
+ mWarmupIterations += durations.size();
if (mWarmupIterations >= WARMUP_MIN_ITERATIONS
&& timeSinceStartingWarmup >= mWarmupDurationNs) {
beginBenchmark(timeSinceStartingWarmup, mWarmupIterations);
@@ -203,7 +220,10 @@
return true;
}
case RUNNING: {
- mResults.add(duration);
+ if (ArrayUtils.isEmpty(durations)) {
+ return true;
+ }
+ mResults.addAll(durations);
final boolean keepRunning = mResults.size() < mMaxIterations;
if (!keepRunning) {
mStats = new Stats(mResults);
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
index 1afed3a..b15b6f6 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
@@ -40,6 +40,8 @@
private final Predicate<TraceMarkLine> mTraceLineFilter;
+ private static final long MICROS_PER_SECOND = 1000L * 1000L;
+
public TraceMarkParser(Predicate<TraceMarkLine> traceLineFilter) {
mTraceLineFilter = traceLineFilter;
}
@@ -116,13 +118,19 @@
}
}
+ public void reset() {
+ mSlicesMap.clear();
+ mDepthMap.clear();
+ mPendingStarts.clear();
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
forAllSlices((key, slices) -> {
double totalMs = 0;
for (TraceMarkSlice s : slices) {
- totalMs += s.getDurarionInSeconds() * 1000;
+ totalMs += s.getDurationInSeconds() * 1000;
}
sb.append(key).append(" count=").append(slices.size()).append(" avg=")
.append(totalMs / slices.size()).append("ms\n");
@@ -134,6 +142,10 @@
return sb.toString();
}
+ static double microsecondToSeconds(long ms) {
+ return (ms * 1.0d) / MICROS_PER_SECOND;
+ }
+
public static class TraceMarkSlice {
public final TraceMarkLine begin;
public final TraceMarkLine end;
@@ -143,7 +155,11 @@
this.end = end;
}
- public double getDurarionInSeconds() {
+ public double getDurationInSeconds() {
+ return microsecondToSeconds(end.timestamp - begin.timestamp);
+ }
+
+ public long getDurationInMicroseconds() {
return end.timestamp - begin.timestamp;
}
}
@@ -164,7 +180,7 @@
static final char SYNC_END = 'E';
public final String taskPid;
- public final double timestamp;
+ public final long timestamp; // in microseconds
public final String name;
public final boolean isAsync;
public final boolean isBegin;
@@ -179,7 +195,7 @@
if (timeBegin < 0) {
throw new IllegalArgumentException("Timestamp start not found");
}
- timestamp = Double.parseDouble(rawLine.substring(timeBegin, timeEnd));
+ timestamp = parseMicroseconds(rawLine.substring(timeBegin, timeEnd));
isAsync = type == ASYNC_START || type == ASYNC_FINISH;
isBegin = type == ASYNC_START || type == SYNC_BEGIN;
@@ -223,9 +239,29 @@
return null;
}
+ /**
+ * Parse the timestamp from atrace output, the format will be like:
+ * 84962.920719 where the decimal part will be always exactly 6 digits.
+ * ^^^^^ ^^^^^^
+ * | |
+ * sec microsec
+ */
+ static long parseMicroseconds(String line) {
+ int end = line.length();
+ long t = 0;
+ for (int i = 0; i < end; i++) {
+ char c = line.charAt(i);
+ if (c >= '0' && c <= '9') {
+ t = t * 10 + (c - '0');
+ }
+ }
+ return t;
+ }
+
@Override
public String toString() {
- return "TraceMarkLine{pid=" + taskPid + " time=" + timestamp + " name=" + name
+ return "TraceMarkLine{pid=" + taskPid + " time="
+ + microsecondToSeconds(timestamp) + " name=" + name
+ " async=" + isAsync + " begin=" + isBegin + "}";
}
}
diff --git a/apex/Android.bp b/apex/Android.bp
new file mode 100644
index 0000000..56f7db2
--- /dev/null
+++ b/apex/Android.bp
@@ -0,0 +1,42 @@
+// 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.
+
+mainline_stubs_args =
+ "--error UnhiddenSystemApi " +
+ "--hide BroadcastBehavior " +
+ "--hide DeprecationMismatch " +
+ "--hide HiddenSuperclass " +
+ "--hide HiddenTypedefConstant " +
+ "--hide HiddenTypeParameter " +
+ "--hide MissingPermission " +
+ "--hide RequiresPermission " +
+ "--hide SdkConstant " +
+ "--hide Todo " +
+ "--hide Typo " +
+ "--hide UnavailableSymbol "
+
+// TODO: remove this server classes are cleaned up.
+mainline_stubs_args += "--hide-package com.android.server "
+
+stubs_defaults {
+ name: "framework-module-stubs-defaults-publicapi",
+ args: mainline_stubs_args,
+ installable: false,
+}
+
+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\\) ",
+ installable: false,
+}
diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp
index bcdcc7d..b014fdc 100644
--- a/apex/appsearch/Android.bp
+++ b/apex/appsearch/Android.bp
@@ -14,9 +14,11 @@
apex {
name: "com.android.appsearch",
-
manifest: "apex_manifest.json",
-
+ java_libs: [
+ "framework-appsearch",
+ "service-appsearch",
+ ],
key: "com.android.appsearch.key",
certificate: ":com.android.appsearch.certificate",
}
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 0a65f73..3dc5a2c 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -23,17 +23,52 @@
java_library {
name: "framework-appsearch",
- installable: false,
- sdk_version: "core_platform",
- srcs: [
- ":framework-appsearch-sources",
- ],
- aidl: {
- export_include_dirs: [
- "java",
- ],
- },
+ installable: true,
+ sdk_version: "core_platform", // TODO(b/146218515) should be core_current
+ srcs: [":framework-appsearch-sources"],
libs: [
- "framework-minus-apex",
+ "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
],
}
+
+metalava_appsearch_docs_args =
+ "--hide-package com.android.server " +
+ "--error UnhiddenSystemApi " +
+ "--hide RequiresPermission " +
+ "--hide MissingPermission " +
+ "--hide BroadcastBehavior " +
+ "--hide HiddenSuperclass " +
+ "--hide DeprecationMismatch " +
+ "--hide UnavailableSymbol " +
+ "--hide SdkConstant " +
+ "--hide HiddenTypeParameter " +
+ "--hide Todo --hide Typo " +
+ "--hide HiddenTypedefConstant " +
+ "--show-annotation android.annotation.SystemApi "
+
+droidstubs {
+ name: "framework-appsearch-stubs-srcs",
+ srcs: [
+ ":framework-annotations",
+ ":framework-appsearch-sources",
+ ],
+ aidl: {
+ include_dirs: ["frameworks/base/core/java"],
+ },
+ args: metalava_appsearch_docs_args,
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+}
+
+java_library {
+ name: "framework-appsearch-stubs",
+ srcs: [":framework-appsearch-stubs-srcs"],
+ aidl: {
+ export_include_dirs: [
+ "java",
+ ],
+ },
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+ installable: false,
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
index fcebe3d..02cc967 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
@@ -15,19 +15,25 @@
*/
package android.app.appsearch;
+import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
import android.content.Context;
/**
- * This is where the AppSearchManagerService wrapper is registered.
+ * Class holding initialization code for the AppSearch module.
*
- * TODO(b/142567528): add comments when implement this class
* @hide
*/
+@SystemApi
public class AppSearchManagerFrameworkInitializer {
+ private AppSearchManagerFrameworkInitializer() {}
/**
- * TODO(b/142567528): add comments when implement this class
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers all AppSearch
+ * services to {@link Context}, so that {@link Context#getSystemService} can return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides
+ * {@link SystemServiceRegistry}
*/
public static void initialize() {
SystemServiceRegistry.registerStaticService(
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 701ea84..3f58c72 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -617,6 +617,14 @@
}
};
+ /** AlarmListener to start monitoring motion if there are registered stationary listeners. */
+ private final AlarmManager.OnAlarmListener mMotionRegistrationAlarmListener = () -> {
+ synchronized (DeviceIdleController.this) {
+ if (mStationaryListeners.size() > 0) {
+ startMonitoringMotionLocked();
+ }
+ }
+ };
private final AlarmManager.OnAlarmListener mMotionTimeoutAlarmListener = () -> {
synchronized (DeviceIdleController.this) {
@@ -753,6 +761,7 @@
@Override
public void onTrigger(TriggerEvent event) {
synchronized (DeviceIdleController.this) {
+ active = false;
motionLocked();
}
}
@@ -760,6 +769,8 @@
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (DeviceIdleController.this) {
+ mSensorManager.unregisterListener(this, mMotionSensor);
+ active = false;
motionLocked();
}
}
@@ -1878,6 +1889,27 @@
return controller.new MyHandler(BackgroundThread.getHandler().getLooper());
}
+ Sensor getMotionSensor() {
+ final SensorManager sensorManager = getSensorManager();
+ Sensor motionSensor = null;
+ int sigMotionSensorId = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
+ if (sigMotionSensorId > 0) {
+ motionSensor = sensorManager.getDefaultSensor(sigMotionSensorId, true);
+ }
+ if (motionSensor == null && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
+ motionSensor = sensorManager.getDefaultSensor(
+ Sensor.TYPE_WRIST_TILT_GESTURE, true);
+ }
+ if (motionSensor == null) {
+ // As a last ditch, fall back to SMD.
+ motionSensor = sensorManager.getDefaultSensor(
+ Sensor.TYPE_SIGNIFICANT_MOTION, true);
+ }
+ return motionSensor;
+ }
+
PowerManager getPowerManager() {
return mContext.getSystemService(PowerManager.class);
}
@@ -2031,21 +2063,7 @@
mSensorManager = mInjector.getSensorManager();
if (mUseMotionSensor) {
- int sigMotionSensorId = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_autoPowerModeAnyMotionSensor);
- if (sigMotionSensorId > 0) {
- mMotionSensor = mSensorManager.getDefaultSensor(sigMotionSensorId, true);
- }
- if (mMotionSensor == null && getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_autoPowerModePreferWristTilt)) {
- mMotionSensor = mSensorManager.getDefaultSensor(
- Sensor.TYPE_WRIST_TILT_GESTURE, true);
- }
- if (mMotionSensor == null) {
- // As a last ditch, fall back to SMD.
- mMotionSensor = mSensorManager.getDefaultSensor(
- Sensor.TYPE_SIGNIFICANT_MOTION, true);
- }
+ mMotionSensor = mInjector.getMotionSensor();
}
if (getContext().getResources().getBoolean(
@@ -3434,6 +3452,10 @@
if (mStationaryListeners.size() > 0) {
postStationaryStatusUpdated();
scheduleMotionTimeoutAlarmLocked();
+ // We need to re-register the motion listener, but we don't want the sensors to be
+ // constantly active or to churn the CPU by registering too early, register after some
+ // delay.
+ scheduleMotionRegistrationAlarmLocked();
}
if (mQuickDozeActivated && !mQuickDozeActivatedWhileIdling) {
// Don't exit idle due to motion if quick doze is enabled.
@@ -3500,9 +3522,12 @@
*/
private void maybeStopMonitoringMotionLocked() {
if (DEBUG) Slog.d(TAG, "maybeStopMonitoringMotionLocked()");
- if (mMotionSensor != null && mMotionListener.active && mStationaryListeners.size() == 0) {
- mMotionListener.unregisterLocked();
- cancelMotionTimeoutAlarmLocked();
+ if (mMotionSensor != null && mStationaryListeners.size() == 0) {
+ if (mMotionListener.active) {
+ mMotionListener.unregisterLocked();
+ cancelMotionTimeoutAlarmLocked();
+ }
+ cancelMotionRegistrationAlarmLocked();
}
}
@@ -3533,6 +3558,10 @@
mAlarmManager.cancel(mMotionTimeoutAlarmListener);
}
+ private void cancelMotionRegistrationAlarmLocked() {
+ mAlarmManager.cancel(mMotionRegistrationAlarmListener);
+ }
+
void cancelSensingTimeoutAlarmLocked() {
if (mNextSensingTimeoutAlarmTime != 0) {
mNextSensingTimeoutAlarmTime = 0;
@@ -3573,6 +3602,15 @@
mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
}
+ private void scheduleMotionRegistrationAlarmLocked() {
+ if (DEBUG) Slog.d(TAG, "scheduleMotionRegistrationAlarmLocked");
+ long nextMotionRegistrationAlarmTime =
+ mInjector.getElapsedRealtime() + mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextMotionRegistrationAlarmTime,
+ "DeviceIdleController.motion_registration", mMotionRegistrationAlarmListener,
+ mHandler);
+ }
+
private void scheduleMotionTimeoutAlarmLocked() {
if (DEBUG) Slog.d(TAG, "scheduleMotionAlarmLocked");
long nextMotionTimeoutAlarmTime =
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 525fbae..435384d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -372,9 +372,10 @@
continue;
}
- // TODO lastEvaluatedPriority should be evaluateJobPriorityLocked. (double check it)
- if (minPriorityForPreemption > nextPending.lastEvaluatedPriority) {
- minPriorityForPreemption = nextPending.lastEvaluatedPriority;
+ if (minPriorityForPreemption > jobPriority) {
+ // Step down the preemption threshold - wind up replacing
+ // the lowest-priority running job
+ minPriorityForPreemption = jobPriority;
selectedContextId = j;
// In this case, we're just going to preempt a low priority job, we're not
// actually starting a job, so don't set startingJob.
diff --git a/apex/sdkext/Android.bp b/apex/sdkext/Android.bp
index 40f3c45..5369a96 100644
--- a/apex/sdkext/Android.bp
+++ b/apex/sdkext/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_visibility: [":__subpackages__"],
+}
+
apex {
name: "com.android.sdkext",
manifest: "manifest.json",
@@ -26,6 +30,11 @@
certificate: ":com.android.sdkext.certificate",
}
+sdk {
+ name: "sdkext-sdk",
+ java_libs: [ "framework-sdkext-stubs-systemapi" ],
+}
+
apex_key {
name: "com.android.sdkext.key",
public_key: "com.android.sdkext.avbpubkey",
@@ -47,12 +56,17 @@
python_binary_host {
name: "gen_sdkinfo",
srcs: [
- "derive_sdk/sdk.proto",
+ "sdk.proto",
"gen_sdkinfo.py",
],
proto: {
canonical_path_from_root: false,
},
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
}
gensrcs {
diff --git a/apex/sdkext/TEST_MAPPING b/apex/sdkext/TEST_MAPPING
index 8dc732d..91947f3 100644
--- a/apex/sdkext/TEST_MAPPING
+++ b/apex/sdkext/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"presubmit": [
{
- "name": "framework-sdkext-tests"
+ "name": "CtsSdkExtTestCases"
}
]
}
diff --git a/apex/sdkext/derive_sdk/derive_sdk.cpp b/apex/sdkext/derive_sdk/derive_sdk.cpp
index 0aacebe..0a97116 100644
--- a/apex/sdkext/derive_sdk/derive_sdk.cpp
+++ b/apex/sdkext/derive_sdk/derive_sdk.cpp
@@ -63,15 +63,17 @@
LOG(ERROR) << "failed to parse " << path;
continue;
}
+ LOG(INFO) << "Read version " << sdk_version.version() << " from " << path;
versions.push_back(sdk_version.version());
}
auto itr = std::min_element(versions.begin(), versions.end());
std::string prop_value = itr == versions.end() ? "0" : std::to_string(*itr);
- if (!android::base::SetProperty("persist.com.android.sdkext.sdk_info", prop_value)) {
+ if (!android::base::SetProperty("ro.build.version.extensions.r", prop_value)) {
LOG(ERROR) << "failed to set sdk_info prop";
return EXIT_FAILURE;
}
+ LOG(INFO) << "R extension version is " << prop_value;
return EXIT_SUCCESS;
}
diff --git a/apex/sdkext/framework/Android.bp b/apex/sdkext/framework/Android.bp
index b17f0f8..a50dc3d 100644
--- a/apex/sdkext/framework/Android.bp
+++ b/apex/sdkext/framework/Android.bp
@@ -12,12 +12,17 @@
// 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 {
@@ -27,4 +32,40 @@
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/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
index c039a82..a8a7eff 100644
--- a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
+++ b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
@@ -17,25 +17,40 @@
package android.os.ext;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.os.Build.VERSION_CODES;
import android.os.SystemProperties;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-/** @hide */
+/**
+ * Methods for interacting with the extension SDK.
+ *
+ * This class provides information about the extension SDK version present
+ * on this device. Use the {@link #getExtensionVersion(int) getExtension} to
+ * query for the extension version for the given SDK version.
+
+ * @hide
+ */
+@SystemApi
public class SdkExtensions {
private static final int R_EXTENSION_INT;
static {
- R_EXTENSION_INT = SystemProperties.getInt("persist.com.android.sdkext.sdk_info", 0);
+ R_EXTENSION_INT = SystemProperties.getInt("ro.build.version.extensions.r", 0);
}
- /** Values suitable as parameters for {@link #getExtensionVersion(int)}. */
+ /**
+ * Values suitable as parameters for {@link #getExtensionVersion(int)}.
+ * @hide
+ */
@IntDef(value = { VERSION_CODES.R })
@Retention(RetentionPolicy.SOURCE)
public @interface SdkVersion {}
+ private SdkExtensions() { }
+
/**
* Return the version of the extension to the given SDK.
*
@@ -45,7 +60,7 @@
*/
public static int getExtensionVersion(@SdkVersion int sdk) {
if (sdk < VERSION_CODES.R) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException(String.valueOf(sdk) + " does not have extensions");
}
return R_EXTENSION_INT;
}
diff --git a/apex/sdkext/framework/tests/Android.bp b/apex/sdkext/framework/tests/Android.bp
deleted file mode 100644
index ab63275..0000000
--- a/apex/sdkext/framework/tests/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-android_test {
- name: "framework-sdkext-tests",
- srcs: ["src/**/*.java"],
- libs: [
- "android.test.base",
- "android.test.runner",
- ],
- static_libs: [ "framework-sdkext" ],
- test_suites: [ "general-tests" ],
- platform_apis: true,
-}
diff --git a/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java b/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java
deleted file mode 100644
index d7dca90..0000000
--- a/apex/sdkext/framework/tests/src/android/os/ext/SdkExtensionsTest.java
+++ /dev/null
@@ -1,40 +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 android.os.ext;
-
-import android.os.Build;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.TestCase;
-
-public class SdkExtensionsTest extends TestCase {
-
- @SmallTest
- public void testBadArgument() throws Exception {
- try {
- SdkExtensions.getExtensionVersion(Build.VERSION_CODES.Q);
- fail("expected IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
- }
-
- @SmallTest
- public void testDefault() throws Exception {
- int r = SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R);
- assertTrue(r >= 0);
- }
-
-}
diff --git a/tests/utils/testutils/java/test/package-info.java b/apex/sdkext/sdk.proto
similarity index 76%
copy from tests/utils/testutils/java/test/package-info.java
copy to apex/sdkext/sdk.proto
index c34d7b2..d15b935 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/apex/sdkext/sdk.proto
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+syntax = "proto3";
+package com.android.sdkext.proto;
+
+option java_outer_classname = "SdkProto";
+option optimize_for = LITE_RUNTIME;
+
+message SdkVersion {
+ int32 version = 1;
+}
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index d76a40e..09ca1d2 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -14,20 +14,20 @@
apex {
name: "com.android.os.statsd",
-
+ defaults: ["com.android.os.statsd-defaults"],
manifest: "apex_manifest.json",
+}
- // optional. if unspecified, a default one is auto-generated
- //androidManifest: "AndroidManifest.xml",
-
+apex_defaults {
// libc.so and libcutils.so are included in the apex
// native_shared_libs: ["libc", "libcutils"],
// binaries: ["vold"],
- // java_libs: ["core-all"],
+ java_libs: [
+ "framework-statsd",
+ "service-statsd",
+ ],
// prebuilts: ["my_prebuilt"],
-
- compile_multilib: "both",
-
+ name: "com.android.os.statsd-defaults",
key: "com.android.os.statsd.key",
certificate: ":com.android.os.statsd.certificate",
}
diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp
index e6ca544..aed6ad9 100644
--- a/apex/statsd/aidl/Android.bp
+++ b/apex/statsd/aidl/Android.bp
@@ -17,6 +17,18 @@
// TODO(b/145815909): move StatsDimensionsValue.aidl and StatsLogEventWrapper.aidl here
filegroup {
name: "statsd_aidl",
+ srcs: [
+ "android/os/IPullAtomCallback.aidl",
+ "android/os/IPullAtomResultReceiver.aidl",
+ "android/os/IStatsCompanionService.aidl",
+ "android/os/IStatsd.aidl",
+ "android/os/IStatsPullerCallback.aidl",
+ "android/util/StatsEventParcel.aidl",
+ ],
+}
+
+filegroup {
+ name: "statsd_java_aidl",
srcs: ["**/*.aidl"],
}
diff --git a/apex/statsd/aidl/android/os/IPullAtomCallback.aidl b/apex/statsd/aidl/android/os/IPullAtomCallback.aidl
index 88d3c3e..ff0b97b 100644
--- a/apex/statsd/aidl/android/os/IPullAtomCallback.aidl
+++ b/apex/statsd/aidl/android/os/IPullAtomCallback.aidl
@@ -26,6 +26,6 @@
/**
* Initiate a request for a pull for an atom.
*/
- void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver);
+ oneway void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver);
}
diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
index 22a2537..5a6118e 100644
--- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
@@ -90,4 +90,7 @@
/** 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
new file mode 100644
index 0000000..45ba3a2
--- /dev/null
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -0,0 +1,65 @@
+/**
+ * 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.app.PendingIntent;
+
+/**
+ * Binder interface to communicate with the Java-based statistics service helper.
+ * Contains parcelable objects available only in Java.
+ * {@hide}
+ */
+interface IStatsManagerService {
+
+ /**
+ * Registers the given pending intent for this config key. This intent is invoked when the
+ * 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.
+ */
+ void setDataFetchOperation(long configKey, in PendingIntent pendingIntent,
+ 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
+ * active. There can be at most one active configs changed listener per package.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ long[] setActiveConfigsChangedOperation(in PendingIntent pendingIntent, in String packageName);
+
+ /**
+ * Set the PendingIntent 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
+ * 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 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 PendingIntent is stored.
+ *
+ * Requires Manifest.permission.DUMP.
+ */
+ void setBroadcastSubscriber(long configKey, long subscriberId, in PendingIntent pendingIntent,
+ in String packageName);
+}
\ No newline at end of file
diff --git a/apex/statsd/aidl/android/os/IStatsManager.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
similarity index 98%
rename from apex/statsd/aidl/android/os/IStatsManager.aidl
rename to apex/statsd/aidl/android/os/IStatsd.aidl
index cc62f07..cce79fa 100644
--- a/apex/statsd/aidl/android/os/IStatsManager.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -24,7 +24,7 @@
* Binder interface to communicate with the statistics management service.
* {@hide}
*/
-interface IStatsManager {
+interface IStatsd {
/**
* Tell the stats daemon that the android system server is up and running.
*/
@@ -215,6 +215,11 @@
*/
oneway void unregisterPullerCallback(int atomTag, String packageName);
+ /**
+ * Unregisters any pullAtomCallback for the given uid/atom.
+ */
+ oneway void unregisterPullAtomCallback(int uid, int atomTag);
+
/**
* The install requires staging.
*/
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
new file mode 100644
index 0000000..a2b0577
--- /dev/null
+++ b/apex/statsd/framework/Android.bp
@@ -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.
+
+filegroup {
+ name: "framework-statsd-sources",
+ srcs: [
+ "java/**/*.java",
+ ],
+ path: "java",
+}
+
+java_library {
+ name: "framework-statsd",
+ installable: true,
+ // TODO(b/146209659): Use system_current instead.
+ sdk_version: "core_current",
+ srcs: [
+ ":framework-statsd-sources",
+ ],
+ permitted_packages: [
+ "android.app",
+ "android.util",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ // TODO(b/146230220): Use framework-system-stubs instead.
+ "android_system_stubs_current",
+ ],
+ // TODO:(b/146210774): Add apex_available field.
+}
+
+droidstubs {
+ name: "framework-statsd-stubs-docs",
+ defaults: [
+ "framework-module-stubs-defaults-publicapi"
+ ],
+ srcs: [
+ ":framework-statsd-sources",
+ ],
+ libs: [
+ "framework-all",
+ ],
+ sdk_version: "core_platform",
+}
+
+// TODO(b/146167933): Use these stubs in frameworks/base/Android.bp
+java_library {
+ name: "framework-statsd-stubs",
+ srcs: [
+ ":framework-statsd-stubs-docs",
+ ],
+ libs: [
+ "framework-all",
+ ],
+ sdk_version: "core_platform",
+}
diff --git a/core/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java
similarity index 91%
rename from core/java/android/util/StatsEvent.java
rename to apex/statsd/framework/java/android/util/StatsEvent.java
index 7e71640..c765945 100644
--- a/core/java/android/util/StatsEvent.java
+++ b/apex/statsd/framework/java/android/util/StatsEvent.java
@@ -31,14 +31,23 @@
*
* <p>Usage:</p>
* <pre>
+ * // Pushed event
+ * StatsEvent statsEvent = StatsEvent.newBuilder()
+ * .setAtomId(atomId)
+ * .writeBoolean(false)
+ * .writeString("annotated String field")
+ * .addBooleanAnnotation(annotationId, true)
+ * .usePooledBuffer()
+ * .build();
+ * StatsLog.write(statsEvent);
+ *
+ * // Pulled event
* StatsEvent statsEvent = StatsEvent.newBuilder()
* .setAtomId(atomId)
* .writeBoolean(false)
* .writeString("annotated String field")
* .addBooleanAnnotation(annotationId, true)
* .build();
- *
- * StatsLog.write(statsEvent);
* </pre>
* @hide
**/
@@ -210,12 +219,15 @@
private static final int MAX_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4;
private final int mAtomId;
- private final Buffer mBuffer;
+ private final byte[] mPayload;
+ private Buffer mBuffer;
private final int mNumBytes;
- private StatsEvent(final int atomId, @NonNull final Buffer buffer, final int numBytes) {
+ private StatsEvent(final int atomId, @Nullable final Buffer buffer,
+ @NonNull final byte[] payload, final int numBytes) {
mAtomId = atomId;
mBuffer = buffer;
+ mPayload = payload;
mNumBytes = numBytes;
}
@@ -243,7 +255,7 @@
**/
@NonNull
public byte[] getBytes() {
- return mBuffer.getBytes();
+ return mPayload;
}
/**
@@ -256,10 +268,14 @@
}
/**
- * Recycle this StatsEvent object.
+ * Recycle resources used by this StatsEvent object.
+ * No actions should be taken on this StatsEvent after release() is called.
**/
public void release() {
- mBuffer.release();
+ if (mBuffer != null) {
+ mBuffer.release();
+ mBuffer = null;
+ }
}
/**
@@ -280,7 +296,18 @@
* optional string field3 = 3 [(annotation1) = true];
* }
*
- * // StatsEvent construction.
+ * // StatsEvent construction for pushed event.
+ * StatsEvent.newBuilder()
+ * StatsEvent statsEvent = StatsEvent.newBuilder()
+ * .setAtomId(atomId)
+ * .writeInt(3) // field1
+ * .writeLong(8L) // field2
+ * .writeString("foo") // field 3
+ * .addBooleanAnnotation(annotation1Id, true)
+ * .usePooledBuffer()
+ * .build();
+ *
+ * // StatsEvent construction for pulled event.
* StatsEvent.newBuilder()
* StatsEvent statsEvent = StatsEvent.newBuilder()
* .setAtomId(atomId)
@@ -306,6 +333,7 @@
private byte mLastType;
private int mNumElements;
private int mErrorMask;
+ private boolean mUsePooledBuffer = false;
private Builder(final Buffer buffer) {
mBuffer = buffer;
@@ -569,6 +597,17 @@
}
/**
+ * Indicates to reuse Buffer's byte array as the underlying payload in StatsEvent.
+ * This should be called for pushed events to reduce memory allocations and garbage
+ * collections.
+ **/
+ @NonNull
+ public Builder usePooledBuffer() {
+ mUsePooledBuffer = true;
+ return this;
+ }
+
+ /**
* Builds a StatsEvent object with values entered in this Builder.
**/
@NonNull
@@ -599,7 +638,18 @@
size = mPos;
}
- return new StatsEvent(mAtomId, mBuffer, size);
+ if (mUsePooledBuffer) {
+ return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size);
+ } else {
+ // Create a copy of the buffer with the required number of bytes.
+ final byte[] payload = new byte[size];
+ System.arraycopy(mBuffer.getBytes(), 0, payload, 0, size);
+
+ // Return Buffer instance to the pool.
+ mBuffer.release();
+
+ return new StatsEvent(mAtomId, null, payload, size);
+ }
}
private void writeTypeId(final byte typeId) {
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index f71d74f..f3a8989 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -1,5 +1,5 @@
// Statsd Service jar, which will eventually be put in the statsd mainline apex.
-// service-statsd needs to be added to PRODUCT_SYSTEM_SERVER_JARS.
+// service-statsd needs to be added to PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS.
// This jar will contain StatsCompanionService
java_library {
name: "service-statsd",
@@ -8,9 +8,9 @@
srcs: [
"java/**/*.java",
],
-
+ // TODO: link against the proper stubs (b/146084685).
libs: [
- "framework",
+ "framework-minus-apex",
"services.core",
],
}
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
new file mode 100644
index 0000000..71b52e2
--- /dev/null
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
@@ -0,0 +1,70 @@
+/*
+ * 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.stats;
+
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+/**
+ * @hide
+ */
+public class StatsCompanion {
+ private static final String TAG = "StatsCompanion";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Lifecycle class for both {@link StatsCompanionService} and {@link StatsManagerService}.
+ */
+ public static final class Lifecycle extends SystemService {
+ private StatsCompanionService mStatsCompanionService;
+ private StatsManagerService mStatsManagerService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mStatsCompanionService = new StatsCompanionService(getContext());
+ mStatsManagerService = new StatsManagerService(getContext());
+ mStatsCompanionService.setStatsManagerService(mStatsManagerService);
+ mStatsManagerService.setStatsCompanionService(mStatsCompanionService);
+
+ try {
+ publishBinderService(Context.STATS_COMPANION_SERVICE,
+ mStatsCompanionService);
+ if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
+ publishBinderService(Context.STATS_MANAGER_SERVICE,
+ mStatsManagerService);
+ if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_MANAGER_SERVICE);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to publishBinderService", e);
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mStatsCompanionService.systemReady();
+ mStatsManagerService.systemReady();
+ }
+ }
+ }
+}
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 6fb3bc4..7ed51ca 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -79,7 +79,7 @@
import android.os.IBinder;
import android.os.IPullAtomCallback;
import android.os.IStatsCompanionService;
-import android.os.IStatsManager;
+import android.os.IStatsd;
import android.os.IStoraged;
import android.os.IThermalEventListener;
import android.os.IThermalService;
@@ -112,7 +112,6 @@
import android.util.Slog;
import android.util.StatsLog;
import android.util.proto.ProtoOutputStream;
-import android.util.proto.ProtoStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.IProcessStats;
@@ -137,7 +136,6 @@
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;
@@ -173,6 +171,7 @@
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -268,7 +267,7 @@
private final AlarmManager mAlarmManager;
private final INetworkStatsService mNetworkStatsService;
@GuardedBy("sStatsdLock")
- private static IStatsManager sStatsd;
+ private static IStatsd sStatsd;
private static final Object sStatsdLock = new Object();
private final OnAlarmListener mAnomalyAlarmListener = new AnomalyAlarmListener();
@@ -278,6 +277,8 @@
private final BroadcastReceiver mUserUpdateReceiver;
private final ShutdownEventReceiver mShutdownEventReceiver;
+ private StatsManagerService mStatsManagerService;
+
private static final class PullerKey {
private final int mUid;
private final int mAtomTag;
@@ -541,9 +542,9 @@
private void informAllUidsLocked(Context context) throws RemoteException {
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
PackageManager pm = context.getPackageManager();
- final List<UserInfo> users = um.getUsers(true);
+ final List<UserHandle> users = um.getUserHandles(true);
if (DEBUG) {
- Slog.d(TAG, "Iterating over " + users.size() + " profiles.");
+ Slog.d(TAG, "Iterating over " + users.size() + " userHandles.");
}
ParcelFileDescriptor[] fds;
@@ -570,11 +571,11 @@
ProtoOutputStream output = new ProtoOutputStream(fout);
int numRecords = 0;
// Add in all the apps for every user/profile.
- for (UserInfo profile : users) {
+ for (UserHandle userHandle : users) {
List<PackageInfo> pi =
pm.getInstalledPackagesAsUser(PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_ANY_USER,
- profile.id);
+ userHandle.getIdentifier());
for (int j = 0; j < pi.size(); j++) {
if (pi.get(j).applicationInfo != null) {
String installer;
@@ -584,23 +585,24 @@
installer = "";
}
long applicationInfoToken =
- output.start(ProtoStream.FIELD_TYPE_MESSAGE
- | ProtoStream.FIELD_COUNT_REPEATED
+ output.start(ProtoOutputStream.FIELD_TYPE_MESSAGE
+ | ProtoOutputStream.FIELD_COUNT_REPEATED
| APPLICATION_INFO_FIELD_ID);
- output.write(ProtoStream.FIELD_TYPE_INT32
- | ProtoStream.FIELD_COUNT_SINGLE | UID_FIELD_ID,
+ output.write(ProtoOutputStream.FIELD_TYPE_INT32
+ | ProtoOutputStream.FIELD_COUNT_SINGLE | UID_FIELD_ID,
pi.get(j).applicationInfo.uid);
- output.write(ProtoStream.FIELD_TYPE_INT64
- | ProtoStream.FIELD_COUNT_SINGLE
+ output.write(ProtoOutputStream.FIELD_TYPE_INT64
+ | ProtoOutputStream.FIELD_COUNT_SINGLE
| VERSION_FIELD_ID, pi.get(j).getLongVersionCode());
- output.write(ProtoStream.FIELD_TYPE_STRING
- | ProtoStream.FIELD_COUNT_SINGLE | VERSION_STRING_FIELD_ID,
+ output.write(ProtoOutputStream.FIELD_TYPE_STRING
+ | ProtoOutputStream.FIELD_COUNT_SINGLE
+ | VERSION_STRING_FIELD_ID,
pi.get(j).versionName);
- output.write(ProtoStream.FIELD_TYPE_STRING
- | ProtoStream.FIELD_COUNT_SINGLE
+ output.write(ProtoOutputStream.FIELD_TYPE_STRING
+ | ProtoOutputStream.FIELD_COUNT_SINGLE
| PACKAGE_NAME_FIELD_ID, pi.get(j).packageName);
- output.write(ProtoStream.FIELD_TYPE_STRING
- | ProtoStream.FIELD_COUNT_SINGLE
+ output.write(ProtoOutputStream.FIELD_TYPE_STRING
+ | ProtoOutputStream.FIELD_COUNT_SINGLE
| INSTALLER_FIELD_ID,
installer == null ? "" : installer);
numRecords++;
@@ -2135,8 +2137,8 @@
pulledData.add(e);
}
- private void pullDangerousPermissionState(long elapsedNanos, final long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
+ private void pullDangerousPermissionState(int atomId, long elapsedNanos,
+ final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
long token = Binder.clearCallingIdentity();
Set<Integer> reportedUids = new HashSet<>();
try {
@@ -2165,6 +2167,11 @@
}
reportedUids.add(pkg.applicationInfo.uid);
+ if (atomId == StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED
+ && ThreadLocalRandom.current().nextFloat() > 0.2f) {
+ continue;
+ }
+
int numPerms = pkg.requestedPermissions.length;
for (int permNum = 0; permNum < numPerms; permNum++) {
String permName = pkg.requestedPermissions[permNum];
@@ -2174,7 +2181,7 @@
try {
permissionInfo = pm.getPermissionInfo(permName, 0);
permissionFlags =
- pm.getPermissionFlags(permName, pkg.packageName, user);
+ pm.getPermissionFlags(permName, pkg.packageName, user);
} catch (PackageManager.NameNotFoundException ignored) {
continue;
@@ -2185,11 +2192,13 @@
}
StatsLogEventWrapper e = new StatsLogEventWrapper(
- StatsLog.DANGEROUS_PERMISSION_STATE, elapsedNanos, wallClockNanos);
+ atomId, elapsedNanos, wallClockNanos);
e.writeString(permName);
e.writeInt(pkg.applicationInfo.uid);
- e.writeString(null);
+ if (atomId == StatsLog.DANGEROUS_PERMISSION_STATE) {
+ e.writeString(null);
+ }
e.writeBoolean((pkg.requestedPermissionsFlags[permNum]
& REQUESTED_PERMISSION_GRANTED) != 0);
e.writeInt(permissionFlags);
@@ -2639,7 +2648,13 @@
break;
}
case StatsLog.DANGEROUS_PERMISSION_STATE: {
- pullDangerousPermissionState(elapsedNanos, wallClockNanos, ret);
+ 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: {
@@ -2681,6 +2696,7 @@
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);
@@ -2736,53 +2752,53 @@
}
}
- // Lifecycle and related code
+ @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
/**
* Fetches the statsd IBinder service.
* Note: This should only be called from sayHiToStatsd. All other clients should use the cached
* sStatsd with a null check.
*/
- private static IStatsManager fetchStatsdService() {
- return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
- }
-
- public static final class Lifecycle extends SystemService {
- private StatsCompanionService mStatsCompanionService;
-
- public Lifecycle(Context context) {
- super(context);
- }
-
- @Override
- public void onStart() {
- mStatsCompanionService = new StatsCompanionService(getContext());
- try {
- publishBinderService(Context.STATS_COMPANION_SERVICE,
- mStatsCompanionService);
- if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
- } catch (Exception e) {
- Slog.e(TAG, "Failed to publishBinderService", e);
- }
- }
-
- @Override
- public void onBootPhase(int phase) {
- super.onBootPhase(phase);
- if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- mStatsCompanionService.systemReady();
- }
- }
+ private static IStatsd fetchStatsdService() {
+ return IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
}
/**
* Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
*/
- private void systemReady() {
+ void systemReady() {
if (DEBUG) Slog.d(TAG, "Learned that systemReady");
sayHiToStatsd();
}
+ void setStatsManagerService(StatsManagerService statsManagerService) {
+ mStatsManagerService = statsManagerService;
+ }
+
/**
* Tells statsd that statscompanion is ready. If the binder call returns, link to
* statsd.
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
new file mode 100644
index 0000000..f3bf909
--- /dev/null
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -0,0 +1,108 @@
+/*
+ * 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.stats;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IStatsManagerService;
+import android.os.IStatsd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * @hide
+ */
+public class StatsManagerService extends IStatsManagerService.Stub {
+
+ 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 StatsCompanionService mStatsCompanionService;
+
+ public StatsManagerService(Context context) {
+ super();
+ }
+
+ @Override
+ public void setDataFetchOperation(long configKey, PendingIntent pendingIntent,
+ String packageName) {
+ // no-op
+ if (DEBUG) {
+ Slog.d(TAG, "setDataFetchOperation");
+ }
+ }
+
+ @Override
+ public long[] setActiveConfigsChangedOperation(PendingIntent pendingIntent,
+ String packageName) {
+ // no-op
+ if (DEBUG) {
+ Slog.d(TAG, "setActiveConfigsChangedOperation");
+ }
+ return new long[]{};
+ }
+
+ @Override
+ public void setBroadcastSubscriber(long configKey, long subscriberId,
+ PendingIntent pendingIntent, String packageName) {
+ //no-op
+ if (DEBUG) {
+ Slog.d(TAG, "setBroadcastSubscriber");
+ }
+ }
+
+ void setStatsCompanionService(StatsCompanionService statsCompanionService) {
+ mStatsCompanionService = statsCompanionService;
+ }
+
+ void systemReady() {
+ if (DEBUG) {
+ Slog.d(TAG, "statsdReady");
+ }
+ setupStatsManagerService();
+ }
+
+ 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"));
+ }
+ return;
+ }
+ sStatsd = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
+ // 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);
+ }
+ }
+ }
+}
diff --git a/apex/statsd/testing/Android.bp b/apex/statsd/testing/Android.bp
new file mode 100644
index 0000000..22e7301
--- /dev/null
+++ b/apex/statsd/testing/Android.bp
@@ -0,0 +1,25 @@
+// 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.os.statsd",
+ visibility: [
+ "//system/apex/tests",
+ ],
+ defaults: ["com.android.os.statsd-defaults"],
+ manifest: "test_manifest.json",
+ file_contexts: ":com.android.os.statsd-file_contexts",
+ // Test APEX, should never be installed
+ installable: false,
+}
diff --git a/apex/statsd/testing/test_manifest.json b/apex/statsd/testing/test_manifest.json
new file mode 100644
index 0000000..57343d3
--- /dev/null
+++ b/apex/statsd/testing/test_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.os.statsd",
+ "version": 2147483647
+}
diff --git a/api/current.txt b/api/current.txt
index 542267c..4d5d1a1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -99,6 +99,7 @@
field public static final String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
field public static final String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
field public static final String NFC = "android.permission.NFC";
+ field public static final String NFC_PREFERRED_PAYMENT_INFO = "android.permission.NFC_PREFERRED_PAYMENT_INFO";
field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
@@ -3954,6 +3955,7 @@
public class ActivityManager {
method public int addAppTask(@NonNull android.app.Activity, @NonNull android.content.Intent, @Nullable android.app.ActivityManager.TaskDescription, @NonNull android.graphics.Bitmap);
+ method public void appNotResponding(@NonNull String);
method public boolean clearApplicationUserData();
method public void clearWatchHeapLimit();
method @RequiresPermission(android.Manifest.permission.DUMP) public void dumpPackageState(java.io.FileDescriptor, String);
@@ -4308,7 +4310,7 @@
method public static String permissionToOp(String);
method public void setNotedAppOpsCollector(@Nullable android.app.AppOpsManager.AppOpsCollector);
method @Deprecated public int startOp(@NonNull String, int, @NonNull String);
- method public int startOp(@NonNull String, int, @Nullable String, @NonNull String, @Nullable String);
+ method public int startOp(@NonNull String, int, @Nullable String, @Nullable String, @Nullable String);
method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
method public int startOpNoThrow(@NonNull String, int, @NonNull String, @NonNull String, @Nullable String);
method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
@@ -6732,6 +6734,7 @@
method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
method public boolean getAutoTime(@NonNull android.content.ComponentName);
method @Deprecated public boolean getAutoTimeRequired();
+ method public boolean getAutoTimeZone(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(@NonNull android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
@@ -6739,6 +6742,7 @@
method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
+ method @NonNull public java.util.Set<java.lang.String> getCrossProfilePackages(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String);
@@ -6849,6 +6853,7 @@
method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
method public void setAutoTime(@NonNull android.content.ComponentName, boolean);
method @Deprecated public void setAutoTimeRequired(@NonNull android.content.ComponentName, boolean);
+ method public void setAutoTimeZone(@NonNull android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(@NonNull android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
@@ -6856,6 +6861,7 @@
method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
+ method public void setCrossProfilePackages(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
method public void setDefaultSmsApplication(@NonNull android.content.ComponentName, @NonNull String);
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);
@@ -9967,6 +9973,7 @@
field public static final String DOWNLOAD_SERVICE = "download";
field public static final String DROPBOX_SERVICE = "dropbox";
field public static final String EUICC_SERVICE = "euicc";
+ field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
field public static final String FINGERPRINT_SERVICE = "fingerprint";
field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final String INPUT_METHOD_SERVICE = "input_method";
@@ -16776,16 +16783,29 @@
package android.hardware.biometrics {
public class BiometricManager {
- method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
+ method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int);
field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
}
+ public static interface BiometricManager.Authenticators {
+ field public static final int BIOMETRIC_STRONG = 15; // 0xf
+ field public static final int BIOMETRIC_WEAK = 255; // 0xff
+ field public static final int DEVICE_CREDENTIAL = 32768; // 0x8000
+ }
+
public class BiometricPrompt {
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject, @NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
+ method @Nullable public int getAllowedAuthenticators();
+ method @Nullable public CharSequence getDescription();
+ method @Nullable public CharSequence getNegativeButtonText();
+ method @Nullable public CharSequence getSubtitle();
+ method @NonNull public CharSequence getTitle();
+ method public boolean isConfirmationRequired();
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
@@ -16821,9 +16841,10 @@
public static class BiometricPrompt.Builder {
ctor public BiometricPrompt.Builder(android.content.Context);
method @NonNull public android.hardware.biometrics.BiometricPrompt build();
+ method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setAllowedAuthenticators(int);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setConfirmationRequired(boolean);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
- method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
+ method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -16916,7 +16937,7 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Rational> CONTROL_AE_COMPENSATION_STEP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AE_LOCK_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AF_AVAILABLE_MODES;
- field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.CapabilityAndMaxSize[]> CONTROL_AVAILABLE_BOKEH_CAPABILITIES;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.Capability[]> CONTROL_AVAILABLE_BOKEH_CAPABILITIES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_EFFECTS;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_SCENE_MODES;
@@ -16927,6 +16948,7 @@
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AF;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AWB;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> CONTROL_ZOOM_RATIO_RANGE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> DEPTH_DEPTH_IS_EXCLUSIVE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> DISTORTION_CORRECTION_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
@@ -17228,6 +17250,7 @@
field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1
field public static final int REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME = 12; // 0xc
field public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; // 0xa
+ field public static final int REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING = 15; // 0xf
field public static final int REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING = 4; // 0x4
field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3
field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5
@@ -17338,6 +17361,7 @@
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EDGE_MODE;
@@ -17424,6 +17448,7 @@
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_MODE;
@@ -17513,9 +17538,10 @@
field public static final int COUNT = 4; // 0x4
}
- public final class CapabilityAndMaxSize {
+ public final class Capability {
method @NonNull public android.util.Size getMaxStreamingSize();
method public int getMode();
+ method @NonNull public android.util.Range<java.lang.Float> getZoomRatioRange();
}
public final class ColorSpaceTransform {
@@ -19594,6 +19620,197 @@
}
+package android.icu.number {
+
+ public class CompactNotation extends android.icu.number.Notation {
+ }
+
+ public abstract class CurrencyPrecision extends android.icu.number.Precision {
+ method public android.icu.number.Precision withCurrency(android.icu.util.Currency);
+ }
+
+ public class FormattedNumber implements java.lang.CharSequence {
+ method public char charAt(int);
+ method public int length();
+ method public CharSequence subSequence(int, int);
+ method public java.math.BigDecimal toBigDecimal();
+ method public java.text.AttributedCharacterIterator toCharacterIterator();
+ }
+
+ public class FormattedNumberRange implements java.lang.CharSequence {
+ method public char charAt(int);
+ method public java.math.BigDecimal getFirstBigDecimal();
+ method public android.icu.number.NumberRangeFormatter.RangeIdentityResult getIdentityResult();
+ method public java.math.BigDecimal getSecondBigDecimal();
+ method public int length();
+ method public CharSequence subSequence(int, int);
+ method public java.text.AttributedCharacterIterator toCharacterIterator();
+ }
+
+ public abstract class FractionPrecision extends android.icu.number.Precision {
+ method public android.icu.number.Precision withMaxDigits(int);
+ method public android.icu.number.Precision withMinDigits(int);
+ }
+
+ public class IntegerWidth {
+ method public android.icu.number.IntegerWidth truncateAt(int);
+ method public static android.icu.number.IntegerWidth zeroFillTo(int);
+ }
+
+ public class LocalizedNumberFormatter extends android.icu.number.NumberFormatterSettings<android.icu.number.LocalizedNumberFormatter> {
+ method public android.icu.number.FormattedNumber format(long);
+ method public android.icu.number.FormattedNumber format(double);
+ method public android.icu.number.FormattedNumber format(Number);
+ method public android.icu.number.FormattedNumber format(android.icu.util.Measure);
+ method public java.text.Format toFormat();
+ }
+
+ public class LocalizedNumberRangeFormatter extends android.icu.number.NumberRangeFormatterSettings<android.icu.number.LocalizedNumberRangeFormatter> {
+ method public android.icu.number.FormattedNumberRange formatRange(int, int);
+ method public android.icu.number.FormattedNumberRange formatRange(double, double);
+ method public android.icu.number.FormattedNumberRange formatRange(Number, Number);
+ }
+
+ public class Notation {
+ method public static android.icu.number.CompactNotation compactLong();
+ method public static android.icu.number.CompactNotation compactShort();
+ method public static android.icu.number.ScientificNotation engineering();
+ method public static android.icu.number.ScientificNotation scientific();
+ method public static android.icu.number.SimpleNotation simple();
+ }
+
+ public final class NumberFormatter {
+ method public static android.icu.number.UnlocalizedNumberFormatter with();
+ method public static android.icu.number.LocalizedNumberFormatter withLocale(java.util.Locale);
+ method public static android.icu.number.LocalizedNumberFormatter withLocale(android.icu.util.ULocale);
+ }
+
+ public enum NumberFormatter.DecimalSeparatorDisplay {
+ enum_constant public static final android.icu.number.NumberFormatter.DecimalSeparatorDisplay ALWAYS;
+ enum_constant public static final android.icu.number.NumberFormatter.DecimalSeparatorDisplay AUTO;
+ }
+
+ public enum NumberFormatter.GroupingStrategy {
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy AUTO;
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy MIN2;
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy OFF;
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy ON_ALIGNED;
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy THOUSANDS;
+ }
+
+ public enum NumberFormatter.SignDisplay {
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_ALWAYS;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_EXCEPT_ZERO;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ALWAYS;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay AUTO;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay EXCEPT_ZERO;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay NEVER;
+ }
+
+ public enum NumberFormatter.UnitWidth {
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth FULL_NAME;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth HIDDEN;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth ISO_CODE;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth NARROW;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth SHORT;
+ }
+
+ public abstract class NumberFormatterSettings<T extends android.icu.number.NumberFormatterSettings<?>> {
+ method public T decimal(android.icu.number.NumberFormatter.DecimalSeparatorDisplay);
+ method public T grouping(android.icu.number.NumberFormatter.GroupingStrategy);
+ method public T integerWidth(android.icu.number.IntegerWidth);
+ method public T notation(android.icu.number.Notation);
+ method public T perUnit(android.icu.util.MeasureUnit);
+ method public T precision(android.icu.number.Precision);
+ method public T roundingMode(java.math.RoundingMode);
+ method public T scale(android.icu.number.Scale);
+ method public T sign(android.icu.number.NumberFormatter.SignDisplay);
+ method public T symbols(android.icu.text.DecimalFormatSymbols);
+ method public T symbols(android.icu.text.NumberingSystem);
+ method public T unit(android.icu.util.MeasureUnit);
+ method public T unitWidth(android.icu.number.NumberFormatter.UnitWidth);
+ }
+
+ public abstract class NumberRangeFormatter {
+ method public static android.icu.number.UnlocalizedNumberRangeFormatter with();
+ method public static android.icu.number.LocalizedNumberRangeFormatter withLocale(java.util.Locale);
+ method public static android.icu.number.LocalizedNumberRangeFormatter withLocale(android.icu.util.ULocale);
+ }
+
+ public enum NumberRangeFormatter.RangeCollapse {
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse ALL;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse AUTO;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse NONE;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse UNIT;
+ }
+
+ public enum NumberRangeFormatter.RangeIdentityFallback {
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback APPROXIMATELY;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback APPROXIMATELY_OR_SINGLE_VALUE;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback RANGE;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback SINGLE_VALUE;
+ }
+
+ public enum NumberRangeFormatter.RangeIdentityResult {
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult EQUAL_AFTER_ROUNDING;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult EQUAL_BEFORE_ROUNDING;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult NOT_EQUAL;
+ }
+
+ public abstract class NumberRangeFormatterSettings<T extends android.icu.number.NumberRangeFormatterSettings<?>> {
+ method public T collapse(android.icu.number.NumberRangeFormatter.RangeCollapse);
+ method public T identityFallback(android.icu.number.NumberRangeFormatter.RangeIdentityFallback);
+ method public T numberFormatterBoth(android.icu.number.UnlocalizedNumberFormatter);
+ method public T numberFormatterFirst(android.icu.number.UnlocalizedNumberFormatter);
+ method public T numberFormatterSecond(android.icu.number.UnlocalizedNumberFormatter);
+ }
+
+ public abstract class Precision implements java.lang.Cloneable {
+ method public Object clone();
+ method public static android.icu.number.CurrencyPrecision currency(android.icu.util.Currency.CurrencyUsage);
+ method public static android.icu.number.FractionPrecision fixedFraction(int);
+ method public static android.icu.number.Precision fixedSignificantDigits(int);
+ method public static android.icu.number.Precision increment(java.math.BigDecimal);
+ method public static android.icu.number.FractionPrecision integer();
+ method public static android.icu.number.FractionPrecision maxFraction(int);
+ method public static android.icu.number.Precision maxSignificantDigits(int);
+ method public static android.icu.number.FractionPrecision minFraction(int);
+ method public static android.icu.number.FractionPrecision minMaxFraction(int, int);
+ method public static android.icu.number.Precision minMaxSignificantDigits(int, int);
+ method public static android.icu.number.Precision minSignificantDigits(int);
+ method public static android.icu.number.Precision unlimited();
+ }
+
+ public class Scale {
+ method public static android.icu.number.Scale byBigDecimal(java.math.BigDecimal);
+ method public static android.icu.number.Scale byDouble(double);
+ method public static android.icu.number.Scale byDoubleAndPowerOfTen(double, int);
+ method public static android.icu.number.Scale none();
+ method public static android.icu.number.Scale powerOfTen(int);
+ }
+
+ public class ScientificNotation extends android.icu.number.Notation implements java.lang.Cloneable {
+ method public Object clone();
+ method public android.icu.number.ScientificNotation withExponentSignDisplay(android.icu.number.NumberFormatter.SignDisplay);
+ method public android.icu.number.ScientificNotation withMinExponentDigits(int);
+ }
+
+ public class SimpleNotation extends android.icu.number.Notation {
+ }
+
+ public class UnlocalizedNumberFormatter extends android.icu.number.NumberFormatterSettings<android.icu.number.UnlocalizedNumberFormatter> {
+ method public android.icu.number.LocalizedNumberFormatter locale(java.util.Locale);
+ method public android.icu.number.LocalizedNumberFormatter locale(android.icu.util.ULocale);
+ }
+
+ public class UnlocalizedNumberRangeFormatter extends android.icu.number.NumberRangeFormatterSettings<android.icu.number.UnlocalizedNumberRangeFormatter> {
+ method public android.icu.number.LocalizedNumberRangeFormatter locale(java.util.Locale);
+ method public android.icu.number.LocalizedNumberRangeFormatter locale(android.icu.util.ULocale);
+ }
+
+}
+
package android.icu.text {
public final class AlphabeticIndex<V> implements java.lang.Iterable<android.icu.text.AlphabeticIndex.Bucket<V>> {
@@ -22108,6 +22325,7 @@
field public static final android.icu.util.MeasureUnit ARC_MINUTE;
field public static final android.icu.util.MeasureUnit ARC_SECOND;
field public static final android.icu.util.MeasureUnit ASTRONOMICAL_UNIT;
+ field public static final android.icu.util.MeasureUnit ATMOSPHERE;
field public static final android.icu.util.MeasureUnit BIT;
field public static final android.icu.util.MeasureUnit BUSHEL;
field public static final android.icu.util.MeasureUnit BYTE;
@@ -22209,6 +22427,9 @@
field public static final android.icu.util.MeasureUnit OUNCE_TROY;
field public static final android.icu.util.MeasureUnit PARSEC;
field public static final android.icu.util.MeasureUnit PART_PER_MILLION;
+ field public static final android.icu.util.MeasureUnit PERCENT;
+ field public static final android.icu.util.MeasureUnit PERMILLE;
+ field public static final android.icu.util.MeasureUnit PETABYTE;
field public static final android.icu.util.MeasureUnit PICOMETER;
field public static final android.icu.util.MeasureUnit PINT;
field public static final android.icu.util.MeasureUnit PINT_METRIC;
@@ -22634,6 +22855,7 @@
method public void onConfigureWindow(android.view.Window, boolean, boolean);
method public android.view.View onCreateCandidatesView();
method public android.view.View onCreateExtractTextView();
+ method @Nullable public android.view.inputmethod.InlineSuggestionsRequest onCreateInlineSuggestionsRequest();
method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
method public android.view.View onCreateInputView();
@@ -22650,6 +22872,7 @@
method public void onFinishInput();
method public void onFinishInputView(boolean);
method public void onInitializeInterface();
+ method public boolean onInlineSuggestionsResponse(@NonNull android.view.inputmethod.InlineSuggestionsResponse);
method public boolean onKeyDown(int, android.view.KeyEvent);
method public boolean onKeyLongPress(int, android.view.KeyEvent);
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -23215,10 +23438,10 @@
}
public interface LocationListener {
- method public void onLocationChanged(android.location.Location);
- method public void onProviderDisabled(String);
- method public void onProviderEnabled(String);
- method @Deprecated public void onStatusChanged(String, int, android.os.Bundle);
+ method public void onLocationChanged(@NonNull android.location.Location);
+ method public default void onProviderDisabled(@NonNull String);
+ method public default void onProviderEnabled(@NonNull String);
+ method @Deprecated public default void onStatusChanged(String, int, android.os.Bundle);
}
public class LocationManager {
@@ -23521,6 +23744,7 @@
field public static final int ENCODING_IEC61937 = 13; // 0xd
field public static final int ENCODING_INVALID = 0; // 0x0
field public static final int ENCODING_MP3 = 9; // 0x9
+ field public static final int ENCODING_OPUS = 20; // 0x14
field public static final int ENCODING_PCM_16BIT = 2; // 0x2
field public static final int ENCODING_PCM_8BIT = 3; // 0x3
field public static final int ENCODING_PCM_FLOAT = 4; // 0x4
@@ -25367,6 +25591,8 @@
field public static final String KEY_COMPLEXITY = "complexity";
field public static final String KEY_CREATE_INPUT_SURFACE_SUSPENDED = "create-input-buffers-suspended";
field public static final String KEY_DURATION = "durationUs";
+ field public static final String KEY_ENCODER_DELAY = "encoder-delay";
+ field public static final String KEY_ENCODER_PADDING = "encoder-padding";
field public static final String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
field public static final String KEY_FRAME_RATE = "frame-rate";
field public static final String KEY_GRID_COLUMNS = "grid-cols";
@@ -25395,6 +25621,8 @@
field public static final String KEY_OPERATING_RATE = "operating-rate";
field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth";
field public static final String KEY_PCM_ENCODING = "pcm-encoding";
+ field public static final String KEY_PIXEL_ASPECT_RATIO_HEIGHT = "sar-height";
+ field public static final String KEY_PIXEL_ASPECT_RATIO_WIDTH = "sar-width";
field public static final String KEY_PREPEND_HEADER_TO_SYNC_FRAMES = "prepend-sps-pps-to-idr-frames";
field public static final String KEY_PRIORITY = "priority";
field public static final String KEY_PROFILE = "profile";
@@ -25637,14 +25865,14 @@
}
public static interface MediaParser.OutputConsumer {
- method public void onFormat(int, @NonNull android.media.MediaFormat);
method public void onSampleCompleted(int, long, int, int, int, @Nullable android.media.MediaCodec.CryptoInfo);
method public void onSampleData(int, @NonNull android.media.MediaParser.InputReader) throws java.io.IOException, java.lang.InterruptedException;
method public void onSeekMap(@NonNull android.media.MediaParser.SeekMap);
+ method public void onTrackData(int, @NonNull android.media.MediaParser.TrackData);
method public void onTracksFound(int);
}
- public static interface MediaParser.SeekMap {
+ public static final class MediaParser.SeekMap {
method public long getDurationUs();
method @NonNull public android.util.Pair<android.media.MediaParser.SeekPoint,android.media.MediaParser.SeekPoint> getSeekPoints(long);
method public boolean isSeekable();
@@ -25661,6 +25889,11 @@
method public void seekToPosition(long);
}
+ public static final class MediaParser.TrackData {
+ field @Nullable public final android.media.DrmInitData drmInitData;
+ field @NonNull public final android.media.MediaFormat mediaFormat;
+ }
+
public static final class MediaParser.UnrecognizedInputFormatException extends java.io.IOException {
}
@@ -27609,6 +27842,7 @@
method @Nullable public android.app.PendingIntent getSessionActivity();
method @NonNull public android.os.Bundle getSessionInfo();
method @NonNull public android.media.session.MediaSession.Token getSessionToken();
+ method @NonNull public String getTag();
method @NonNull public android.media.session.MediaController.TransportControls getTransportControls();
method public void registerCallback(@NonNull android.media.session.MediaController.Callback);
method public void registerCallback(@NonNull android.media.session.MediaController.Callback, @Nullable android.os.Handler);
@@ -29209,6 +29443,7 @@
public class NetworkRequest implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
method public boolean hasCapability(int);
method public boolean hasTransport(int);
method public void writeToParcel(android.os.Parcel, int);
@@ -30010,6 +30245,7 @@
@Deprecated public static class WifiConfiguration.GroupCipher {
field @Deprecated public static final int CCMP = 3; // 0x3
field @Deprecated public static final int GCMP_256 = 5; // 0x5
+ field @Deprecated public static final int SMS4 = 6; // 0x6
field @Deprecated public static final int TKIP = 2; // 0x2
field @Deprecated public static final int WEP104 = 1; // 0x1
field @Deprecated public static final int WEP40 = 0; // 0x0
@@ -30039,6 +30275,7 @@
field @Deprecated public static final int CCMP = 2; // 0x2
field @Deprecated public static final int GCMP_256 = 3; // 0x3
field @Deprecated public static final int NONE = 0; // 0x0
+ field @Deprecated public static final int SMS4 = 4; // 0x4
field @Deprecated public static final int TKIP = 1; // 0x1
field @Deprecated public static final String[] strings;
field @Deprecated public static final String varName = "pairwise";
@@ -30046,6 +30283,7 @@
@Deprecated public static class WifiConfiguration.Protocol {
field @Deprecated public static final int RSN = 1; // 0x1
+ field @Deprecated public static final int WAPI = 3; // 0x3
field @Deprecated public static final int WPA = 0; // 0x0
field @Deprecated public static final String[] strings;
field @Deprecated public static final String varName = "proto";
@@ -30092,6 +30330,12 @@
method @Deprecated public void setSubjectMatch(String);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiEnterpriseConfig> CREATOR;
+ field public static final String EXTRA_WAPI_AS_CERTIFICATE_DATA = "android.net.wifi.extra.WAPI_AS_CERTIFICATE_DATA";
+ field public static final String EXTRA_WAPI_AS_CERTIFICATE_NAME = "android.net.wifi.extra.WAPI_AS_CERTIFICATE_NAME";
+ field public static final String EXTRA_WAPI_USER_CERTIFICATE_DATA = "android.net.wifi.extra.WAPI_USER_CERTIFICATE_DATA";
+ field public static final String EXTRA_WAPI_USER_CERTIFICATE_NAME = "android.net.wifi.extra.WAPI_USER_CERTIFICATE_NAME";
+ field public static final String WAPI_AS_CERTIFICATE = "WAPIAS_";
+ field public static final String WAPI_USER_CERTIFICATE = "WAPIUSR_";
}
public static final class WifiEnterpriseConfig.Eap {
@@ -30104,6 +30348,7 @@
field public static final int TLS = 1; // 0x1
field public static final int TTLS = 2; // 0x2
field public static final int UNAUTH_TLS = 7; // 0x7
+ field public static final int WAPI_CERT = 8; // 0x8
}
public static final class WifiEnterpriseConfig.Phase2 {
@@ -30175,6 +30420,7 @@
method public boolean isPreferredNetworkOffloadSupported();
method @Deprecated public boolean isScanAlwaysAvailable();
method public boolean isTdlsSupported();
+ method public boolean isWapiSupported();
method public boolean isWifiEnabled();
method public boolean isWpa3SaeSupported();
method public boolean isWpa3SuiteBSupported();
@@ -30331,6 +30577,8 @@
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);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiPassphrase(@NonNull String);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa2EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa2Passphrase(@NonNull String);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa3EnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
@@ -30368,8 +30616,11 @@
method public int getMaxMatchFilterLength();
method public int getMaxServiceNameLength();
method public int getMaxServiceSpecificInfoLength();
+ method public int getSupportedCipherSuites();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.aware.Characteristics> CREATOR;
+ field public static final int WIFI_AWARE_CIPHER_SUITE_NCS_SK_128 = 1; // 0x1
+ field public static final int WIFI_AWARE_CIPHER_SUITE_NCS_SK_256 = 2; // 0x2
}
public class DiscoverySession implements java.lang.AutoCloseable {
@@ -30617,6 +30868,10 @@
ctor public WifiP2pConfig();
ctor public WifiP2pConfig(android.net.wifi.p2p.WifiP2pConfig);
method public int describeContents();
+ method public int getGroupOwnerBand();
+ method public int getNetworkId();
+ method @Nullable public String getNetworkName();
+ method @Nullable public String getPassphrase();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pConfig> CREATOR;
field public static final int GROUP_OWNER_BAND_2GHZ = 1; // 0x1
@@ -30690,6 +30945,8 @@
method public boolean isGroupOwner();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pGroup> CREATOR;
+ field public static final int PERSISTENT_NET_ID = -2; // 0xfffffffe
+ field public static final int TEMPORARY_NET_ID = -1; // 0xffffffff
}
public class WifiP2pInfo implements android.os.Parcelable {
@@ -31075,6 +31332,7 @@
method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
+ field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
field public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT) public static final String ACTION_TRANSACTION_DETECTED = "android.nfc.action.TRANSACTION_DETECTED";
@@ -31083,6 +31341,7 @@
field public static final String EXTRA_DATA = "android.nfc.extra.DATA";
field public static final String EXTRA_ID = "android.nfc.extra.ID";
field public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
+ field public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON = "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
@@ -31093,6 +31352,9 @@
field public static final int FLAG_READER_NFC_V = 8; // 0x8
field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100
field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80
+ field public static final int PREFERRED_PAYMENT_CHANGED = 2; // 0x2
+ field public static final int PREFERRED_PAYMENT_LOADED = 1; // 0x1
+ field public static final int PREFERRED_PAYMENT_UPDATED = 3; // 0x3
field public static final int STATE_OFF = 1; // 0x1
field public static final int STATE_ON = 3; // 0x3
field public static final int STATE_TURNING_OFF = 4; // 0x4
@@ -31148,8 +31410,11 @@
public final class CardEmulation {
method public boolean categoryAllowsForegroundPreference(String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getDescriptionForPreferredPaymentService();
method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
method public int getSelectionModeForCategory(String);
method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
@@ -35000,6 +35265,7 @@
method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException;
+ method public static int getSuggestedMaxIpcSizeBytes();
method public boolean isBinderAlive();
method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException;
method public boolean pingBinder();
@@ -35196,6 +35462,7 @@
method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+ method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
method @Nullable public android.os.PersistableBundle readPersistableBundle();
method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
@@ -35243,6 +35510,7 @@
method public void writeNoException();
method public void writeParcelable(@Nullable android.os.Parcelable, int);
method public <T extends android.os.Parcelable> void writeParcelableArray(@Nullable T[], int);
+ method public void writeParcelableCreator(@NonNull android.os.Parcelable);
method public <T extends android.os.Parcelable> void writeParcelableList(@Nullable java.util.List<T>, int);
method public void writePersistableBundle(@Nullable android.os.PersistableBundle);
method public void writeSerializable(@Nullable java.io.Serializable);
@@ -35384,11 +35652,12 @@
method public boolean isIgnoringBatteryOptimizations(String);
method public boolean isInteractive();
method public boolean isPowerSaveMode();
+ method public boolean isRebootingUserspaceSupported();
method @Deprecated public boolean isScreenOn();
method public boolean isSustainedPerformanceModeSupported();
method public boolean isWakeLockLevelSupported(int);
method public android.os.PowerManager.WakeLock newWakeLock(int, String);
- method public void reboot(String);
+ method public void reboot(@Nullable String);
method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
@@ -35719,7 +35988,7 @@
method @Deprecated public void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
method public static boolean supportsMultipleUsers();
field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
- field public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
+ field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
field public static final String DISALLOW_ADD_USER = "no_add_user";
field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
@@ -35758,7 +36027,7 @@
field public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
field public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
field public static final String DISALLOW_PRINTING = "no_printing";
- field public static final String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile";
+ field @Deprecated public static final String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile";
field public static final String DISALLOW_REMOVE_USER = "no_remove_user";
field public static final String DISALLOW_SAFE_BOOT = "no_safe_boot";
field public static final String DISALLOW_SET_USER_ICON = "no_set_user_icon";
@@ -35993,7 +36262,8 @@
method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException;
method public String getMountedObbPath(String);
method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume();
- method @Nullable public android.os.storage.StorageVolume getStorageVolume(java.io.File);
+ method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes();
+ method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File);
method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri);
method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException;
@@ -36019,6 +36289,8 @@
method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
method public int describeContents();
method public String getDescription(android.content.Context);
+ method @Nullable public java.io.File getDirectory();
+ method @Nullable public String getMediaStoreVolumeName();
method public String getState();
method @Nullable public String getUuid();
method public boolean isEmulated();
@@ -38342,6 +38614,8 @@
public static final class ContactsContract.RawContacts implements android.provider.BaseColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.SyncColumns {
method public static android.net.Uri getContactLookupUri(android.content.ContentResolver, android.net.Uri);
+ method @Nullable public static String getLocalAccountName(@NonNull android.content.Context);
+ method @Nullable public static String getLocalAccountType(@NonNull android.content.Context);
method public static android.content.EntityIterator newEntityIterator(android.database.Cursor);
field public static final int AGGREGATION_MODE_DEFAULT = 0; // 0x0
field public static final int AGGREGATION_MODE_DISABLED = 3; // 0x3
@@ -38368,11 +38642,11 @@
protected static interface ContactsContract.RawContactsColumns {
field public static final String ACCOUNT_TYPE_AND_DATA_SET = "account_type_and_data_set";
field public static final String AGGREGATION_MODE = "aggregation_mode";
- field public static final String BACKUP_ID = "backup_id";
+ field @Deprecated public static final String BACKUP_ID = "backup_id";
field public static final String CONTACT_ID = "contact_id";
field public static final String DATA_SET = "data_set";
field public static final String DELETED = "deleted";
- field public static final String METADATA_DIRTY = "metadata_dirty";
+ field @Deprecated public static final String METADATA_DIRTY = "metadata_dirty";
field public static final String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
field public static final String RAW_CONTACT_IS_USER_PROFILE = "raw_contact_is_user_profile";
}
@@ -38679,19 +38953,21 @@
public final class MediaStore {
ctor public MediaStore();
+ method @NonNull public static android.app.PendingIntent createDeleteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
+ method @NonNull public static android.app.PendingIntent createFavoriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+ method @NonNull public static android.app.PendingIntent createTrashRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+ 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 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);
method public static boolean getRequireOriginal(@NonNull android.net.Uri);
method @NonNull public static String getVersion(@NonNull android.content.Context);
method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
method @Deprecated @NonNull public static android.net.Uri setIncludePending(@NonNull android.net.Uri);
method @NonNull public static android.net.Uri setRequireOriginal(@NonNull android.net.Uri);
- method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
- method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
- method public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
field public static final String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
field public static final String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
field public static final String ACTION_REVIEW = "android.provider.action.REVIEW";
@@ -38951,6 +39227,7 @@
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method @Deprecated public static android.net.Uri getContentUri(String);
+ method @Deprecated @NonNull public static android.util.Size getKindSize(int);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
@@ -38994,6 +39271,8 @@
field public static final String GENRE = "genre";
field public static final String HEIGHT = "height";
field public static final String INSTANCE_ID = "instance_id";
+ field public static final String IS_DOWNLOAD = "is_download";
+ field public static final String IS_DRM = "is_drm";
field public static final String IS_FAVORITE = "is_favorite";
field public static final String IS_PENDING = "is_pending";
field public static final String IS_TRASHED = "is_trashed";
@@ -39033,6 +39312,7 @@
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
method @Deprecated public static android.net.Uri getContentUri(String);
+ method @Deprecated @NonNull public static android.util.Size getKindSize(int);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
field @Deprecated public static final String DATA = "_data";
@@ -39096,6 +39376,7 @@
field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
+ field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
field public static final String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
@@ -39107,7 +39388,7 @@
field public static final String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
field public static final String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
field public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
- field public static final String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
+ field @Deprecated public static final String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
field public static final String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
field public static final String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
field public static final String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -39170,6 +39451,7 @@
field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final String EXTRA_AUTHORITIES = "authorities";
field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final String EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED = "android.provider.extra.BIOMETRIC_MINIMUM_STRENGTH_REQUIRED";
field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
@@ -41108,6 +41390,11 @@
method public android.security.ConfirmationPrompt.Builder setPromptText(CharSequence);
}
+ public final class FileIntegrityManager {
+ method public boolean isApkVeritySupported();
+ method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
+ }
+
public final class KeyChain {
ctor public KeyChain();
method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable String, int, @Nullable String);
@@ -41539,7 +41826,8 @@
method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
method public int getFlags();
method public int getId();
- method public void writeToParcel(android.os.Parcel, int);
+ method @Nullable public android.view.inputmethod.InlineSuggestionsRequest getInlineSuggestionsRequest();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillRequest> CREATOR;
field public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 2; // 0x2
field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1
@@ -41556,6 +41844,7 @@
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);
@@ -43861,6 +44150,7 @@
field public static final String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
field public static final String EVENT_CALL_REMOTELY_HELD = "android.telecom.event.CALL_REMOTELY_HELD";
field public static final String EVENT_CALL_REMOTELY_UNHELD = "android.telecom.event.CALL_REMOTELY_UNHELD";
+ field public static final String EVENT_CALL_SWITCH_FAILED = "android.telecom.event.CALL_SWITCH_FAILED";
field public static final String EVENT_MERGE_COMPLETE = "android.telecom.event.MERGE_COMPLETE";
field public static final String EVENT_MERGE_START = "android.telecom.event.MERGE_START";
field public static final String EVENT_ON_HOLD_TONE_END = "android.telecom.event.ON_HOLD_TONE_END";
@@ -44318,19 +44608,27 @@
field @Deprecated public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
field public static final String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED";
+ field public static final String ACTION_POST_CALL = "android.telecom.action.POST_CALL";
field public static final String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
field public static final String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
field public static final String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS";
field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
+ field public static final int DURATION_LONG = 3; // 0x3
+ field public static final int DURATION_MEDIUM = 2; // 0x2
+ field public static final int DURATION_SHORT = 1; // 0x1
+ field public static final int DURATION_VERY_SHORT = 0; // 0x0
field public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER";
field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
+ field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telecom.extra.CALL_NETWORK_TYPE";
field public static final String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT";
field public static final String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME";
field public static final String EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME = "android.telecom.extra.DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME";
+ field public static final String EXTRA_DISCONNECT_CAUSE = "android.telecom.extra.DISCONNECT_CAUSE";
+ field public static final String EXTRA_HANDLE = "android.telecom.extra.HANDLE";
field public static final String EXTRA_INCOMING_CALL_ADDRESS = "android.telecom.extra.INCOMING_CALL_ADDRESS";
field public static final String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS";
field public static final String EXTRA_INCOMING_VIDEO_STATE = "android.telecom.extra.INCOMING_VIDEO_STATE";
@@ -44570,6 +44868,7 @@
method public void notifyConfigChangedForSubId(int);
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 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 KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
@@ -44580,6 +44879,7 @@
field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
field public static final String KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL = "allow_emergency_video_calls_bool";
+ field public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL = "allow_holding_video_call";
field public static final String KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL = "allow_hold_call_during_emergency_bool";
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";
@@ -44629,11 +44929,16 @@
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";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
- field public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ field public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING = "config_ims_mmtel_package_override_string";
+ field @Deprecated public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+ 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_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";
+ field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool";
+ field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool";
field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
field public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING = "default_vm_number_roaming_and_ims_unregistered_string";
@@ -44752,6 +45057,11 @@
field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
+ public static final class CarrierConfigManager.Gps {
+ field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool";
+ field public static final String KEY_PREFIX = "gps.";
+ }
+
public static final class CarrierConfigManager.Ims {
field public static final String KEY_PREFIX = "ims.";
}
@@ -45185,6 +45495,7 @@
ctor public PhoneStateListener();
ctor public PhoneStateListener(@NonNull java.util.concurrent.Executor);
method public void onActiveDataSubscriptionIdChanged(int);
+ method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
method public void onCallForwardingIndicatorChanged(boolean);
method public void onCallStateChanged(int, String);
method public void onCellInfoChanged(java.util.List<android.telephony.CellInfo>);
@@ -45192,12 +45503,15 @@
method public void onDataActivity(int);
method public void onDataConnectionStateChanged(int);
method public void onDataConnectionStateChanged(int, int);
+ 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 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.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
field public static final int LISTEN_CELL_INFO = 1024; // 0x400
@@ -45205,14 +45519,26 @@
field public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80
field public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40
field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
+ field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
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 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
field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
}
+ public final class PreciseDataConnectionState implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getLastCauseCode();
+ method @Nullable public android.net.LinkProperties getLinkProperties();
+ method public int getNetworkType();
+ method public int getState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+ }
+
public final class RadioAccessSpecifier implements android.os.Parcelable {
ctor public RadioAccessSpecifier(int, int[], int[]);
method public int describeContents();
@@ -45593,6 +45919,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
method public int getActiveModemCount();
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
+ method @NonNull public static int[] getAllNetworkTypes();
method public int getCallState();
method public int getCardIdForDefaultEuicc();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig();
@@ -45724,6 +46051,7 @@
field public static final int DATA_CONNECTED = 2; // 0x2
field public static final int DATA_CONNECTING = 1; // 0x1
field public static final int DATA_DISCONNECTED = 0; // 0x0
+ field public static final int DATA_DISCONNECTING = 4; // 0x4
field public static final int DATA_SUSPENDED = 3; // 0x3
field public static final int DATA_UNKNOWN = -1; // 0xffffffff
field public static final String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
@@ -45965,6 +46293,7 @@
field public static final int TYPE_MCX = 1024; // 0x400
field public static final int TYPE_MMS = 2; // 0x2
field public static final int TYPE_SUPL = 4; // 0x4
+ field public static final int TYPE_XCAP = 2048; // 0x800
}
public static class ApnSetting.Builder {
@@ -46534,12 +46863,12 @@
package android.text {
- public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
- method public char charAt(int);
- method public void getChars(int, int, char[], int);
- method public int length();
- method public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);
- method public CharSequence subSequence(int, int);
+ @Deprecated public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
+ method @Deprecated public char charAt(int);
+ method @Deprecated public void getChars(int, int, char[], int);
+ method @Deprecated public int length();
+ method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);
+ method @Deprecated public CharSequence subSequence(int, int);
}
@Deprecated public class AndroidCharacter {
@@ -47274,7 +47603,7 @@
field public static final long WEEK_IN_MILLIS = 604800000L; // 0x240c8400L
field public static final String YEAR_FORMAT = "%Y";
field public static final String YEAR_FORMAT_TWO_DIGITS = "%g";
- field public static final long YEAR_IN_MILLIS = 31449600000L; // 0x7528ad000L
+ field @Deprecated public static final long YEAR_IN_MILLIS = 31449600000L; // 0x7528ad000L
field @Deprecated public static final int[] sameMonthTable;
field @Deprecated public static final int[] sameYearTable;
}
@@ -48713,6 +49042,13 @@
ctor public Base64OutputStream(java.io.OutputStream, int);
}
+ public final class CloseGuard {
+ ctor public CloseGuard();
+ method public void close();
+ method public void open(@NonNull String);
+ method public void warnIfOpen();
+ }
+
@Deprecated public final class Config {
field @Deprecated public static final boolean DEBUG = false;
field @Deprecated public static final boolean LOGD = true;
@@ -49366,6 +49702,71 @@
}
+package android.util.proto {
+
+ public final class ProtoOutputStream {
+ ctor public ProtoOutputStream();
+ ctor public ProtoOutputStream(int);
+ ctor public ProtoOutputStream(@NonNull java.io.OutputStream);
+ method public static int checkFieldId(long, long);
+ method public void dump(@NonNull String);
+ method public void end(long);
+ method public void flush();
+ method @NonNull public byte[] getBytes();
+ method @Nullable public static String getFieldCountString(long);
+ method @NonNull public static String getFieldIdString(long);
+ method @Nullable public static String getFieldTypeString(long);
+ method public int getRawSize();
+ method @Nullable public static String getWireTypeString(int);
+ method public static long makeFieldId(int, long);
+ method public static long makeToken(int, boolean, int, int, int);
+ method public long start(long);
+ method @NonNull public static String token2String(long);
+ method public void write(long, double);
+ method public void write(long, float);
+ method public void write(long, int);
+ method public void write(long, long);
+ method public void write(long, boolean);
+ method public void write(long, @Nullable String);
+ method public void write(long, @Nullable byte[]);
+ method public void writeTag(int, int);
+ field public static final long FIELD_COUNT_MASK = 16492674416640L; // 0xf0000000000L
+ field public static final long FIELD_COUNT_PACKED = 5497558138880L; // 0x50000000000L
+ field public static final long FIELD_COUNT_REPEATED = 2199023255552L; // 0x20000000000L
+ field public static final int FIELD_COUNT_SHIFT = 40; // 0x28
+ field public static final long FIELD_COUNT_SINGLE = 1099511627776L; // 0x10000000000L
+ field public static final long FIELD_COUNT_UNKNOWN = 0L; // 0x0L
+ field public static final int FIELD_ID_SHIFT = 3; // 0x3
+ field public static final long FIELD_TYPE_BOOL = 34359738368L; // 0x800000000L
+ field public static final long FIELD_TYPE_BYTES = 51539607552L; // 0xc00000000L
+ field public static final long FIELD_TYPE_DOUBLE = 4294967296L; // 0x100000000L
+ field public static final long FIELD_TYPE_ENUM = 60129542144L; // 0xe00000000L
+ field public static final long FIELD_TYPE_FIXED32 = 30064771072L; // 0x700000000L
+ field public static final long FIELD_TYPE_FIXED64 = 25769803776L; // 0x600000000L
+ field public static final long FIELD_TYPE_FLOAT = 8589934592L; // 0x200000000L
+ field public static final long FIELD_TYPE_INT32 = 21474836480L; // 0x500000000L
+ field public static final long FIELD_TYPE_INT64 = 12884901888L; // 0x300000000L
+ field public static final long FIELD_TYPE_MASK = 1095216660480L; // 0xff00000000L
+ field public static final long FIELD_TYPE_MESSAGE = 47244640256L; // 0xb00000000L
+ field public static final long FIELD_TYPE_SFIXED32 = 64424509440L; // 0xf00000000L
+ field public static final long FIELD_TYPE_SFIXED64 = 68719476736L; // 0x1000000000L
+ field public static final int FIELD_TYPE_SHIFT = 32; // 0x20
+ field public static final long FIELD_TYPE_SINT32 = 73014444032L; // 0x1100000000L
+ field public static final long FIELD_TYPE_SINT64 = 77309411328L; // 0x1200000000L
+ field public static final long FIELD_TYPE_STRING = 38654705664L; // 0x900000000L
+ field public static final long FIELD_TYPE_UINT32 = 55834574848L; // 0xd00000000L
+ field public static final long FIELD_TYPE_UINT64 = 17179869184L; // 0x400000000L
+ field public static final int WIRE_TYPE_END_GROUP = 4; // 0x4
+ field public static final int WIRE_TYPE_FIXED32 = 5; // 0x5
+ field public static final int WIRE_TYPE_FIXED64 = 1; // 0x1
+ field public static final int WIRE_TYPE_LENGTH_DELIMITED = 2; // 0x2
+ field public static final int WIRE_TYPE_MASK = 7; // 0x7
+ field public static final int WIRE_TYPE_START_GROUP = 3; // 0x3
+ field public static final int WIRE_TYPE_VARINT = 0; // 0x0
+ }
+
+}
+
package android.view {
public abstract class AbsSavedState implements android.os.Parcelable {
@@ -51042,6 +51443,9 @@
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 @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 public void dispatchWindowSystemUiVisiblityChanged(int);
method public void dispatchWindowVisibilityChanged(int);
method @CallSuper public void draw(android.graphics.Canvas);
@@ -51222,6 +51626,7 @@
method @android.view.ViewDebug.ExportedProperty(category="layout") public final int getWidth();
method protected int getWindowAttachCount();
method public android.view.WindowId getWindowId();
+ method @Nullable public android.view.WindowInsetsController getWindowInsetsController();
method public int getWindowSystemUiVisibility();
method public android.os.IBinder getWindowToken();
method public int getWindowVisibility();
@@ -51288,6 +51693,7 @@
method public boolean isScrollContainer();
method public boolean isScrollbarFadingEnabled();
method @android.view.ViewDebug.ExportedProperty public boolean isSelected();
+ method public final boolean isShowingLayoutBounds();
method public boolean isShown();
method @android.view.ViewDebug.ExportedProperty public boolean isSoundEffectsEnabled();
method public final boolean isTemporarilyDetached();
@@ -51560,6 +51966,7 @@
method public void setVisibility(int);
method @Deprecated public void setWillNotCacheDrawing(boolean);
method public void setWillNotDraw(boolean);
+ method public void setWindowInsetsAnimationCallback(@Nullable android.view.WindowInsetsAnimationCallback);
method public void setX(float);
method public void setY(float);
method public void setZ(float);
@@ -52446,6 +52853,7 @@
method public android.transition.Transition getExitTransition();
method protected final int getFeatures();
method protected final int getForcedWindowFlags();
+ method @Nullable public android.view.WindowInsetsController getInsetsController();
method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
method protected final int getLocalFeatures();
method public android.media.session.MediaController getMediaController();
@@ -52661,7 +53069,9 @@
method @NonNull public android.view.WindowInsets consumeStableInsets();
method @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
method @Nullable public android.view.DisplayCutout getDisplayCutout();
+ method @NonNull public android.graphics.Insets getInsets(int);
method @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
+ method @NonNull public android.graphics.Insets getMaxInsets(int) throws java.lang.IllegalArgumentException;
method public int getStableInsetBottom();
method public int getStableInsetLeft();
method public int getStableInsetRight();
@@ -52680,6 +53090,7 @@
method @NonNull public android.view.WindowInsets inset(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int);
method public boolean isConsumed();
method public boolean isRound();
+ method public boolean isVisible(int);
method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(int, int, int, int);
method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(android.graphics.Rect);
}
@@ -52689,11 +53100,78 @@
ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
method @NonNull public android.view.WindowInsets build();
method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
+ method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
+ method @NonNull public android.view.WindowInsets.Builder setMaxInsets(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
method @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setTappableElementInsets(@NonNull android.graphics.Insets);
+ method @NonNull public android.view.WindowInsets.Builder setVisible(int, boolean);
+ }
+
+ public static final class WindowInsets.Type {
+ method public static int all();
+ method public static int captionBar();
+ method public static int ime();
+ method public static int mandatorySystemGestures();
+ method public static int navigationBars();
+ method public static int statusBars();
+ method public static int systemBars();
+ method public static int systemGestures();
+ method public static int tappableElement();
+ method public static int windowDecor();
+ }
+
+ public interface WindowInsetsAnimationCallback {
+ method public default void onFinished(@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);
+ }
+
+ public static final class WindowInsetsAnimationCallback.AnimationBounds {
+ ctor public WindowInsetsAnimationCallback.AnimationBounds(@NonNull android.graphics.Insets, @NonNull android.graphics.Insets);
+ method @NonNull public android.graphics.Insets getLowerBound();
+ method @NonNull public android.graphics.Insets getUpperBound();
+ method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds inset(@NonNull android.graphics.Insets);
+ }
+
+ public static final class WindowInsetsAnimationCallback.InsetsAnimation {
+ ctor public WindowInsetsAnimationCallback.InsetsAnimation(int, @Nullable android.view.animation.Interpolator, long);
+ method @FloatRange(from=0.0f, to=1.0f) public float getAlpha();
+ method public long getDurationMillis();
+ method @FloatRange(from=0.0f, to=1.0f) public float getFraction();
+ method public float getInterpolatedFraction();
+ method @Nullable public android.view.animation.Interpolator getInterpolator();
+ method public int getTypeMask();
+ method public void setDuration(long);
+ method public void setFraction(@FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public interface WindowInsetsAnimationControlListener {
+ method public void onCancelled();
+ method public void onReady(@NonNull android.view.WindowInsetsAnimationController, int);
+ }
+
+ public interface WindowInsetsAnimationController {
+ method public void finish(boolean);
+ method public float getCurrentAlpha();
+ method @FloatRange(from=0.0f, to=1.0f) public float getCurrentFraction();
+ method @NonNull public android.graphics.Insets getCurrentInsets();
+ method @NonNull public android.graphics.Insets getHiddenStateInsets();
+ method @NonNull public android.graphics.Insets getShownStateInsets();
+ method public int getTypes();
+ method public void setInsetsAndAlpha(@Nullable android.graphics.Insets, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+ }
+
+ public interface WindowInsetsController {
+ method public default void controlInputMethodAnimation(long, @NonNull android.view.WindowInsetsAnimationControlListener);
+ method public default void hideInputMethod();
+ method public void setSystemBarsAppearance(int);
+ method public void setSystemBarsBehavior(int);
+ method public default void showInputMethod();
+ field public static final int APPEARANCE_LIGHT_NAVIGATION_BARS = 16; // 0x10
+ field public static final int APPEARANCE_LIGHT_STATUS_BARS = 8; // 0x8
}
public interface WindowManager extends android.view.ViewManager {
@@ -54791,6 +55269,7 @@
method public int describeContents();
method @NonNull public android.os.Bundle getExtras();
method @NonNull public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks();
+ method @NonNull public String getText();
method public void writeToParcel(android.os.Parcel, int);
field public static final int APPLY_STRATEGY_IGNORE = 0; // 0x0
field public static final int APPLY_STRATEGY_REPLACE = 1; // 0x1
@@ -54817,6 +55296,7 @@
method @Nullable public android.os.LocaleList getDefaultLocales();
method @Nullable public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig();
method @NonNull public android.os.Bundle getExtras();
+ method @Nullable public java.time.ZonedDateTime getReferenceTime();
method @NonNull public CharSequence getText();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.Request> CREATOR;
@@ -54828,6 +55308,7 @@
method @NonNull public android.view.textclassifier.TextLinks.Request.Builder setDefaultLocales(@Nullable android.os.LocaleList);
method @NonNull public android.view.textclassifier.TextLinks.Request.Builder setEntityConfig(@Nullable android.view.textclassifier.TextClassifier.EntityConfig);
method public android.view.textclassifier.TextLinks.Request.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.view.textclassifier.TextLinks.Request.Builder setReferenceTime(@Nullable java.time.ZonedDateTime);
}
public static final class TextLinks.TextLink implements android.os.Parcelable {
diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt
index 4a37e67..508718e 100644
--- a/api/lint-baseline.txt
+++ b/api/lint-baseline.txt
@@ -527,6 +527,14 @@
MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth) parameter #1:
+MissingNullability: android.icu.util.MeasureUnit#ATMOSPHERE:
+ Missing nullability on field `ATMOSPHERE` in class `class android.icu.util.MeasureUnit`
+MissingNullability: android.icu.util.MeasureUnit#PERCENT:
+ Missing nullability on field `PERCENT` in class `class android.icu.util.MeasureUnit`
+MissingNullability: android.icu.util.MeasureUnit#PERMILLE:
+ Missing nullability on field `PERMILLE` in class `class android.icu.util.MeasureUnit`
+MissingNullability: android.icu.util.MeasureUnit#PETABYTE:
+ Missing nullability on field `PETABYTE` in class `class android.icu.util.MeasureUnit`
MissingNullability: android.icu.util.VersionInfo#UNICODE_12_0:
MissingNullability: android.icu.util.VersionInfo#UNICODE_12_1:
diff --git a/api/removed.txt b/api/removed.txt
index 1a2f434..fb6d576 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -435,14 +435,6 @@
field @Deprecated public static final String TIMESTAMP = "timestamp";
}
- public final class MediaStore {
- method @Deprecated @NonNull public static android.net.Uri createPending(@NonNull android.content.Context, @NonNull android.provider.MediaStore.PendingParams);
- method @Deprecated @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context);
- method @Deprecated public static boolean getIncludePending(@NonNull android.net.Uri);
- method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri);
- method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri);
- }
-
public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
field public static final String ALBUM = "album";
field public static final String ARTIST = "artist";
@@ -470,22 +462,6 @@
field @Deprecated public static final String GROUP_ID = "group_id";
}
- @Deprecated public static class MediaStore.PendingParams {
- ctor public MediaStore.PendingParams(@NonNull android.net.Uri, @NonNull String, @NonNull String);
- method public void setDownloadUri(@Nullable android.net.Uri);
- method public void setRefererUri(@Nullable android.net.Uri);
- method public void setRelativePath(@Nullable String);
- }
-
- @Deprecated public static class MediaStore.PendingSession implements java.lang.AutoCloseable {
- method public void abandon();
- method public void close();
- method public void notifyProgress(@IntRange(from=0, to=100) int);
- method @NonNull public android.os.ParcelFileDescriptor open() throws java.io.FileNotFoundException;
- method @NonNull public java.io.OutputStream openOutputStream() throws java.io.FileNotFoundException;
- method @NonNull public android.net.Uri publish();
- }
-
public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
field public static final String ALBUM = "album";
field public static final String ARTIST = "artist";
diff --git a/api/system-current.txt b/api/system-current.txt
old mode 100644
new mode 100755
index 40539d7..1cd0798
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -15,6 +15,7 @@
field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
+ field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
@@ -259,7 +260,6 @@
public static final class R.drawable {
field public static final int ic_info = 17301684; // 0x10800b4
- field public static final int stat_notify_wifi_in_range = 17301685; // 0x10800b5
}
public static final class R.raw {
@@ -280,9 +280,6 @@
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 notification_channel_network_alerts = 17039398; // 0x1040026
- field public static final int notification_channel_network_available = 17039399; // 0x1040027
- field public static final int notification_channel_network_status = 17039397; // 0x1040025
}
public static final class R.style {
@@ -365,6 +362,7 @@
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_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";
field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
@@ -473,15 +471,20 @@
public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
method public int describeContents();
- method public long getDuration();
+ method @Deprecated public long getDuration();
method @NonNull public java.util.Map<java.lang.String,android.app.AppOpsManager.OpFeatureEntry> getFeatures();
method public long getLastAccessBackgroundTime(int);
method public long getLastAccessForegroundTime(int);
method public long getLastAccessTime(int);
method public long getLastAccessTime(int, int, int);
method public long getLastBackgroundDuration(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastBackgroundProxyInfo(int);
+ method public long getLastDuration(int);
method public long getLastDuration(int, int, int);
method public long getLastForegroundDuration(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastForegroundProxyInfo(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastProxyInfo(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastProxyInfo(int, int, int);
method public long getLastRejectBackgroundTime(int);
method public long getLastRejectForegroundTime(int);
method public long getLastRejectTime(int);
@@ -489,34 +492,44 @@
method public int getMode();
method @NonNull public String getOpStr();
method @Deprecated @Nullable public String getProxyPackageName();
- method @Nullable public String getProxyPackageName(int, int);
+ method @Deprecated @Nullable public String getProxyPackageName(int, int);
method @Deprecated public int getProxyUid();
- method public int getProxyUid(int, int);
+ method @Deprecated public int getProxyUid(int, int);
method public boolean isRunning();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.OpEntry> CREATOR;
}
- public static final class AppOpsManager.OpFeatureEntry {
- method public long getDuration();
+ public static final class AppOpsManager.OpEventProxyInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getFeatureId();
+ method @Nullable public String getPackageName();
+ method @IntRange(from=0) public int getUid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.OpEventProxyInfo> CREATOR;
+ }
+
+ public static final class AppOpsManager.OpFeatureEntry implements android.os.Parcelable {
+ method public int describeContents();
method public long getLastAccessBackgroundTime(int);
method public long getLastAccessForegroundTime(int);
method public long getLastAccessTime(int);
method public long getLastAccessTime(int, int, int);
method public long getLastBackgroundDuration(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastBackgroundProxyInfo(int);
+ method public long getLastDuration(int);
method public long getLastDuration(int, int, int);
method public long getLastForegroundDuration(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastForegroundProxyInfo(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastProxyInfo(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastProxyInfo(int, int, int);
method public long getLastRejectBackgroundTime(int);
method public long getLastRejectForegroundTime(int);
method public long getLastRejectTime(int);
method public long getLastRejectTime(int, int, int);
- method @Nullable public String getProxyFeatureId();
- method @Nullable public String getProxyFeatureId(int, int);
- method @Nullable public String getProxyPackageName();
- method @Nullable public String getProxyPackageName(int, int);
- method public int getProxyUid();
- method public int getProxyUid(int, int);
method public boolean isRunning();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.OpFeatureEntry> CREATOR;
}
public static final class AppOpsManager.PackageOps implements android.os.Parcelable {
@@ -524,7 +537,7 @@
method @NonNull public java.util.List<android.app.AppOpsManager.OpEntry> getOps();
method @NonNull public String getPackageName();
method public int getUid();
- method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
}
@@ -546,10 +559,12 @@
method public final android.os.IBinder onBind(android.content.Intent);
method @Deprecated public void onGetInstantAppIntentFilter(@Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
method @Deprecated public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
- method public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method @Deprecated public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method public void onGetInstantAppIntentFilter(@NonNull android.content.pm.InstantAppRequestInfo, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
method @Deprecated public void onGetInstantAppResolveInfo(@Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
method @Deprecated public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
- method public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method @Deprecated public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method public void onGetInstantAppResolveInfo(@NonNull android.content.pm.InstantAppRequestInfo, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
}
public static final class InstantAppResolverService.InstantAppResolutionCallback {
@@ -810,6 +825,14 @@
}
+package android.app.appsearch {
+
+ public class AppSearchManagerFrameworkInitializer {
+ method public static void initialize();
+ }
+
+}
+
package android.app.assist {
public static class AssistStructure.ViewNode {
@@ -1306,6 +1329,7 @@
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
method public int getUsageSource();
+ method @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES) public void onCarrierPrivilegedAppsChanged();
method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @Nullable android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerAppUsageObserver(int, @NonNull String[], long, @NonNull java.util.concurrent.TimeUnit, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void registerUsageSessionObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @NonNull android.app.PendingIntent, @Nullable android.app.PendingIntent);
@@ -1350,6 +1374,14 @@
field public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; // 0xffffffff
}
+ public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile {
+ method public void finalize();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothAdapter {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice);
@@ -1424,15 +1456,24 @@
public final class BluetoothDevice implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess();
+ method public boolean cancelPairing();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getBatteryLevel();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getMessageAccessPermission();
method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getPhonebookAccessPermission();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isBondingInitiatedLocally();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMessageAccessPermission(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, @NonNull byte[]);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPin(@Nullable String);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSimAccessPermission(int);
field public static final int ACCESS_ALLOWED = 1; // 0x1
field public static final int ACCESS_REJECTED = 2; // 0x2
field public static final int ACCESS_UNKNOWN = 0; // 0x0
@@ -1467,7 +1508,9 @@
}
public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getActiveDevices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public long getHiSyncId(@Nullable android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
}
@@ -1486,6 +1529,11 @@
field public static final int REMOTE_PANU_ROLE = 2; // 0x2
}
+ public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
+ method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public interface BluetoothProfile {
field public static final int CONNECTION_POLICY_ALLOWED = 100; // 0x64
field public static final int CONNECTION_POLICY_FORBIDDEN = 0; // 0x0
@@ -1617,7 +1665,9 @@
field public static final String STATS_MANAGER = "stats";
field public static final String STATUS_BAR_SERVICE = "statusbar";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
+ field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
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 @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
@@ -1687,9 +1737,25 @@
field public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
field public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
+ field @Deprecated public static final String EXTRA_SIM_LOCKED_REASON = "reason";
+ 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 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";
+ field @Deprecated public static final String SIM_LOCKED_ON_PIN = "PIN";
+ field @Deprecated public static final String SIM_LOCKED_ON_PUK = "PUK";
+ field @Deprecated public static final String SIM_STATE_ABSENT = "ABSENT";
+ field @Deprecated public static final String SIM_STATE_CARD_IO_ERROR = "CARD_IO_ERROR";
+ field @Deprecated public static final String SIM_STATE_CARD_RESTRICTED = "CARD_RESTRICTED";
+ field @Deprecated public static final String SIM_STATE_IMSI = "IMSI";
+ field @Deprecated public static final String SIM_STATE_LOADED = "LOADED";
+ field @Deprecated public static final String SIM_STATE_LOCKED = "LOCKED";
+ field @Deprecated public static final String SIM_STATE_NOT_READY = "NOT_READY";
+ field @Deprecated public static final String SIM_STATE_PRESENT = "PRESENT";
+ field @Deprecated public static final String SIM_STATE_READY = "READY";
+ field @Deprecated public static final String SIM_STATE_UNKNOWN = "UNKNOWN";
}
public class IntentFilter implements android.os.Parcelable {
@@ -1731,7 +1797,7 @@
}
public abstract class AtomicFormula implements android.content.integrity.Formula {
- ctor public AtomicFormula(@android.content.integrity.AtomicFormula.Key int);
+ ctor public AtomicFormula(int);
method public int getKey();
field public static final int APP_CERTIFICATE = 1; // 0x1
field public static final int EQ = 0; // 0x0
@@ -1747,7 +1813,7 @@
}
public static final class AtomicFormula.BooleanAtomicFormula extends android.content.integrity.AtomicFormula implements android.os.Parcelable {
- ctor public AtomicFormula.BooleanAtomicFormula(@android.content.integrity.AtomicFormula.Key int, boolean);
+ ctor public AtomicFormula.BooleanAtomicFormula(int, boolean);
method public int describeContents();
method public int getTag();
method public boolean getValue();
@@ -1757,7 +1823,7 @@
}
public static final class AtomicFormula.IntAtomicFormula extends android.content.integrity.AtomicFormula implements android.os.Parcelable {
- ctor public AtomicFormula.IntAtomicFormula(@android.content.integrity.AtomicFormula.Key int, @android.content.integrity.AtomicFormula.Operator int, int);
+ ctor public AtomicFormula.IntAtomicFormula(int, int, int);
method public int describeContents();
method public int getOperator();
method public int getTag();
@@ -1767,14 +1833,8 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.integrity.AtomicFormula.IntAtomicFormula> CREATOR;
}
- @IntDef({android.content.integrity.AtomicFormula.PACKAGE_NAME, android.content.integrity.AtomicFormula.APP_CERTIFICATE, android.content.integrity.AtomicFormula.INSTALLER_NAME, android.content.integrity.AtomicFormula.INSTALLER_CERTIFICATE, android.content.integrity.AtomicFormula.VERSION_CODE, android.content.integrity.AtomicFormula.PRE_INSTALLED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface AtomicFormula.Key {
- }
-
- @IntDef({android.content.integrity.AtomicFormula.EQ, android.content.integrity.AtomicFormula.LT, android.content.integrity.AtomicFormula.LE, android.content.integrity.AtomicFormula.GT, android.content.integrity.AtomicFormula.GE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface AtomicFormula.Operator {
- }
-
public static final class AtomicFormula.StringAtomicFormula extends android.content.integrity.AtomicFormula implements android.os.Parcelable {
- ctor public AtomicFormula.StringAtomicFormula(@android.content.integrity.AtomicFormula.Key int, @NonNull String, boolean);
+ ctor public AtomicFormula.StringAtomicFormula(int, @NonNull String, boolean);
method public int describeContents();
method public boolean getIsHashedValue();
method public int getTag();
@@ -1785,9 +1845,9 @@
}
public final class CompoundFormula implements android.content.integrity.Formula android.os.Parcelable {
- ctor public CompoundFormula(@android.content.integrity.CompoundFormula.Connector int, @NonNull java.util.List<android.content.integrity.Formula>);
+ ctor public CompoundFormula(int, @NonNull java.util.List<android.content.integrity.Formula>);
method public int describeContents();
- method @android.content.integrity.CompoundFormula.Connector public int getConnector();
+ method public int getConnector();
method @NonNull public java.util.List<android.content.integrity.Formula> getFormulas();
method public int getTag();
method public boolean isSatisfied(@NonNull android.content.integrity.AppInstallMetadata);
@@ -1798,11 +1858,8 @@
field public static final int OR = 1; // 0x1
}
- @IntDef({android.content.integrity.CompoundFormula.AND, android.content.integrity.CompoundFormula.OR, android.content.integrity.CompoundFormula.NOT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CompoundFormula.Connector {
- }
-
public interface Formula {
- method @android.content.integrity.Formula.Tag public int getTag();
+ method public int getTag();
method public boolean isSatisfied(@NonNull android.content.integrity.AppInstallMetadata);
method @NonNull public static android.content.integrity.Formula readFromParcel(@NonNull android.os.Parcel);
method public static void writeToParcel(@NonNull android.content.integrity.Formula, @NonNull android.os.Parcel, int);
@@ -1812,13 +1869,10 @@
field public static final int STRING_ATOMIC_FORMULA_TAG = 1; // 0x1
}
- @IntDef({android.content.integrity.Formula.COMPOUND_FORMULA_TAG, android.content.integrity.Formula.STRING_ATOMIC_FORMULA_TAG, android.content.integrity.Formula.INT_ATOMIC_FORMULA_TAG, android.content.integrity.Formula.BOOLEAN_ATOMIC_FORMULA_TAG}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface Formula.Tag {
- }
-
public final class Rule implements android.os.Parcelable {
- ctor public Rule(@NonNull android.content.integrity.Formula, @android.content.integrity.Rule.Effect int);
+ ctor public Rule(@NonNull android.content.integrity.Formula, int);
method public int describeContents();
- method @android.content.integrity.Rule.Effect public int getEffect();
+ method public int getEffect();
method @NonNull public android.content.integrity.Formula getFormula();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.integrity.Rule> CREATOR;
@@ -1826,9 +1880,6 @@
field public static final int FORCE_ALLOW = 1; // 0x1
}
- @IntDef({android.content.integrity.Rule.DENY, android.content.integrity.Rule.FORCE_ALLOW}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface Rule.Effect {
- }
-
public class RuleSet {
method @NonNull public java.util.List<android.content.integrity.Rule> getRules();
method @NonNull public String getVersion();
@@ -1879,6 +1930,15 @@
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) public void startActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
}
+ public class DataLoaderParams {
+ method @NonNull public static final android.content.pm.DataLoaderParams forIncremental(@NonNull android.content.ComponentName, @NonNull String, @Nullable java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>);
+ method @NonNull public static final android.content.pm.DataLoaderParams forStreaming(@NonNull android.content.ComponentName, @NonNull String);
+ method @NonNull public final String getArguments();
+ method @NonNull public final android.content.ComponentName getComponentName();
+ method @NonNull public final java.util.Map<java.lang.String,android.os.ParcelFileDescriptor> getDynamicArgs();
+ method @NonNull public final int getType();
+ }
+
public final class InstantAppInfo implements android.os.Parcelable {
ctor public InstantAppInfo(android.content.pm.ApplicationInfo, String[], String[]);
ctor public InstantAppInfo(String, CharSequence, String[], String[]);
@@ -1902,6 +1962,18 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstantAppIntentFilter> CREATOR;
}
+ public final class InstantAppRequestInfo implements android.os.Parcelable {
+ ctor public InstantAppRequestInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, boolean, @NonNull String);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstantAppRequestInfo> CREATOR;
+ field @Nullable public final int[] hostDigestPrefix;
+ field @NonNull public final android.content.Intent intent;
+ field public final boolean isRequesterInstantApp;
+ field @NonNull public final String token;
+ field @NonNull public final android.os.UserHandle userHandle;
+ }
+
public final class InstantAppResolveInfo implements android.os.Parcelable {
ctor public InstantAppResolveInfo(@NonNull android.content.pm.InstantAppResolveInfo.InstantAppDigest, @Nullable String, @Nullable java.util.List<android.content.pm.InstantAppIntentFilter>, int);
ctor public InstantAppResolveInfo(@NonNull android.content.pm.InstantAppResolveInfo.InstantAppDigest, @Nullable String, @Nullable java.util.List<android.content.pm.InstantAppIntentFilter>, long, @Nullable android.os.Bundle);
@@ -1956,6 +2028,7 @@
}
public static class PackageInstaller.Session implements java.io.Closeable {
+ method public void addFile(@NonNull String, long, @NonNull byte[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
}
@@ -1969,14 +2042,17 @@
method public boolean getInstallAsInstantApp(boolean);
method public boolean getInstallAsVirtualPreload();
method public boolean getRequestDowngrade();
+ method public int getRollbackDataPolicy();
method @NonNull public java.util.Set<java.lang.String> getWhitelistedRestrictedPermissions();
}
public static class PackageInstaller.SessionParams implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean);
method @Deprecated public void setAllowDowngrade(boolean);
+ method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams);
method public void setDontKillApp(boolean);
method public void setEnableRollback(boolean);
+ method public void setEnableRollback(boolean, int);
method @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex();
method public void setInstallAsInstantApp(boolean);
@@ -2040,6 +2116,7 @@
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
field public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 32768; // 0x8000
+ field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
@@ -2114,7 +2191,7 @@
method public void onPermissionsChanged(int);
}
- @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+ @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
}
public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -2127,6 +2204,7 @@
public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
field public static final int FLAG_REMOVED = 2; // 0x2
field public static final int PROTECTION_FLAG_APP_PREDICTOR = 2097152; // 0x200000
+ field public static final int PROTECTION_FLAG_COMPANION = 8388608; // 0x800000
field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000
field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
@@ -2265,6 +2343,15 @@
}
+package android.hardware.biometrics {
+
+ public static interface BiometricManager.Authenticators {
+ field public static final int BIOMETRIC_CONVENIENCE = 4095; // 0xfff
+ field public static final int EMPTY_SET = 0; // 0x0
+ }
+
+}
+
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
@@ -3410,6 +3497,14 @@
field public static final int STATUS_OK = 0; // 0x0
}
+ 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;
+ field public final int start;
+ }
+
public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
@@ -3879,6 +3974,7 @@
field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 64; // 0x40
field public static final int FLAG_BYPASS_MUTE = 128; // 0x80
field public static final int FLAG_HW_HOTWORD = 32; // 0x20
+ field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
}
public static class AudioAttributes.Builder {
@@ -3929,16 +4025,19 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
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);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method public boolean isAudioServerRunning();
method public boolean isHdmiSystemAudioSupported();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int requestAudioFocus(@NonNull android.media.AudioFocusRequest, @Nullable android.media.audiopolicy.AudioPolicy);
method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAddress);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
@@ -3946,6 +4045,7 @@
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
field public static final int SUCCESS = 0; // 0x0
}
@@ -3967,6 +4067,7 @@
method public android.media.PlayerProxy getPlayerProxy();
method public int getPlayerState();
method public int getPlayerType();
+ method public boolean isActive();
field public static final int PLAYER_STATE_IDLE = 1; // 0x1
field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
field public static final int PLAYER_STATE_RELEASED = 0; // 0x0
@@ -4109,9 +4210,11 @@
}
public final class AudioProductStrategy implements android.os.Parcelable {
+ method @NonNull public static android.media.audiopolicy.AudioProductStrategy createInvalidAudioProductStrategy(int);
method public int describeContents();
method @NonNull public android.media.AudioAttributes getAudioAttributes();
method public int getId();
+ method public boolean supportsAudioAttributes(@NonNull android.media.AudioAttributes);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioProductStrategy> CREATOR;
}
@@ -4132,18 +4235,20 @@
package android.media.session {
public final class MediaSessionManager {
- method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.Callback);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventDispatchedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventDispatchedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
+ method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
- method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void unregisterCallback(@NonNull android.media.session.MediaSessionManager.Callback);
}
- public abstract static class MediaSessionManager.Callback {
- ctor public MediaSessionManager.Callback();
- method public abstract void onAddressedPlayerChanged(android.media.session.MediaSession.Token);
- method public abstract void onAddressedPlayerChanged(android.content.ComponentName);
- method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token);
- method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName);
+ public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener {
+ method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token);
+ }
+
+ public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+ method public default void onMediaKeyEventSessionChanged(@NonNull String, @Nullable android.media.session.MediaSession.Token);
}
public static interface MediaSessionManager.OnMediaKeyListener {
@@ -4193,8 +4298,11 @@
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerDetector createSoundTriggerDetector(java.util.UUID, @NonNull android.media.soundtrigger.SoundTriggerDetector.Callback, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID);
method public int getDetectionServiceOperationsTimeout();
- method @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.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 @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 void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
}
@@ -4351,6 +4459,7 @@
method @Nullable public String onHardwareRemoved(android.media.tv.TvInputHardwareInfo);
method @Nullable public android.media.tv.TvInputInfo onHdmiDeviceAdded(android.hardware.hdmi.HdmiDeviceInfo);
method @Nullable public String onHdmiDeviceRemoved(android.hardware.hdmi.HdmiDeviceInfo);
+ method public void onHdmiDeviceUpdated(@NonNull android.hardware.hdmi.HdmiDeviceInfo);
}
public abstract static class TvInputService.RecordingSession {
@@ -4400,6 +4509,24 @@
}
+package android.media.tv.tuner {
+
+ public abstract class FrontendSettings {
+ method public final int getFrequency();
+ method public abstract int getType();
+ }
+
+ 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();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.FrontendSettings);
+ }
+
+ public class Tuner.Descrambler {
+ }
+
+}
+
package android.metrics {
public class LogMaker {
@@ -4493,6 +4620,14 @@
method public void onUpstreamChanged(@Nullable android.net.Network);
}
+ public class InvalidPacketException extends java.lang.Exception {
+ ctor public InvalidPacketException(int);
+ field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+ field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+ field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+ field public final int error;
+ }
+
public final class IpConfiguration implements android.os.Parcelable {
ctor public IpConfiguration();
ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
@@ -4543,6 +4678,15 @@
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
}
+ public class KeepalivePacketData {
+ ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+ method @NonNull public byte[] getPacket();
+ field @NonNull public final java.net.InetAddress dstAddress;
+ field public final int dstPort;
+ field @NonNull public final java.net.InetAddress srcAddress;
+ field public final int srcPort;
+ }
+
public class LinkAddress implements android.os.Parcelable {
ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int);
ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
@@ -4558,12 +4702,20 @@
ctor public LinkProperties(@Nullable android.net.LinkProperties);
method public boolean addDnsServer(@NonNull java.net.InetAddress);
method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
+ method public boolean addPcscfServer(@NonNull java.net.InetAddress);
+ method @NonNull public java.util.List<java.net.InetAddress> getAddresses();
+ method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames();
+ method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses();
+ method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes();
method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
method @Nullable public String getTcpBufferSizes();
method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
method public boolean hasGlobalIpv6Address();
method public boolean hasIpv4Address();
+ method public boolean hasIpv4DefaultRoute();
+ method public boolean hasIpv4DnsServer();
method public boolean hasIpv6DefaultRoute();
+ method public boolean hasIpv6DnsServer();
method public boolean isIpv4Provisioned();
method public boolean isIpv6Provisioned();
method public boolean isProvisioned();
@@ -4822,11 +4974,14 @@
method @NonNull public java.util.List<android.net.LinkAddress> getInternalAddresses();
method @NonNull public java.util.List<java.net.InetAddress> getInternalDhcpServers();
method @NonNull public java.util.List<java.net.InetAddress> getInternalDnsServers();
- method @NonNull public java.util.List<android.net.LinkAddress> getInternalSubnets();
+ method @NonNull public java.util.List<android.net.IpPrefix> getInternalSubnets();
method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getOutboundTrafficSelectors();
}
public abstract class ChildSessionParams {
+ method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getLocalTrafficSelectors();
+ method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getRemoteTrafficSelectors();
+ method @NonNull public java.util.List<android.net.ipsec.ike.ChildSaProposal> getSaProposals();
}
public class IkeFqdnIdentification extends android.net.ipsec.ike.IkeIdentification {
@@ -4888,21 +5043,29 @@
public final class IkeSessionConfiguration {
ctor public IkeSessionConfiguration();
method @NonNull public String getRemoteApplicationVersion();
+ method @NonNull public java.util.List<byte[]> getRemoteVendorIDs();
method public boolean isIkeExtensionEnabled(int);
field public static final int EXTENSION_TYPE_FRAGMENTATION = 1; // 0x1
field public static final int EXTENSION_TYPE_MOBIKE = 2; // 0x2
}
public final class IkeSessionParams {
+ method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getLocalAuthConfig();
+ method @NonNull public android.net.ipsec.ike.IkeIdentification getLocalIdentification();
+ method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getRemoteAuthConfig();
+ method @NonNull public android.net.ipsec.ike.IkeIdentification getRemoteIdentification();
+ method @NonNull public java.util.List<android.net.ipsec.ike.IkeSaProposal> getSaProposals();
+ method @NonNull public java.net.InetAddress getServerAddress();
+ method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket getUdpEncapsulationSocket();
}
public static final class IkeSessionParams.Builder {
ctor public IkeSessionParams.Builder();
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.IkeSaProposal);
method @NonNull public android.net.ipsec.ike.IkeSessionParams build();
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
- method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@NonNull java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
+ method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey);
+ method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
+ method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@Nullable java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthPsk(@NonNull byte[]);
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLocalIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setRemoteIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
@@ -4910,6 +5073,27 @@
method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setUdpEncapsulationSocket(@NonNull android.net.IpSecManager.UdpEncapsulationSocket);
}
+ public abstract static class IkeSessionParams.IkeAuthConfig {
+ }
+
+ public static class IkeSessionParams.IkeAuthDigitalSignLocalConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
+ method @NonNull public java.security.cert.X509Certificate getClientEndCertificate();
+ method @NonNull public java.util.List<java.security.cert.X509Certificate> getIntermediateCertificates();
+ method @NonNull public java.security.PrivateKey getPrivateKey();
+ }
+
+ public static class IkeSessionParams.IkeAuthDigitalSignRemoteConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
+ method @Nullable public java.security.cert.X509Certificate getRemoteCaCert();
+ }
+
+ public static class IkeSessionParams.IkeAuthEapConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
+ method @NonNull public android.net.eap.EapSessionConfig getEapConfig();
+ }
+
+ public static class IkeSessionParams.IkeAuthPskConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
+ method @NonNull public byte[] getPsk();
+ }
+
public final class IkeTrafficSelector {
ctor public IkeTrafficSelector(int, int, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress);
field public final int endPort;
@@ -4956,23 +5140,49 @@
}
public final class TunnelModeChildSessionParams extends android.net.ipsec.ike.ChildSessionParams {
+ method @NonNull public java.util.List<android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest> getConfigurationRequests();
}
public static final class TunnelModeChildSessionParams.Builder {
ctor public TunnelModeChildSessionParams.Builder();
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.InetAddress, int);
+ method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet4Address);
+ method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet6Address, int);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDhcpServerRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDhcpServerRequest(@NonNull java.net.InetAddress);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDnsServerRequest(int);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDnsServerRequest(@NonNull java.net.InetAddress);
- method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalSubnetRequest(int);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams build();
}
+ public static interface TunnelModeChildSessionParams.ConfigRequest {
+ }
+
+ public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
+ method @Nullable public java.net.Inet4Address getAddress();
+ }
+
+ public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
+ method @Nullable public java.net.Inet4Address getAddress();
+ }
+
+ public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
+ method @Nullable public java.net.Inet4Address getAddress();
+ }
+
+ public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Netmask extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
+ }
+
+ public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
+ method @Nullable public java.net.Inet6Address getAddress();
+ method public int getPrefixLength();
+ }
+
+ public static interface TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
+ method @Nullable public java.net.Inet6Address getAddress();
+ }
+
}
package android.net.ipsec.ike.exceptions {
@@ -5182,11 +5392,15 @@
ctor public EasyConnectStatusCallback();
method public abstract void onConfiguratorSuccess(int);
method public abstract void onEnrolleeSuccess(int);
- method public abstract void onFailure(int);
+ method public void onFailure(int);
+ method public void onFailure(int, @Nullable String, @NonNull android.util.SparseArray<int[]>, @NonNull int[]);
method public abstract void onProgress(int);
field public static final int EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION = -2; // 0xfffffffe
field public static final int EASY_CONNECT_EVENT_FAILURE_BUSY = -5; // 0xfffffffb
+ field public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10; // 0xfffffff6
field public static final int EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = -4; // 0xfffffffc
+ field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11; // 0xfffffff5
+ field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12; // 0xfffffff4
field public static final int EASY_CONNECT_EVENT_FAILURE_GENERIC = -7; // 0xfffffff9
field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9; // 0xfffffff7
field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_URI = -1; // 0xffffffff
@@ -5194,7 +5408,10 @@
field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = -8; // 0xfffffff8
field public static final int EASY_CONNECT_EVENT_FAILURE_TIMEOUT = -6; // 0xfffffffa
field public static final int EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0; // 0x0
+ field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3; // 0x3
+ field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2; // 0x2
field public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1; // 0x1
+ field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1; // 0x1
field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0; // 0x0
}
@@ -5390,6 +5607,11 @@
field @Deprecated public byte id;
}
+ public class ScanResult implements android.os.Parcelable {
+ field public static final int KEY_MGMT_WAPI_CERT = 14; // 0xe
+ field public static final int KEY_MGMT_WAPI_PSK = 13; // 0xd
+ }
+
public final class SoftApConfiguration implements android.os.Parcelable {
method public int describeContents();
method public int getBand();
@@ -5400,9 +5622,10 @@
method @Nullable public String getWpa2Passphrase();
method public boolean isHiddenSsid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field public static final int BAND_2GHZ = 0; // 0x0
- field public static final int BAND_5GHZ = 1; // 0x1
- field public static final int BAND_ANY = -1; // 0xffffffff
+ field public static final int BAND_2GHZ = 1; // 0x1
+ field public static final int BAND_5GHZ = 2; // 0x2
+ field public static final int BAND_6GHZ = 4; // 0x4
+ field public static final int BAND_ANY = 7; // 0x7
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
@@ -5414,7 +5637,7 @@
method @NonNull public android.net.wifi.SoftApConfiguration build();
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);
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
@@ -5487,6 +5710,8 @@
}
@Deprecated public static class WifiConfiguration.KeyMgmt {
+ field @Deprecated public static final int WAPI_CERT = 14; // 0xe
+ field @Deprecated public static final int WAPI_PSK = 13; // 0xd
field @Deprecated public static final int WPA2_PSK = 4; // 0x4
}
@@ -5523,10 +5748,12 @@
method @NonNull public String getCaPath();
method @NonNull public String getClientCertificateAlias();
method public int getOcsp();
+ method @Nullable public String getWapiCertSuite();
method public void setCaCertificateAliases(@Nullable String[]);
method public void setCaPath(@Nullable String);
method public void setClientCertificateAlias(@Nullable String);
method public void setOcsp(int);
+ method public void setWapiCertSuite(@Nullable String);
field public static final int OCSP_NONE = 0; // 0x0
field public static final int OCSP_REQUEST_CERT_STATUS = 1; // 0x1
field public static final int OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS = 3; // 0x3
@@ -5577,7 +5804,6 @@
method public boolean isApMacRandomizationSupported();
method public boolean isConnectedMacRandomizationSupported();
method @Deprecated public boolean isDeviceToDeviceRttSupported();
- method public boolean isDualBandSupported();
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean isDualModeSupported();
method public boolean isPortableHotspotSupported();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
@@ -5590,10 +5816,10 @@
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerTrafficStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.TrafficStateCallback);
method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void removeOnWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreBackupData(@NonNull byte[]);
- method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreSoftApBackupData(@NonNull byte[]);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public android.net.wifi.SoftApConfiguration restoreSoftApBackupData(@NonNull byte[]);
method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void restoreSupplicantBackupData(@NonNull byte[], @NonNull byte[]);
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveBackupData();
- method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
@@ -6039,6 +6265,130 @@
}
+package android.net.wifi.wificond {
+
+ public final class NativeScanResult implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public byte[] getBssid();
+ method @NonNull public java.util.BitSet getCapabilities();
+ method public int getFrequencyMhz();
+ method @NonNull public byte[] getInformationElements();
+ method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos();
+ method public int getSignalMbm();
+ method @NonNull public byte[] getSsid();
+ method public long getTsf();
+ method public boolean isAssociated();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeScanResult> CREATOR;
+ }
+
+ public final class NativeWifiClient 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.net.wifi.wificond.NativeWifiClient> CREATOR;
+ field @NonNull public final byte[] macAddress;
+ }
+
+ public final class PnoNetwork implements android.os.Parcelable {
+ ctor public PnoNetwork();
+ method public int describeContents();
+ method @NonNull public int[] getFrequenciesMhz();
+ method @NonNull public byte[] getSsid();
+ method public boolean isHidden();
+ method public void setFrequenciesMhz(@NonNull int[]);
+ method public void setHidden(boolean);
+ method public void setSsid(@NonNull byte[]);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.PnoNetwork> CREATOR;
+ }
+
+ public final class PnoSettings implements android.os.Parcelable {
+ ctor public PnoSettings();
+ method public int describeContents();
+ method public int getIntervalMillis();
+ method public int getMin2gRssiDbm();
+ method public int getMin5gRssiDbm();
+ method public int getMin6gRssiDbm();
+ method @NonNull public java.util.List<android.net.wifi.wificond.PnoNetwork> getPnoNetworks();
+ method public void setIntervalMillis(int);
+ method public void setMin2gRssiDbm(int);
+ method public void setMin5gRssiDbm(int);
+ method public void setMin6gRssiDbm(int);
+ method public void setPnoNetworks(@NonNull java.util.List<android.net.wifi.wificond.PnoNetwork>);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.PnoSettings> CREATOR;
+ }
+
+ public final class RadioChainInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getChainId();
+ method public int getLevelDbm();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.RadioChainInfo> CREATOR;
+ }
+
+ public class WifiCondManager {
+ method public void abortScan(@NonNull String);
+ method public void enableVerboseLogging(boolean);
+ method @NonNull public int[] getChannelsMhzForBand(int);
+ 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 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);
+ method public boolean setupInterfaceForSoftApMode(@NonNull String);
+ method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String);
+ method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.wificond.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.PnoScanRequestCallback);
+ method public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>);
+ method public boolean stopPnoScan(@NonNull String);
+ method public boolean tearDownClientInterface(@NonNull String);
+ method public boolean tearDownInterfaces();
+ method public boolean tearDownSoftApInterface(@NonNull String);
+ field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1
+ field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0
+ field public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5; // 0x5
+ field public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2; // 0x2
+ field public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3; // 0x3
+ field public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4; // 0x4
+ field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1
+ }
+
+ public static interface WifiCondManager.PnoScanRequestCallback {
+ method public void onPnoRequestFailed();
+ method public void onPnoRequestSucceeded();
+ }
+
+ public static interface WifiCondManager.ScanEventCallback {
+ method public void onScanFailed();
+ method public void onScanResultReady();
+ }
+
+ public static interface WifiCondManager.SendMgmtFrameCallback {
+ method public void onAck(int);
+ method public void onFailure(int);
+ }
+
+ public static class WifiCondManager.SignalPollResult {
+ field public final int associationFrequencyMHz;
+ field public final int currentRssiDbm;
+ field public final int rxBitrateMbps;
+ field public final int txBitrateMbps;
+ }
+
+ public static interface WifiCondManager.SoftApCallback {
+ method public void onConnectedClientsChanged(@NonNull android.net.wifi.wificond.NativeWifiClient, boolean);
+ method public void onFailure();
+ method public void onSoftApChannelSwitched(int, int);
+ }
+
+ public static class WifiCondManager.TxPacketCounters {
+ field public final int txPacketFailed;
+ field public final int txPacketSucceeded;
+ }
+
+}
+
package android.nfc {
public final class NfcAdapter {
@@ -6133,6 +6483,27 @@
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void noteWifiScanStoppedFromSource(@NonNull android.os.WorkSource);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void noteWifiState(int, @Nullable String);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void noteWifiSupplicantStateChanged(int, boolean);
+ field public static final int WIFI_STATE_OFF = 0; // 0x0
+ field public static final int WIFI_STATE_OFF_SCANNING = 1; // 0x1
+ field public static final int WIFI_STATE_ON_CONNECTED_P2P = 5; // 0x5
+ field public static final int WIFI_STATE_ON_CONNECTED_STA = 4; // 0x4
+ field public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6; // 0x6
+ field public static final int WIFI_STATE_ON_DISCONNECTED = 3; // 0x3
+ field public static final int WIFI_STATE_ON_NO_NETWORKS = 2; // 0x2
+ field public static final int WIFI_STATE_SOFT_AP = 7; // 0x7
+ field public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7; // 0x7
+ field public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6; // 0x6
+ field public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5; // 0x5
+ field public static final int WIFI_SUPPL_STATE_COMPLETED = 10; // 0xa
+ field public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1; // 0x1
+ field public static final int WIFI_SUPPL_STATE_DORMANT = 11; // 0xb
+ field public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8; // 0x8
+ field public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9; // 0x9
+ field public static final int WIFI_SUPPL_STATE_INACTIVE = 3; // 0x3
+ field public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2; // 0x2
+ field public static final int WIFI_SUPPL_STATE_INVALID = 0; // 0x0
+ field public static final int WIFI_SUPPL_STATE_SCANNING = 4; // 0x4
+ field public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12; // 0xc
}
public class Binder implements android.os.IBinder {
@@ -6193,6 +6564,7 @@
}
public class Environment {
+ method @NonNull public static java.util.Collection<java.io.File> getInternalMediaDirectories();
method @NonNull public static java.io.File getOdmDirectory();
method @NonNull public static java.io.File getOemDirectory();
method @NonNull public static java.io.File getProductDirectory();
@@ -6432,6 +6804,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int);
field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1
field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0
+ field public static final String REBOOT_USERSPACE = "userspace";
field public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; // 0x3
field public static final int USER_ACTIVITY_EVENT_BUTTON = 1; // 0x1
field public static final int USER_ACTIVITY_EVENT_OTHER = 0; // 0x0
@@ -6524,6 +6897,21 @@
field public static final int STATUS_WAITING_REBOOT = 5; // 0x5
}
+ public class TelephonyServiceManager {
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyServiceRegisterer();
+ }
+
+ public static class TelephonyServiceManager.ServiceNotFoundException extends java.lang.Exception {
+ ctor public TelephonyServiceManager.ServiceNotFoundException(@NonNull String);
+ }
+
+ public final class TelephonyServiceManager.ServiceRegisterer {
+ method @Nullable public android.os.IBinder get();
+ method @NonNull public android.os.IBinder getOrThrow() throws android.os.TelephonyServiceManager.ServiceNotFoundException;
+ method public void register(@NonNull android.os.IBinder);
+ method @Nullable public android.os.IBinder tryGet();
+ }
+
public class UpdateEngine {
ctor public UpdateEngine();
method public void applyPayload(String, long, long, String[]);
@@ -6610,6 +6998,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap);
@@ -6732,6 +7121,14 @@
}
+package android.os.ext {
+
+ public class SdkExtensions {
+ method public static int getExtensionVersion(int);
+ }
+
+}
+
package android.os.image {
public class DynamicSystemClient {
@@ -6801,13 +7198,14 @@
method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>);
method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable);
method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
+ method @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String);
method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
- method @BinderThread public void onUpdateUserSensitive();
+ method @BinderThread public void onUpdateUserSensitivePermissionFlags();
field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
}
@@ -6817,6 +7215,8 @@
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 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(allOf={android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void startOneTimePermissionSession(@NonNull String, long, int, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void stopOneTimePermissionSession(@NonNull String);
}
public static final class PermissionManager.SplitPermissionInfo {
@@ -6940,34 +7340,34 @@
field public static final int STATUS_NOT_BLOCKED = 0; // 0x0
}
- public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
- field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
- field public static final android.net.Uri CONTENT_URI;
- field public static final String METADATA_AUTHORITY = "com.android.contacts.metadata";
- field public static final android.net.Uri METADATA_AUTHORITY_URI;
+ @Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
+ field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
+ field @Deprecated public static final android.net.Uri CONTENT_URI;
+ field @Deprecated public static final String METADATA_AUTHORITY = "com.android.contacts.metadata";
+ field @Deprecated public static final android.net.Uri METADATA_AUTHORITY_URI;
}
- protected static interface ContactsContract.MetadataSyncColumns {
- field public static final String ACCOUNT_NAME = "account_name";
- field public static final String ACCOUNT_TYPE = "account_type";
- field public static final String DATA = "data";
- field public static final String DATA_SET = "data_set";
- field public static final String DELETED = "deleted";
- field public static final String RAW_CONTACT_BACKUP_ID = "raw_contact_backup_id";
+ @Deprecated protected static interface ContactsContract.MetadataSyncColumns {
+ field @Deprecated public static final String ACCOUNT_NAME = "account_name";
+ field @Deprecated public static final String ACCOUNT_TYPE = "account_type";
+ field @Deprecated public static final String DATA = "data";
+ field @Deprecated public static final String DATA_SET = "data_set";
+ field @Deprecated public static final String DELETED = "deleted";
+ field @Deprecated public static final String RAW_CONTACT_BACKUP_ID = "raw_contact_backup_id";
}
- public static final class ContactsContract.MetadataSyncState implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncStateColumns {
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata_sync_state";
- field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata_sync_state";
- field public static final android.net.Uri CONTENT_URI;
+ @Deprecated public static final class ContactsContract.MetadataSyncState implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncStateColumns {
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata_sync_state";
+ field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata_sync_state";
+ field @Deprecated public static final android.net.Uri CONTENT_URI;
}
- protected static interface ContactsContract.MetadataSyncStateColumns {
- field public static final String ACCOUNT_NAME = "account_name";
- field public static final String ACCOUNT_TYPE = "account_type";
- field public static final String DATA_SET = "data_set";
- field public static final String STATE = "state";
+ @Deprecated protected static interface ContactsContract.MetadataSyncStateColumns {
+ field @Deprecated public static final String ACCOUNT_NAME = "account_name";
+ field @Deprecated public static final String ACCOUNT_TYPE = "account_type";
+ field @Deprecated public static final String DATA_SET = "data_set";
+ field @Deprecated public static final String STATE = "state";
}
public final class DeviceConfig {
@@ -6981,13 +7381,14 @@
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
- method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
field public static final String NAMESPACE_APP_COMPAT = "app_compat";
field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
field public static final String NAMESPACE_AUTOFILL = "autofill";
+ field public static final String NAMESPACE_BIOMETRICS = "biometrics";
field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
@@ -7013,6 +7414,10 @@
field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
}
+ public static class DeviceConfig.BadConfigException extends java.lang.Exception {
+ ctor public DeviceConfig.BadConfigException();
+ }
+
public static interface DeviceConfig.OnPropertiesChangedListener {
method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
}
@@ -7027,6 +7432,16 @@
method @Nullable public String getString(@NonNull String, @Nullable String);
}
+ public static final class DeviceConfig.Properties.Builder {
+ ctor public DeviceConfig.Properties.Builder(@NonNull String);
+ method @NonNull public android.provider.DeviceConfig.Properties build();
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setBoolean(@NonNull String, boolean);
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setFloat(@NonNull String, float);
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setInt(@NonNull String, int);
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setLong(@NonNull String, long);
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setString(@NonNull String, @Nullable String);
+ }
+
public final class DocumentsContract {
method public static boolean isManageMode(@NonNull android.net.Uri);
method @NonNull public static android.net.Uri setManageMode(@NonNull android.net.Uri);
@@ -7043,8 +7458,12 @@
}
public final class MediaStore {
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+ method @NonNull public static android.net.Uri rewriteToLegacy(@NonNull android.net.Uri);
+ method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+ method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
+ method public static void waitForIdle(@NonNull android.content.ContentResolver);
+ field public static final String AUTHORITY_LEGACY = "media_legacy";
+ field @NonNull public static final android.net.Uri AUTHORITY_LEGACY_URI;
}
public abstract class SearchIndexableData {
@@ -7160,6 +7579,7 @@
public final class Settings {
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";
field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION";
@@ -7187,6 +7607,7 @@
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
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 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";
@@ -7674,6 +8095,14 @@
}
+package android.service.dataloader {
+
+ public abstract class DataLoaderService extends android.app.Service {
+ ctor public DataLoaderService();
+ }
+
+}
+
package android.service.euicc {
public final class DownloadSubscriptionResult implements android.os.Parcelable {
@@ -7836,6 +8265,7 @@
field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
field public static final String KEY_IMPORTANCE = "key_importance";
field public static final String KEY_PEOPLE = "key_people";
+ field public static final String KEY_RANKING_SCORE = "key_ranking_score";
field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final String KEY_TEXT_REPLIES = "key_text_replies";
field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -8017,16 +8447,6 @@
}
-package android.service.sms {
-
- public abstract class FinancialSmsService extends android.app.Service {
- method public android.os.IBinder onBind(android.content.Intent);
- method @Nullable public abstract android.database.CursorWindow onGetSmsMessages(@NonNull android.os.Bundle);
- field public static final String ACTION_FINANCIAL_SERVICE_INTENT = "android.service.sms.action.FINANCIAL_SERVICE_INTENT";
- }
-
-}
-
package android.service.storage {
public abstract class ExternalStorageService extends android.app.Service {
@@ -8484,18 +8904,12 @@
}
public class CarrierConfigManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void overrideConfig(int, @Nullable android.os.PersistableBundle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void updateConfigForPhoneId(int, String);
field public static final String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
- }
-
- public static final class CarrierConfigManager.Wifi {
- field public static final String KEY_CARRIER_CONNECTION_MANAGER_PACKAGE_STRING = "wifi.carrier_connection_manager_package_string";
- field public static final String KEY_CARRIER_PROFILES_VERSION_INT = "wifi.carrier_profiles_version_int";
- field public static final String KEY_NETWORK_PROFILES_STRING_ARRAY = "wifi.network_profiles_string_array";
- field public static final String KEY_PASSPOINT_PROFILES_STRING_ARRAY = "wifi.passpoint_profiles_string_array";
- field public static final String KEY_PREFIX = "wifi.";
+ field public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL = "support_cdma_1x_voice_calls_bool";
}
public final class CarrierRestrictionRules implements android.os.Parcelable {
@@ -8527,6 +8941,13 @@
public class CbGeoUtils {
}
+ public static class CbGeoUtils.Circle implements android.telephony.CbGeoUtils.Geometry {
+ ctor public CbGeoUtils.Circle(@NonNull android.telephony.CbGeoUtils.LatLng, double);
+ method public boolean contains(@NonNull android.telephony.CbGeoUtils.LatLng);
+ method @NonNull public android.telephony.CbGeoUtils.LatLng getCenter();
+ method public double getRadius();
+ }
+
public static interface CbGeoUtils.Geometry {
method public boolean contains(@NonNull android.telephony.CbGeoUtils.LatLng);
}
@@ -8539,6 +8960,12 @@
field public final double lng;
}
+ public static class CbGeoUtils.Polygon implements android.telephony.CbGeoUtils.Geometry {
+ ctor public CbGeoUtils.Polygon(@NonNull java.util.List<android.telephony.CbGeoUtils.LatLng>);
+ method public boolean contains(@NonNull android.telephony.CbGeoUtils.LatLng);
+ method @NonNull public java.util.List<android.telephony.CbGeoUtils.LatLng> getVertices();
+ }
+
public class CellBroadcastIntents {
method public static void sendOrderedBroadcastForBackgroundReceivers(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
}
@@ -8552,6 +8979,34 @@
field public static final String CELL_BROADCAST_SERVICE_INTERFACE = "android.telephony.CellBroadcastService";
}
+ public abstract class CellIdentity implements android.os.Parcelable {
+ method @NonNull public abstract android.telephony.CellLocation asCellLocation();
+ }
+
+ public final class CellIdentityCdma extends android.telephony.CellIdentity {
+ method @NonNull public android.telephony.cdma.CdmaCellLocation asCellLocation();
+ }
+
+ public final class CellIdentityGsm extends android.telephony.CellIdentity {
+ method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+ }
+
+ public final class CellIdentityLte extends android.telephony.CellIdentity {
+ method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+ }
+
+ public final class CellIdentityNr extends android.telephony.CellIdentity {
+ method @NonNull public android.telephony.CellLocation asCellLocation();
+ }
+
+ public final class CellIdentityTdscdma extends android.telephony.CellIdentity {
+ method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+ }
+
+ public final class CellIdentityWcdma extends android.telephony.CellIdentity {
+ method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+ }
+
public final class DataFailCause {
field public static final int ACCESS_ATTEMPT_ALREADY_IN_PROGRESS = 2219; // 0x8ab
field public static final int ACCESS_BLOCK = 2087; // 0x827
@@ -8869,6 +9324,7 @@
field public static final int UE_SECURITY_CAPABILITIES_MISMATCH = 2185; // 0x889
field public static final int UMTS_HANDOVER_TO_IWLAN = 2199; // 0x897
field public static final int UMTS_REACTIVATION_REQ = 39; // 0x27
+ field public static final int UNACCEPTABLE_NETWORK_PARAMETER = 65538; // 0x10002
field public static final int UNACCEPTABLE_NON_EPS_AUTHENTICATION = 2187; // 0x88b
field public static final int UNKNOWN = 65536; // 0x10000
field public static final int UNKNOWN_INFO_ELEMENT = 99; // 0x63
@@ -9021,6 +9477,7 @@
method public int getSleepTimeMillis();
method public long getTimestamp();
method @NonNull public java.util.List<android.telephony.ModemActivityInfo.TransmitPower> getTransmitPowerInfo();
+ method public boolean isValid();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
field public static final int TX_POWER_LEVELS = 5; // 0x5
@@ -9129,28 +9586,23 @@
public class PhoneStateListener {
method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
- method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
- method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
- method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
method public void onRadioPowerStateChanged(int);
method public void onSrvccStateChanged(int);
method public void onVoiceActivationStateChanged(int);
field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
- field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
- field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
- field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
}
public final class PreciseCallState implements android.os.Parcelable {
+ ctor public PreciseCallState(int, int, int, int, int);
method public int describeContents();
method public int getBackgroundCallState();
method public int getForegroundCallState();
@@ -9170,13 +9622,12 @@
}
public final class PreciseDataConnectionState implements android.os.Parcelable {
- method public int describeContents();
- method @Nullable public String getDataConnectionApn();
- method public int getDataConnectionApnTypeBitMask();
- method public int getDataConnectionFailCause();
- method public int getDataConnectionState();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+ method @Deprecated @NonNull public String getDataConnectionApn();
+ method @Deprecated public int getDataConnectionApnTypeBitMask();
+ method @Deprecated public int getDataConnectionFailCause();
+ method @Deprecated @Nullable public android.net.LinkProperties getDataConnectionLinkProperties();
+ method @Deprecated public int getDataConnectionNetworkType();
+ method @Deprecated public int getDataConnectionState();
}
public final class PreciseDisconnectCause {
@@ -9277,6 +9728,7 @@
}
public class ServiceState implements android.os.Parcelable {
+ method public int getDataRegistrationState();
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);
@@ -9415,11 +9867,13 @@
public final class SmsManager {
method public boolean disableCellBroadcastRange(int, int, int);
method public boolean enableCellBroadcastRange(int, int, int);
+ 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>);
}
public class SubscriptionInfo implements android.os.Parcelable {
+ method public boolean areUiccApplicationsEnabled();
method @Nullable public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
method public int getProfileClass();
method public boolean isGroupDisabled();
@@ -9438,6 +9892,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+ field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
@@ -9459,6 +9914,7 @@
public class TelephonyFrameworkInitializer {
method public static void registerServiceWrappers();
+ method public static void setTelephonyServiceManager(@NonNull android.os.TelephonyServiceManager);
}
public final class TelephonyHistogram implements android.os.Parcelable {
@@ -9483,9 +9939,6 @@
public class TelephonyManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionReportDefaultNetworkStatus(int, boolean);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionResetAll(int);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionSetRadioEnabled(int, boolean);
method public int checkCarrierPrivilegesForPackage(String);
method public int checkCarrierPrivilegesForPackageAnyPhone(String);
method public void dial(String);
@@ -9498,6 +9951,7 @@
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCarrierPrivilegeStatus(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCarrierPrivilegedPackagesForAllActiveSubscriptions();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierRestrictionRules getCarrierRestrictionRules();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn();
@@ -9520,6 +9974,7 @@
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();
method public static long getMaxNumberVerificationTimeoutMillis();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getMergedImsisFromGroup();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getNetworkCountryIso(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
@@ -9539,6 +9994,8 @@
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 @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 isDataConnectivityPossible();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
@@ -9557,8 +10014,11 @@
method public boolean needsOtaServiceProvisioning();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
+ method public void requestModemActivityInfo(@NonNull android.os.ResultReceiver);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig();
method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings();
@@ -9572,11 +10032,13 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(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);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPin(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String);
@@ -9586,6 +10048,11 @@
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_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_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";
field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
@@ -10180,6 +10647,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
}
+ public class ImsManager {
+ method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
+ method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+ }
+
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -10223,6 +10695,22 @@
ctor @Deprecated public ImsMmTelManager.RegistrationCallback();
}
+ public class ImsRcsManager implements android.telephony.ims.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>);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAvailable(int) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCapable(int, int) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerRcsAvailabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterRcsAvailabilityCallback(@NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+ }
+
+ public static class ImsRcsManager.AvailabilityCallback {
+ ctor public ImsRcsManager.AvailabilityCallback();
+ method public void onAvailabilityChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
+ }
+
public final class ImsReasonInfo implements android.os.Parcelable {
field public static final String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service";
}
@@ -10584,9 +11072,22 @@
public class RcsFeature extends android.telephony.ims.feature.ImsFeature {
ctor public RcsFeature();
- method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+ method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+ method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
method public void onFeatureReady();
method public void onFeatureRemoved();
+ method public boolean queryCapabilityConfiguration(int, int);
+ method @NonNull public final android.telephony.ims.feature.RcsFeature.RcsImsCapabilities queryCapabilityStatus();
+ }
+
+ public static class RcsFeature.RcsImsCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+ ctor public RcsFeature.RcsImsCapabilities(int);
+ method public void addCapabilities(int);
+ method public boolean isCapable(int);
+ method public void removeCapabilities(int);
+ field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0
+ field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1
+ field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
}
}
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index fcf5178..da0aae0 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -24,6 +24,11 @@
ExecutorRegistration: android.net.wifi.p2p.WifiP2pManager#setWifiP2pChannels(android.net.wifi.p2p.WifiP2pManager.Channel, int, int, android.net.wifi.p2p.WifiP2pManager.ActionListener):
Registration methods should have overload that accepts delivery Executor: `setWifiP2pChannels`
+HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities():
+ Type must not be heavy BitSet (method android.net.wifi.wificond.NativeScanResult.getCapabilities())
+PairedRegistration: android.net.wifi.wificond.WifiCondManager#registerApCallback(String, java.util.concurrent.Executor, android.net.wifi.wificond.WifiCondManager.SoftApCallback):
+ Found registerApCallback but not unregisterApCallback in android.net.wifi.wificond.WifiCondManager
+
GenericException: android.app.prediction.AppPredictor#finalize():
@@ -196,7 +201,6 @@
ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context):
-
SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
diff --git a/api/test-current.txt b/api/test-current.txt
index 3e14469..08a2160 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -12,6 +12,7 @@
field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
+ field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
@@ -157,6 +158,7 @@
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static int getNumOps();
method public static String[] getOpStrs();
+ method @NonNull @RequiresPermission("android.permission.GET_APP_OPS_STATS") public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...);
method public boolean isOperationActive(int, int, String);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void offsetHistory(long);
method public static int opToDefaultMode(@NonNull String);
@@ -307,15 +309,20 @@
public static final class AppOpsManager.OpEntry implements android.os.Parcelable {
method public int describeContents();
- method public long getDuration();
+ method @Deprecated public long getDuration();
method @NonNull public java.util.Map<java.lang.String,android.app.AppOpsManager.OpFeatureEntry> getFeatures();
method public long getLastAccessBackgroundTime(int);
method public long getLastAccessForegroundTime(int);
method public long getLastAccessTime(int);
method public long getLastAccessTime(int, int, int);
method public long getLastBackgroundDuration(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastBackgroundProxyInfo(int);
+ method public long getLastDuration(int);
method public long getLastDuration(int, int, int);
method public long getLastForegroundDuration(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastForegroundProxyInfo(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastProxyInfo(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastProxyInfo(int, int, int);
method public long getLastRejectBackgroundTime(int);
method public long getLastRejectForegroundTime(int);
method public long getLastRejectTime(int);
@@ -323,34 +330,53 @@
method public int getMode();
method @NonNull public String getOpStr();
method @Deprecated @Nullable public String getProxyPackageName();
- method @Nullable public String getProxyPackageName(int, int);
+ method @Deprecated @Nullable public String getProxyPackageName(int, int);
method @Deprecated public int getProxyUid();
- method public int getProxyUid(int, int);
+ method @Deprecated public int getProxyUid(int, int);
method public boolean isRunning();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.OpEntry> CREATOR;
}
- public static final class AppOpsManager.OpFeatureEntry {
- method public long getDuration();
+ public static final class AppOpsManager.OpEventProxyInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getFeatureId();
+ method @Nullable public String getPackageName();
+ method @IntRange(from=0) public int getUid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.OpEventProxyInfo> CREATOR;
+ }
+
+ public static final class AppOpsManager.OpFeatureEntry implements android.os.Parcelable {
+ method public int describeContents();
method public long getLastAccessBackgroundTime(int);
method public long getLastAccessForegroundTime(int);
method public long getLastAccessTime(int);
method public long getLastAccessTime(int, int, int);
method public long getLastBackgroundDuration(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastBackgroundProxyInfo(int);
+ method public long getLastDuration(int);
method public long getLastDuration(int, int, int);
method public long getLastForegroundDuration(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastForegroundProxyInfo(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastProxyInfo(int);
+ method @Nullable public android.app.AppOpsManager.OpEventProxyInfo getLastProxyInfo(int, int, int);
method public long getLastRejectBackgroundTime(int);
method public long getLastRejectForegroundTime(int);
method public long getLastRejectTime(int);
method public long getLastRejectTime(int, int, int);
- method @Nullable public String getProxyFeatureId();
- method @Nullable public String getProxyFeatureId(int, int);
- method @Nullable public String getProxyPackageName();
- method @Nullable public String getProxyPackageName(int, int);
- method public int getProxyUid();
- method public int getProxyUid(int, int);
method public boolean isRunning();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.OpFeatureEntry> CREATOR;
+ }
+
+ public static final class AppOpsManager.PackageOps implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.app.AppOpsManager.OpEntry> getOps();
+ method @NonNull public String getPackageName();
+ method public int getUid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
}
public class DownloadManager {
@@ -629,6 +655,9 @@
public class StorageStatsManager {
method public boolean isQuotaSupported(@NonNull java.util.UUID);
method public boolean isReservedSupported(@NonNull java.util.UUID);
+ method @NonNull @WorkerThread public java.util.Collection<android.os.storage.CrateInfo> queryCratesForPackage(@NonNull java.util.UUID, @NonNull String, @NonNull android.os.UserHandle) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull @WorkerThread public java.util.Collection<android.os.storage.CrateInfo> queryCratesForUid(@NonNull java.util.UUID, int) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_CRATES) @WorkerThread public java.util.Collection<android.os.storage.CrateInfo> queryCratesForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
}
public final class UsageStatsManager {
@@ -697,6 +726,7 @@
public abstract class Context {
method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public java.io.File getCrateDir(@NonNull String);
method public abstract android.view.Display getDisplay();
method public abstract int getDisplayId();
method public android.os.UserHandle getUser();
@@ -711,6 +741,7 @@
field public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
field public static final String ROLLBACK_SERVICE = "rollback";
field public static final String STATUS_BAR_SERVICE = "statusbar";
+ field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
field public static final String TEST_NETWORK_SERVICE = "test_network";
}
@@ -722,6 +753,7 @@
public class Intent implements java.lang.Cloneable android.os.Parcelable {
field @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
+ field public static final String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
}
@@ -746,13 +778,16 @@
}
public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
+ method public int getRollbackDataPolicy();
method @NonNull public java.util.Set<java.lang.String> getWhitelistedRestrictedPermissions();
}
public static class PackageInstaller.SessionParams implements android.os.Parcelable {
method public void setEnableRollback(boolean);
+ method public void setEnableRollback(boolean, int);
method @RequiresPermission("android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS") public void setGrantedRuntimePermissions(String[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex();
+ method public void setInstallerPackageName(@Nullable String);
method public void setRequestDowngrade(boolean);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
}
@@ -805,6 +840,7 @@
public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
field public static final int FLAG_REMOVED = 2; // 0x2
field public static final int PROTECTION_FLAG_APP_PREDICTOR = 2097152; // 0x200000
+ field public static final int PROTECTION_FLAG_COMPANION = 8388608; // 0x800000
field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000
field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
@@ -1151,6 +1187,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
+ field public static final String FUSED_PROVIDER = "fused";
}
public final class LocationRequest implements android.os.Parcelable {
@@ -2309,6 +2346,17 @@
package android.os.storage {
+ public final class CrateInfo implements android.os.Parcelable {
+ ctor public CrateInfo(@NonNull CharSequence, long);
+ ctor public CrateInfo(@NonNull CharSequence);
+ method @Nullable public static android.os.storage.CrateInfo copyFrom(int, @Nullable String, @Nullable String);
+ method public int describeContents();
+ method public long getExpirationMillis();
+ method @NonNull public CharSequence getLabel();
+ method public void writeToParcel(@Nullable android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.storage.CrateInfo> CREATOR;
+ }
+
public class StorageManager {
method public static boolean hasIsolatedStorage();
}
@@ -2430,6 +2478,7 @@
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
field public static final String NAMESPACE_ANDROID = "android";
field public static final String NAMESPACE_AUTOFILL = "autofill";
+ field public static final String NAMESPACE_BIOMETRICS = "biometrics";
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
field public static final String NAMESPACE_PERMISSIONS = "permissions";
field public static final String NAMESPACE_PRIVACY = "privacy";
@@ -2451,15 +2500,20 @@
method @Nullable public String getString(@NonNull String, @Nullable String);
}
+ public static final class DeviceConfig.Properties.Builder {
+ ctor public DeviceConfig.Properties.Builder(@NonNull String);
+ method @NonNull public android.provider.DeviceConfig.Properties build();
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setBoolean(@NonNull String, boolean);
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setFloat(@NonNull String, float);
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setInt(@NonNull String, int);
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setLong(@NonNull String, long);
+ method @NonNull public android.provider.DeviceConfig.Properties.Builder setString(@NonNull String, @Nullable String);
+ }
+
public final class MediaStore {
- method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
- method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
- method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
- method public static android.net.Uri scanFile(android.content.Context, java.io.File);
- method public static android.net.Uri scanFileFromShell(android.content.Context, java.io.File);
- method public static void scanVolume(android.content.Context, java.io.File);
- method public static void waitForIdle(android.content.Context);
+ method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+ method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
+ method public static void waitForIdle(@NonNull android.content.ContentResolver);
}
public final class Settings {
@@ -2817,6 +2871,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
field public static final String KEY_IMPORTANCE = "key_importance";
+ field public static final String KEY_RANKING_SCORE = "key_ranking_score";
field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final String KEY_TEXT_REPLIES = "key_text_replies";
field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -3147,6 +3202,10 @@
field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
}
+ public final class PreciseDataConnectionState implements android.os.Parcelable {
+ ctor @Deprecated public PreciseDataConnectionState(int, int, int, @NonNull String, @Nullable android.net.LinkProperties, int);
+ }
+
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
method public void setCdmaSystemAndNetworkId(int, int);
@@ -3413,6 +3472,11 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
}
+ public class ImsManager {
+ method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
+ method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+ }
+
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -3456,6 +3520,22 @@
ctor @Deprecated public ImsMmTelManager.RegistrationCallback();
}
+ public class ImsRcsManager implements android.telephony.ims.RegistrationManager {
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isAvailable(int) throws android.telephony.ims.ImsException;
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isCapable(int, int) throws android.telephony.ims.ImsException;
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerRcsAvailabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterRcsAvailabilityCallback(@NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+ }
+
+ public static class ImsRcsManager.AvailabilityCallback {
+ ctor public ImsRcsManager.AvailabilityCallback();
+ method public void onAvailabilityChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
+ }
+
public class ImsService extends android.app.Service {
ctor public ImsService();
method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int);
@@ -3813,9 +3893,22 @@
public class RcsFeature extends android.telephony.ims.feature.ImsFeature {
ctor public RcsFeature();
- method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+ method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+ method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
method public void onFeatureReady();
method public void onFeatureRemoved();
+ method public boolean queryCapabilityConfiguration(int, int);
+ method @NonNull public final android.telephony.ims.feature.RcsFeature.RcsImsCapabilities queryCapabilityStatus();
+ }
+
+ public static class RcsFeature.RcsImsCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+ ctor public RcsFeature.RcsImsCapabilities(int);
+ method public void addCapabilities(int);
+ method public boolean isCapable(int);
+ method public void removeCapabilities(int);
+ field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0
+ field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1
+ field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
}
}
@@ -4182,138 +4275,10 @@
method public void writeRawZigZag64(long);
}
- public final class ProtoOutputStream extends android.util.proto.ProtoStream {
- ctor public ProtoOutputStream();
- ctor public ProtoOutputStream(int);
- ctor public ProtoOutputStream(java.io.OutputStream);
- ctor public ProtoOutputStream(java.io.FileDescriptor);
- method public static int checkFieldId(long, long);
- method public void dump(String);
- method public void end(long);
- method @Deprecated public void endObject(long);
- method @Deprecated public void endRepeatedObject(long);
- method public void flush();
- method public byte[] getBytes();
- method public int getRawSize();
- method public static long makeFieldId(int, long);
- method public long start(long);
- method @Deprecated public long startObject(long);
- method @Deprecated public long startRepeatedObject(long);
- method public void write(long, double);
- method public void write(long, float);
- method public void write(long, int);
- method public void write(long, long);
- method public void write(long, boolean);
- method public void write(long, String);
- method public void write(long, byte[]);
- method @Deprecated public void writeBool(long, boolean);
- method @Deprecated public void writeBytes(long, byte[]);
- method @Deprecated public void writeDouble(long, double);
- method @Deprecated public void writeEnum(long, int);
- method @Deprecated public void writeFixed32(long, int);
- method @Deprecated public void writeFixed64(long, long);
- method @Deprecated public void writeFloat(long, float);
- method @Deprecated public void writeInt32(long, int);
- method @Deprecated public void writeInt64(long, long);
- method @Deprecated public void writeObject(long, byte[]);
- method @Deprecated public void writePackedBool(long, boolean[]);
- method @Deprecated public void writePackedDouble(long, double[]);
- method @Deprecated public void writePackedEnum(long, int[]);
- method @Deprecated public void writePackedFixed32(long, int[]);
- method @Deprecated public void writePackedFixed64(long, long[]);
- method @Deprecated public void writePackedFloat(long, float[]);
- method @Deprecated public void writePackedInt32(long, int[]);
- method @Deprecated public void writePackedInt64(long, long[]);
- method @Deprecated public void writePackedSFixed32(long, int[]);
- method @Deprecated public void writePackedSFixed64(long, long[]);
- method @Deprecated public void writePackedSInt32(long, int[]);
- method @Deprecated public void writePackedSInt64(long, long[]);
- method @Deprecated public void writePackedUInt32(long, int[]);
- method @Deprecated public void writePackedUInt64(long, long[]);
- method @Deprecated public void writeRepeatedBool(long, boolean);
- method @Deprecated public void writeRepeatedBytes(long, byte[]);
- method @Deprecated public void writeRepeatedDouble(long, double);
- method @Deprecated public void writeRepeatedEnum(long, int);
- method @Deprecated public void writeRepeatedFixed32(long, int);
- method @Deprecated public void writeRepeatedFixed64(long, long);
- method @Deprecated public void writeRepeatedFloat(long, float);
- method @Deprecated public void writeRepeatedInt32(long, int);
- method @Deprecated public void writeRepeatedInt64(long, long);
- method @Deprecated public void writeRepeatedObject(long, byte[]);
- method @Deprecated public void writeRepeatedSFixed32(long, int);
- method @Deprecated public void writeRepeatedSFixed64(long, long);
- method @Deprecated public void writeRepeatedSInt32(long, int);
- method @Deprecated public void writeRepeatedSInt64(long, long);
- method @Deprecated public void writeRepeatedString(long, String);
- method @Deprecated public void writeRepeatedUInt32(long, int);
- method @Deprecated public void writeRepeatedUInt64(long, long);
- method @Deprecated public void writeSFixed32(long, int);
- method @Deprecated public void writeSFixed64(long, long);
- method @Deprecated public void writeSInt32(long, int);
- method @Deprecated public void writeSInt64(long, long);
- method @Deprecated public void writeString(long, String);
- method public void writeTag(int, int);
- method @Deprecated public void writeUInt32(long, int);
- method @Deprecated public void writeUInt64(long, long);
- }
-
public class ProtoParseException extends java.lang.RuntimeException {
ctor public ProtoParseException(String);
}
- public abstract class ProtoStream {
- ctor public ProtoStream();
- method public static int convertObjectIdToOrdinal(int);
- method public static int getDepthFromToken(long);
- method public static String getFieldCountString(long);
- method public static String getFieldIdString(long);
- method public static String getFieldTypeString(long);
- method public static int getObjectIdFromToken(long);
- method public static int getOffsetFromToken(long);
- method public static boolean getRepeatedFromToken(long);
- method public static int getTagSizeFromToken(long);
- method public static String getWireTypeString(int);
- method public static long makeFieldId(int, long);
- method public static long makeToken(int, boolean, int, int, int);
- method public static String token2String(long);
- field public static final long FIELD_COUNT_MASK = 16492674416640L; // 0xf0000000000L
- field public static final long FIELD_COUNT_PACKED = 5497558138880L; // 0x50000000000L
- field public static final long FIELD_COUNT_REPEATED = 2199023255552L; // 0x20000000000L
- field public static final int FIELD_COUNT_SHIFT = 40; // 0x28
- field public static final long FIELD_COUNT_SINGLE = 1099511627776L; // 0x10000000000L
- field public static final long FIELD_COUNT_UNKNOWN = 0L; // 0x0L
- field public static final int FIELD_ID_MASK = -8; // 0xfffffff8
- field public static final int FIELD_ID_SHIFT = 3; // 0x3
- field public static final long FIELD_TYPE_BOOL = 34359738368L; // 0x800000000L
- field public static final long FIELD_TYPE_BYTES = 51539607552L; // 0xc00000000L
- field public static final long FIELD_TYPE_DOUBLE = 4294967296L; // 0x100000000L
- field public static final long FIELD_TYPE_ENUM = 60129542144L; // 0xe00000000L
- field public static final long FIELD_TYPE_FIXED32 = 30064771072L; // 0x700000000L
- field public static final long FIELD_TYPE_FIXED64 = 25769803776L; // 0x600000000L
- field public static final long FIELD_TYPE_FLOAT = 8589934592L; // 0x200000000L
- field public static final long FIELD_TYPE_INT32 = 21474836480L; // 0x500000000L
- field public static final long FIELD_TYPE_INT64 = 12884901888L; // 0x300000000L
- field public static final long FIELD_TYPE_MASK = 1095216660480L; // 0xff00000000L
- field public static final long FIELD_TYPE_MESSAGE = 47244640256L; // 0xb00000000L
- field protected static final String[] FIELD_TYPE_NAMES;
- field public static final long FIELD_TYPE_SFIXED32 = 64424509440L; // 0xf00000000L
- field public static final long FIELD_TYPE_SFIXED64 = 68719476736L; // 0x1000000000L
- field public static final int FIELD_TYPE_SHIFT = 32; // 0x20
- field public static final long FIELD_TYPE_SINT32 = 73014444032L; // 0x1100000000L
- field public static final long FIELD_TYPE_SINT64 = 77309411328L; // 0x1200000000L
- field public static final long FIELD_TYPE_STRING = 38654705664L; // 0x900000000L
- field public static final long FIELD_TYPE_UINT32 = 55834574848L; // 0xd00000000L
- field public static final long FIELD_TYPE_UINT64 = 17179869184L; // 0x400000000L
- field public static final long FIELD_TYPE_UNKNOWN = 0L; // 0x0L
- field public static final int WIRE_TYPE_END_GROUP = 4; // 0x4
- field public static final int WIRE_TYPE_FIXED32 = 5; // 0x5
- field public static final int WIRE_TYPE_FIXED64 = 1; // 0x1
- field public static final int WIRE_TYPE_LENGTH_DELIMITED = 2; // 0x2
- field public static final int WIRE_TYPE_MASK = 7; // 0x7
- field public static final int WIRE_TYPE_START_GROUP = 3; // 0x3
- field public static final int WIRE_TYPE_VARINT = 0; // 0x0
- }
-
public class WireTypeMismatchException extends android.util.proto.ProtoParseException {
ctor public WireTypeMismatchException(String);
}
@@ -4394,6 +4359,7 @@
method public void setAutofilled(boolean);
method public final void setFocusedInCluster();
method public void setIsRootNamespace(boolean);
+ method public final void setShowingLayoutBounds(boolean);
}
public class ViewConfiguration {
@@ -4421,7 +4387,7 @@
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40
field public CharSequence accessibilityTitle;
- field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x400, equals=0x400, name="KEYGUARD"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC")}) public int privateFlags;
+ field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x400, equals=0x400, name="KEYGUARD"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC"), @android.view.ViewDebug.FlagToString(mask=0x4000000, equals=0x4000000, name="FIT_INSETS_CONTROLLED"), @android.view.ViewDebug.FlagToString(mask=0x8000000, equals=0x8000000, name="ONLY_DRAW_BOTTOM_BAR_BACKGROUND")}) public int privateFlags;
}
public class WindowlessViewRoot {
@@ -4437,7 +4403,7 @@
public final class AccessibilityManager {
method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, @Nullable android.os.Handler);
- method @Nullable @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public String getAccessibilityShortcutService();
+ method @NonNull @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
method @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public void performAccessibilityShortcut();
method public void removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
}
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index a8c4db38..ef8165f 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -2426,6 +2426,12 @@
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.
+
RawAidl: android.telephony.mbms.vendor.MbmsDownloadServiceBase:
diff --git a/cmds/hid/jni/Android.bp b/cmds/hid/jni/Android.bp
index 095cfc6..2c07de0 100644
--- a/cmds/hid/jni/Android.bp
+++ b/cmds/hid/jni/Android.bp
@@ -5,6 +5,7 @@
shared_libs: [
"libandroid",
+ "libbase",
"liblog",
"libnativehelper",
],
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index d4fdf85..f56dd6e 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -21,17 +21,21 @@
#include <linux/uhid.h>
#include <fcntl.h>
+#include <inttypes.h>
+#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <memory>
-#include <unistd.h>
+#include <android/log.h>
+#include <android/looper.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedUtfChars.h>
-#include <android/looper.h>
-#include <android/log.h>
+
+#include <android-base/stringprintf.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
@@ -46,6 +50,7 @@
static struct {
jmethodID onDeviceOpen;
jmethodID onDeviceGetReport;
+ jmethodID onDeviceOutput;
jmethodID onDeviceError;
} gDeviceCallbackClassInfo;
@@ -61,6 +66,26 @@
}
}
+static ScopedLocalRef<jbyteArray> toJbyteArray(JNIEnv* env, const std::vector<uint8_t>& vector) {
+ ScopedLocalRef<jbyteArray> array(env, env->NewByteArray(vector.size()));
+ if (array.get() == nullptr) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+ return array;
+ }
+ static_assert(sizeof(char) == sizeof(uint8_t));
+ env->SetByteArrayRegion(array.get(), 0, vector.size(),
+ reinterpret_cast<const signed char*>(vector.data()));
+ return array;
+}
+
+static std::string toString(const std::vector<uint8_t>& data) {
+ std::string s = "";
+ for (uint8_t b : data) {
+ s += android::base::StringPrintf("%x ", b);
+ }
+ return s;
+}
+
DeviceCallback::DeviceCallback(JNIEnv* env, jobject callback) :
mCallbackObject(env->NewGlobalRef(callback)) {
env->GetJavaVM(&mJavaVM);
@@ -90,23 +115,30 @@
checkAndClearException(env, "onDeviceGetReport");
}
+void DeviceCallback::onDeviceOutput(const std::vector<uint8_t>& data) {
+ JNIEnv* env = getJNIEnv();
+ env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput,
+ toJbyteArray(env, data).get());
+ checkAndClearException(env, "onDeviceOutput");
+}
+
JNIEnv* DeviceCallback::getJNIEnv() {
JNIEnv* env;
mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
return env;
}
-Device* Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
- std::vector<uint8_t> descriptor, std::unique_ptr<DeviceCallback> callback) {
-
+std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
+ const std::vector<uint8_t>& descriptor,
+ std::unique_ptr<DeviceCallback> callback) {
size_t size = descriptor.size();
if (size > HID_MAX_DESCRIPTOR_SIZE) {
LOGE("Received invalid hid report with descriptor size %zu, skipping", size);
return nullptr;
}
- int fd = ::open(UHID_PATH, O_RDWR | O_CLOEXEC);
- if (fd < 0) {
+ android::base::unique_fd fd(::open(UHID_PATH, O_RDWR | O_CLOEXEC));
+ if (!fd.ok()) {
LOGE("Failed to open uhid: %s", strerror(errno));
return nullptr;
}
@@ -114,8 +146,7 @@
struct uhid_event ev = {};
ev.type = UHID_CREATE2;
strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
- memcpy(&ev.u.create2.rd_data, descriptor.data(),
- size * sizeof(ev.u.create2.rd_data[0]));
+ memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
ev.u.create2.rd_size = size;
ev.u.create2.bus = BUS_BLUETOOTH;
ev.u.create2.vendor = vid;
@@ -126,7 +157,6 @@
errno = 0;
ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
if (ret < 0 || ret != sizeof(ev)) {
- ::close(fd);
LOGE("Failed to create uhid node: %s", strerror(errno));
return nullptr;
}
@@ -134,21 +164,21 @@
// Wait for the device to actually be created.
ret = TEMP_FAILURE_RETRY(::read(fd, &ev, sizeof(ev)));
if (ret < 0 || ev.type != UHID_START) {
- ::close(fd);
LOGE("uhid node failed to start: %s", strerror(errno));
return nullptr;
}
- return new Device(id, fd, std::move(callback));
+ // using 'new' to access non-public constructor
+ return std::unique_ptr<Device>(new Device(id, std::move(fd), std::move(callback)));
}
-Device::Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback) :
- mId(id), mFd(fd), mDeviceCallback(std::move(callback)) {
+Device::Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback)
+ : mId(id), mFd(std::move(fd)), mDeviceCallback(std::move(callback)) {
ALooper* aLooper = ALooper_forThread();
if (aLooper == NULL) {
LOGE("Could not get ALooper, ALooper_forThread returned NULL");
aLooper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
}
- ALooper_addFd(aLooper, fd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents,
+ ALooper_addFd(aLooper, mFd, 0, ALOOPER_EVENT_INPUT, handleLooperEvents,
reinterpret_cast<void*>(this));
}
@@ -162,8 +192,14 @@
struct uhid_event ev = {};
ev.type = UHID_DESTROY;
TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
- ::close(mFd);
- mFd = -1;
+}
+
+// Send event over the fd.
+static void writeEvent(int fd, struct uhid_event& ev, const char* messageType) {
+ ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
+ if (ret < 0 || ret != sizeof(ev)) {
+ LOGE("Failed to send uhid_event %s: %s", messageType, strerror(errno));
+ }
}
void Device::sendReport(const std::vector<uint8_t>& report) const {
@@ -176,10 +212,7 @@
ev.type = UHID_INPUT2;
ev.u.input2.size = report.size();
memcpy(&ev.u.input2.data, report.data(), report.size() * sizeof(ev.u.input2.data[0]));
- ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
- if (ret < 0 || ret != sizeof(ev)) {
- LOGE("Failed to send hid event: %s", strerror(errno));
- }
+ writeEvent(mFd, ev, "UHID_INPUT2");
}
void Device::sendGetFeatureReportReply(uint32_t id, const std::vector<uint8_t>& report) const {
@@ -190,10 +223,7 @@
ev.u.get_report_reply.size = report.size();
memcpy(&ev.u.get_report_reply.data, report.data(),
report.size() * sizeof(ev.u.get_report_reply.data[0]));
- ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
- if (ret < 0 || ret != sizeof(ev)) {
- LOGE("Failed to send hid event (UHID_GET_REPORT_REPLY): %s", strerror(errno));
- }
+ writeEvent(mFd, ev, "UHID_GET_REPORT_REPLY");
}
int Device::handleEvents(int events) {
@@ -210,13 +240,37 @@
return 0;
}
- if (ev.type == UHID_OPEN) {
- mDeviceCallback->onDeviceOpen();
- } else if (ev.type == UHID_GET_REPORT) {
- mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum);
- } else if (ev.type == UHID_SET_REPORT) {
- LOGE("UHID_SET_REPORT is currently not supported");
- return 0;
+ switch (ev.type) {
+ case UHID_OPEN: {
+ mDeviceCallback->onDeviceOpen();
+ break;
+ }
+ case UHID_GET_REPORT: {
+ mDeviceCallback->onDeviceGetReport(ev.u.get_report.id, ev.u.get_report.rnum);
+ break;
+ }
+ case UHID_SET_REPORT: {
+ const struct uhid_set_report_req& set_report = ev.u.set_report;
+ if (set_report.size > UHID_DATA_MAX) {
+ LOGE("SET_REPORT contains too much data: size = %" PRIu16, set_report.size);
+ return 0;
+ }
+
+ std::vector<uint8_t> data(set_report.data, set_report.data + set_report.size);
+ LOGI("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id,
+ set_report.rnum, toString(data).c_str());
+ break;
+ }
+ case UHID_OUTPUT: {
+ struct uhid_output_req& output = ev.u.output;
+ std::vector<uint8_t> data(output.data, output.data + output.size);
+ mDeviceCallback->onDeviceOutput(data);
+ break;
+ }
+ default: {
+ LOGI("Unhandled event type: %" PRIu32, ev.type);
+ break;
+ }
}
return 1;
@@ -250,9 +304,10 @@
std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
- uhid::Device* d = uhid::Device::open(
- id, reinterpret_cast<const char*>(name.c_str()), vid, pid, desc, std::move(cb));
- return reinterpret_cast<jlong>(d);
+ std::unique_ptr<uhid::Device> d =
+ uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, desc,
+ std::move(cb));
+ return reinterpret_cast<jlong>(d.release());
}
static void sendReport(JNIEnv* env, jclass /* clazz */, jlong ptr, jbyteArray rawReport) {
@@ -304,6 +359,8 @@
env->GetMethodID(clazz, "onDeviceOpen", "()V");
uhid::gDeviceCallbackClassInfo.onDeviceGetReport =
env->GetMethodID(clazz, "onDeviceGetReport", "(II)V");
+ uhid::gDeviceCallbackClassInfo.onDeviceOutput =
+ env->GetMethodID(clazz, "onDeviceOutput", "([B)V");
uhid::gDeviceCallbackClassInfo.onDeviceError =
env->GetMethodID(clazz, "onDeviceError", "()V");
if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL ||
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 892c7cd..93ea881 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -19,6 +19,8 @@
#include <jni.h>
+#include <android-base/unique_fd.h>
+
namespace android {
namespace uhid {
@@ -29,6 +31,7 @@
void onDeviceOpen();
void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
+ void onDeviceOutput(const std::vector<uint8_t>& data);
void onDeviceError();
private:
@@ -39,10 +42,10 @@
class Device {
public:
- static Device* open(int32_t id, const char* name, int32_t vid, int32_t pid,
- std::vector<uint8_t> descriptor, std::unique_ptr<DeviceCallback> callback);
+ static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid,
+ const std::vector<uint8_t>& descriptor,
+ std::unique_ptr<DeviceCallback> callback);
- Device(int32_t id, int fd, std::unique_ptr<DeviceCallback> callback);
~Device();
void sendReport(const std::vector<uint8_t>& report) const;
@@ -52,8 +55,9 @@
int handleEvents(int events);
private:
+ Device(int32_t id, android::base::unique_fd fd, std::unique_ptr<DeviceCallback> callback);
int32_t mId;
- int mFd;
+ android::base::unique_fd mFd;
std::unique_ptr<DeviceCallback> mDeviceCallback;
};
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 616d411..874604c 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -20,13 +20,16 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
-import android.os.MessageQueue;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.os.SomeArgs;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Map;
+
public class Device {
private static final String TAG = "HidDevice";
@@ -40,6 +43,7 @@
private final DeviceHandler mHandler;
// mFeatureReports is limited to 256 entries, because the report number is 8-bit
private final SparseArray<byte[]> mFeatureReports;
+ private final Map<ByteBuffer, byte[]> mOutputs;
private long mTimeToSend;
private final Object mCond = new Object();
@@ -55,12 +59,13 @@
private static native void nativeCloseDevice(long ptr);
public Device(int id, String name, int vid, int pid, byte[] descriptor,
- byte[] report, SparseArray<byte[]> featureReports) {
+ byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs) {
mId = id;
mThread = new HandlerThread("HidDeviceHandler");
mThread.start();
mHandler = new DeviceHandler(mThread.getLooper());
mFeatureReports = featureReports;
+ mOutputs = outputs;
SomeArgs args = SomeArgs.obtain();
args.argi1 = id;
args.argi2 = vid;
@@ -160,6 +165,11 @@
}
public void onDeviceGetReport(int requestId, int reportId) {
+ if (mFeatureReports == null) {
+ Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId
+ + ", but 'feature_reports' section is not found");
+ return;
+ }
byte[] report = mFeatureReports.get(reportId);
if (report == null) {
@@ -176,6 +186,29 @@
mHandler.sendMessageAtTime(msg, mTimeToSend);
}
+ // native callback
+ public void onDeviceOutput(byte[] data) {
+ if (mOutputs == null) {
+ Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found");
+ return;
+ }
+ byte[] response = mOutputs.get(ByteBuffer.wrap(data));
+ if (response == null) {
+ Log.i(TAG,
+ "Requested response for output " + Arrays.toString(data) + " is not found");
+ return;
+ }
+
+ Message msg;
+ msg = mHandler.obtainMessage(MSG_SEND_REPORT, response);
+
+ // Message is set to asynchronous so it won't be blocked by synchronization
+ // barrier during UHID_OPEN. This is necessary for drivers that do
+ // UHID_OUTPUT requests during probe, and expect a response right away.
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, mTimeToSend);
+ }
+
public void onDeviceError() {
Log.e(TAG, "Device error occurred, closing /dev/uhid");
Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index 746e372..62587a7 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -21,10 +21,13 @@
import android.util.Log;
import android.util.SparseArray;
-import java.io.InputStreamReader;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
public class Event {
private static final String TAG = "HidEvent";
@@ -41,6 +44,7 @@
private int mPid;
private byte[] mReport;
private SparseArray<byte[]> mFeatureReports;
+ private Map<ByteBuffer, byte[]> mOutputs;
private int mDuration;
public int getId() {
@@ -75,6 +79,10 @@
return mFeatureReports;
}
+ public Map<ByteBuffer, byte[]> getOutputs() {
+ return mOutputs;
+ }
+
public int getDuration() {
return mDuration;
}
@@ -88,6 +96,7 @@
+ ", pid=" + mPid
+ ", report=" + Arrays.toString(mReport)
+ ", feature_reports=" + mFeatureReports.toString()
+ + ", outputs=" + mOutputs.toString()
+ ", duration=" + mDuration
+ "}";
}
@@ -123,6 +132,10 @@
mEvent.mFeatureReports = reports;
}
+ public void setOutputs(Map<ByteBuffer, byte[]> outputs) {
+ mEvent.mOutputs = outputs;
+ }
+
public void setVid(int vid) {
mEvent.mVid = vid;
}
@@ -199,6 +212,9 @@
case "feature_reports":
eb.setFeatureReports(readFeatureReports());
break;
+ case "outputs":
+ eb.setOutputs(readOutputs());
+ break;
case "duration":
eb.setDuration(readInt());
break;
@@ -250,7 +266,7 @@
private SparseArray<byte[]> readFeatureReports()
throws IllegalStateException, IOException {
- SparseArray<byte[]> featureReports = new SparseArray();
+ SparseArray<byte[]> featureReports = new SparseArray<>();
try {
mReader.beginArray();
while (mReader.hasNext()) {
@@ -276,17 +292,60 @@
}
}
mReader.endObject();
- if (data != null)
+ if (data != null) {
featureReports.put(id, data);
+ }
}
mReader.endArray();
- } catch (IllegalStateException|NumberFormatException e) {
+ } catch (IllegalStateException | NumberFormatException e) {
consumeRemainingElements();
mReader.endArray();
throw new IllegalStateException("Encountered malformed data.", e);
- } finally {
- return featureReports;
}
+ return featureReports;
+ }
+
+ private Map<ByteBuffer, byte[]> readOutputs()
+ throws IllegalStateException, IOException {
+ Map<ByteBuffer, byte[]> outputs = new HashMap<>();
+
+ try {
+ mReader.beginArray();
+ while (mReader.hasNext()) {
+ byte[] output = null;
+ byte[] response = null;
+ mReader.beginObject();
+ while (mReader.hasNext()) {
+ String name = mReader.nextName();
+ switch (name) {
+ case "description":
+ // Description is only used to keep track of the output responses
+ mReader.nextString();
+ break;
+ case "output":
+ output = readData();
+ break;
+ case "response":
+ response = readData();
+ break;
+ default:
+ consumeRemainingElements();
+ mReader.endObject();
+ throw new IllegalStateException("Invalid key in outputs: " + name);
+ }
+ }
+ mReader.endObject();
+ if (output != null) {
+ outputs.put(ByteBuffer.wrap(output), response);
+ }
+ }
+ mReader.endArray();
+ } catch (IllegalStateException | NumberFormatException e) {
+ consumeRemainingElements();
+ mReader.endArray();
+ throw new IllegalStateException("Encountered malformed data.", e);
+ }
+ return outputs;
}
private void consumeRemainingElements() throws IOException {
@@ -296,10 +355,6 @@
}
}
- private static void error(String msg) {
- error(msg, null);
- }
-
private static void error(String msg, Exception e) {
System.out.println(msg);
Log.e(TAG, msg);
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 54ac1b0..0ee2cc4 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -16,22 +16,17 @@
package com.android.commands.hid;
-import android.util.JsonReader;
-import android.util.JsonToken;
import android.util.Log;
import android.util.SparseArray;
import libcore.io.IoUtils;
-import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.IOException;
import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
public class Hid {
private static final String TAG = "HID";
@@ -119,7 +114,7 @@
}
int id = e.getId();
Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(),
- e.getDescriptor(), e.getReport(), e.getFeatureReports());
+ e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs());
mDevices.append(id, d);
}
diff --git a/cmds/idmap2/CPPLINT.cfg b/cmds/idmap2/CPPLINT.cfg
index 9dc6b4a..20ed43c 100644
--- a/cmds/idmap2/CPPLINT.cfg
+++ b/cmds/idmap2/CPPLINT.cfg
@@ -15,4 +15,4 @@
set noparent
linelength=100
root=..
-filter=+build/include_alpha
+filter=+build/include_alpha,-runtime/references,-build/c++
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 924efe5..ff45b14 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -40,7 +40,8 @@
void Write8(uint8_t value);
void Write16(uint16_t value);
void Write32(uint32_t value);
- void WriteString(const StringPiece& value);
+ void WriteString256(const StringPiece& value);
+ void WriteString(const std::string& value);
std::ostream& stream_;
};
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index 2639c6f..d4a0c322 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -18,19 +18,21 @@
* # idmap file format (current version)
*
* idmap := header data*
- * header := magic version target_crc overlay_crc target_path overlay_path
+ * header := magic version target_crc overlay_crc target_path overlay_path debug_info
* data := data_header data_block*
* data_header := target_package_id types_count
* data_block := target_type overlay_type entry_count entry_offset entry*
- * overlay_path := string
- * target_path := string
+ * overlay_path := string256
+ * target_path := string256
+ * debug_info := string
+ * string := <uint32_t> <uint8_t>+ '\0'+
* entry := <uint32_t>
* entry_count := <uint16_t>
* entry_offset := <uint16_t>
* magic := <uint32_t>
* overlay_crc := <uint32_t>
* overlay_type := <uint16_t>
- * string := <uint8_t>[256]
+ * string256 := <uint8_t>[256]
* target_crc := <uint32_t>
* target_package_id := <uint16_t>
* target_type := <uint16_t>
@@ -41,6 +43,7 @@
* # idmap file format changelog
* ## v1
* - Identical to idmap v1.
+ *
* ## v2
* - Entries are no longer separated by type into type specific data blocks.
* - Added overlay-indexed target resource id lookup capabilities.
@@ -53,6 +56,9 @@
* - Idmap can now encode a type and value to override a resource without needing a table entry.
* - A string pool block is included to retrieve the value of strings that do not have a resource
* table entry.
+ *
+ * ## v3
+ * - Add 'debug' block to IdmapHeader.
*/
#ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
@@ -116,6 +122,10 @@
return StringPiece(overlay_path_);
}
+ inline const std::string& GetDebugInfo() const {
+ return debug_info_;
+ }
+
// Invariant: anytime the idmap data encoding is changed, the idmap version
// field *must* be incremented. Because of this, we know that if the idmap
// header is up-to-date the entire file is up-to-date.
@@ -133,6 +143,7 @@
uint32_t overlay_crc_;
char target_path_[kIdmapStringLength];
char overlay_path_[kIdmapStringLength];
+ std::string debug_info_;
friend Idmap;
DISALLOW_COPY_AND_ASSIGN(IdmapHeader);
diff --git a/cmds/idmap2/include/idmap2/LogInfo.h b/cmds/idmap2/include/idmap2/LogInfo.h
new file mode 100644
index 0000000..a6237e6
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/LogInfo.h
@@ -0,0 +1,81 @@
+/*
+ * 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 IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_
+#define IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_
+
+#include <algorithm>
+#include <iterator>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#if __ANDROID__
+#include "android-base/logging.h"
+#else
+#include <iostream>
+#endif
+
+namespace android::idmap2 {
+
+class LogMessage {
+ public:
+ LogMessage() = default;
+
+ template <typename T>
+ LogMessage& operator<<(const T& value) {
+ stream_ << value;
+ return *this;
+ }
+
+ std::string GetString() const {
+ return stream_.str();
+ }
+
+ private:
+ std::stringstream stream_;
+};
+
+class LogInfo {
+ public:
+ LogInfo() = default;
+
+ inline void Info(const LogMessage& msg) {
+ lines_.push_back("I " + msg.GetString());
+ }
+
+ inline void Warning(const LogMessage& msg) {
+#ifdef __ANDROID__
+ LOG(WARNING) << msg.GetString();
+#else
+ std::cerr << "W " << msg.GetString() << std::endl;
+#endif
+ lines_.push_back("W " + msg.GetString());
+ }
+
+ inline std::string GetString() const {
+ std::ostringstream stream;
+ std::copy(lines_.begin(), lines_.end(), std::ostream_iterator<std::string>(stream, "\n"));
+ return stream.str();
+ }
+
+ private:
+ std::vector<std::string> lines_;
+};
+
+} // namespace android::idmap2
+
+#endif // IDMAP2_INCLUDE_IDMAP2_LOGINFO_H_
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index 76475ab..92c1864 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -44,7 +44,7 @@
void print(uint8_t value, const char* fmt, ...);
void print(uint16_t value, const char* fmt, ...);
void print(uint32_t value, const char* fmt, ...);
- void print(const std::string& value, const char* fmt, ...);
+ void print(const std::string& value, size_t encoded_size, const char* fmt, ...);
void print_raw(uint32_t length, const char* fmt, ...);
std::ostream& stream_;
diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h
index c3e1ef0..86dfab2 100644
--- a/cmds/idmap2/include/idmap2/ResourceMapping.h
+++ b/cmds/idmap2/include/idmap2/ResourceMapping.h
@@ -22,6 +22,7 @@
#include <utility>
#include "androidfw/ApkAssets.h"
+#include "idmap2/LogInfo.h"
#include "idmap2/Policies.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
@@ -50,7 +51,7 @@
const ApkAssets& overlay_apk_assets,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
- bool enforce_overlayable);
+ bool enforce_overlayable, LogInfo& log_info);
// Retrieves the mapping of target resource id to overlay value.
inline TargetResourceMap GetTargetToOverlayMap() const {
@@ -100,7 +101,8 @@
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
size_t string_pool_offset,
- const XmlParser& overlay_parser);
+ const XmlParser& overlay_parser,
+ LogInfo& log_info);
// Generates a ResourceMapping that maps target resources to overlay resources by name. To overlay
// a target resource, a resource must exist in the overlay with the same type and entry name as
@@ -115,7 +117,7 @@
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
const OverlayManifestInfo& overlay_info,
- const PolicyBitmask& fulfilled_policies);
+ const PolicyBitmask& fulfilled_policies, LogInfo& log_info);
TargetResourceMap target_map_;
std::multimap<ResourceId, ResourceId> overlay_map_;
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 3b0940a..362dcb3 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -42,13 +42,21 @@
stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
}
-void BinaryStreamVisitor::WriteString(const StringPiece& value) {
+void BinaryStreamVisitor::WriteString256(const StringPiece& value) {
char buf[kIdmapStringLength];
memset(buf, 0, sizeof(buf));
memcpy(buf, value.data(), std::min(value.size(), sizeof(buf)));
stream_.write(buf, sizeof(buf));
}
+void BinaryStreamVisitor::WriteString(const std::string& value) {
+ // pad with null to nearest word boundary; include at least one terminating null
+ size_t padding_size = 4 - (value.size() % 4);
+ Write32(value.size() + padding_size);
+ stream_.write(value.c_str(), value.size());
+ stream_.write("\0\0\0\0", padding_size);
+}
+
void BinaryStreamVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
// nothing to do
}
@@ -58,8 +66,9 @@
Write32(header.GetVersion());
Write32(header.GetTargetCrc());
Write32(header.GetOverlayCrc());
- WriteString(header.GetTargetPath());
- WriteString(header.GetOverlayPath());
+ WriteString256(header.GetTargetPath());
+ WriteString256(header.GetOverlayPath());
+ WriteString(header.GetDebugInfo());
}
void BinaryStreamVisitor::visit(const IdmapData& data) {
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 5cb91d7..7f2cd959 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -70,7 +70,7 @@
}
// a string is encoded as a kIdmapStringLength char array; the array is always null-terminated
-bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) {
+bool WARN_UNUSED ReadString256(std::istream& stream, char out[kIdmapStringLength]) {
char buf[kIdmapStringLength];
memset(buf, 0, sizeof(buf));
if (!stream.read(buf, sizeof(buf))) {
@@ -83,6 +83,23 @@
return true;
}
+Result<std::string> ReadString(std::istream& stream) {
+ uint32_t size;
+ if (!Read32(stream, &size)) {
+ return Error("failed to read string size");
+ }
+ if (size == 0) {
+ return std::string("");
+ }
+ std::string buf(size, '\0');
+ if (!stream.read(buf.data(), size)) {
+ return Error("failed to read string of size %u", size);
+ }
+ // buf is guaranteed to be null terminated (with enough nulls to end on a word boundary)
+ buf.resize(strlen(buf.c_str()));
+ return buf;
+}
+
Result<uint32_t> GetCrc(const ZipFile& zip) {
const Result<uint32_t> a = zip.Crc("resources.arsc");
const Result<uint32_t> b = zip.Crc("AndroidManifest.xml");
@@ -98,11 +115,17 @@
if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) ||
!Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) ||
- !ReadString(stream, idmap_header->target_path_) ||
- !ReadString(stream, idmap_header->overlay_path_)) {
+ !ReadString256(stream, idmap_header->target_path_) ||
+ !ReadString256(stream, idmap_header->overlay_path_)) {
return nullptr;
}
+ auto debug_str = ReadString(stream);
+ if (!debug_str) {
+ return nullptr;
+ }
+ idmap_header->debug_info_ = std::move(*debug_str);
+
return std::move(idmap_header);
}
@@ -307,17 +330,15 @@
memset(header->overlay_path_, 0, sizeof(header->overlay_path_));
memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size());
- std::unique_ptr<Idmap> idmap(new Idmap());
- idmap->header_ = std::move(header);
-
auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path);
if (!overlay_info) {
return overlay_info.GetError();
}
+ LogInfo log_info;
auto resource_mapping =
ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *overlay_info,
- fulfilled_policies, enforce_overlayable);
+ fulfilled_policies, enforce_overlayable, log_info);
if (!resource_mapping) {
return resource_mapping.GetError();
}
@@ -327,7 +348,11 @@
return idmap_data.GetError();
}
+ std::unique_ptr<Idmap> idmap(new Idmap());
+ header->debug_info_ = log_info.GetString();
+ idmap->header_ = std::move(header);
idmap->data_.push_back(std::move(*idmap_data));
+
return {std::move(idmap)};
}
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index a662aa5..63ee8a6 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -16,6 +16,7 @@
#include "idmap2/PrettyPrintVisitor.h"
+#include <istream>
#include <string>
#include "android-base/macros.h"
@@ -28,17 +29,30 @@
#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
+#define TAB " "
+
void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
}
void PrettyPrintVisitor::visit(const IdmapHeader& header) {
- stream_ << "target apk path : " << header.GetTargetPath() << std::endl
- << "overlay apk path : " << header.GetOverlayPath() << std::endl;
+ stream_ << "Paths:" << std::endl
+ << TAB "target apk path : " << header.GetTargetPath() << std::endl
+ << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
+ const std::string& debug = header.GetDebugInfo();
+ if (!debug.empty()) {
+ std::istringstream debug_stream(debug);
+ std::string line;
+ stream_ << "Debug info:" << std::endl;
+ while (std::getline(debug_stream, line)) {
+ stream_ << TAB << line << std::endl;
+ }
+ }
target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
if (target_apk_) {
target_am_.SetApkAssets({target_apk_.get()});
}
+ stream_ << "Mapping:" << std::endl;
}
void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) {
@@ -51,7 +65,7 @@
const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset();
for (auto& target_entry : data.GetTargetEntries()) {
- stream_ << base::StringPrintf("0x%08x ->", target_entry.target_id);
+ stream_ << TAB << base::StringPrintf("0x%08x ->", target_entry.target_id);
if (target_entry.data_type != Res_value::TYPE_REFERENCE &&
target_entry.data_type != Res_value::TYPE_DYNAMIC_REFERENCE) {
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
index 13973d6..751c60c 100644
--- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -16,6 +16,7 @@
#include "idmap2/RawPrintVisitor.h"
+#include <algorithm>
#include <cstdarg>
#include <string>
@@ -27,6 +28,15 @@
using android::ApkAssets;
+namespace {
+
+size_t StringSizeWhenEncoded(const std::string& s) {
+ size_t null_bytes = 4 - (s.size() % 4);
+ return sizeof(uint32_t) + s.size() + null_bytes;
+}
+
+} // namespace
+
namespace android::idmap2 {
// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils
@@ -40,8 +50,9 @@
print(header.GetVersion(), "version");
print(header.GetTargetCrc(), "target crc");
print(header.GetOverlayCrc(), "overlay crc");
- print(header.GetTargetPath().to_string(), "target path");
- print(header.GetOverlayPath().to_string(), "overlay path");
+ print(header.GetTargetPath().to_string(), kIdmapStringLength, "target path");
+ print(header.GetOverlayPath().to_string(), kIdmapStringLength, "overlay path");
+ print("...", StringSizeWhenEncoded(header.GetDebugInfo()), "debug info");
target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
if (target_apk_) {
@@ -164,7 +175,7 @@
}
// NOLINTNEXTLINE(cert-dcl50-cpp)
-void RawPrintVisitor::print(const std::string& value, const char* fmt, ...) {
+void RawPrintVisitor::print(const std::string& value, size_t encoded_size, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
std::string comment;
@@ -174,7 +185,7 @@
stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value
<< std::endl;
- offset_ += kIdmapStringLength;
+ offset_ += encoded_size;
}
// NOLINTNEXTLINE(cert-dcl50-cpp)
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 651d20f..229628c 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -146,7 +146,8 @@
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
size_t string_pool_offset,
- const XmlParser& overlay_parser) {
+ const XmlParser& overlay_parser,
+ LogInfo& log_info) {
ResourceMapping resource_mapping;
auto root_it = overlay_parser.tree_iterator();
if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
@@ -181,7 +182,8 @@
ResourceId target_id =
target_am->GetResourceId(*target_resource, "", target_package->GetPackageName());
if (target_id == 0U) {
- LOG(WARNING) << "failed to find resource \"" << *target_resource << "\" in target resources";
+ log_info.Warning(LogMessage() << "failed to find resource \"" << *target_resource
+ << "\" in target resources");
continue;
}
@@ -196,7 +198,7 @@
overlay_resource->dataType == Res_value::TYPE_DYNAMIC_REFERENCE)
? overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data)
: false;
-
+
if (rewrite_overlay_reference) {
overlay_resource->dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
}
@@ -239,7 +241,8 @@
const LoadedPackage* target_package,
const LoadedPackage* overlay_package,
const OverlayManifestInfo& overlay_info,
- const PolicyBitmask& fulfilled_policies) {
+ const PolicyBitmask& fulfilled_policies,
+ LogInfo& log_info) {
std::set<ResourceId> remove_ids;
for (const auto& target_map : target_map_) {
const ResourceId target_resid = target_map.first;
@@ -256,9 +259,9 @@
name = StringPrintf("0x%08x", target_resid);
}
- LOG(WARNING) << "overlay \"" << overlay_package->GetPackageName()
- << "\" is not allowed to overlay resource \"" << *name
- << "\" in target: " << success.GetErrorMessage();
+ log_info.Warning(LogMessage() << "overlay \"" << overlay_package->GetPackageName()
+ << "\" is not allowed to overlay resource \"" << *name
+ << "\" in target: " << success.GetErrorMessage());
remove_ids.insert(target_resid);
}
@@ -272,7 +275,15 @@
const ApkAssets& overlay_apk_assets,
const OverlayManifestInfo& overlay_info,
const PolicyBitmask& fulfilled_policies,
- bool enforce_overlayable) {
+ bool enforce_overlayable,
+ LogInfo& log_info) {
+ if (enforce_overlayable) {
+ log_info.Info(LogMessage() << "fulfilled_policies="
+ << ConcatPolicies(BitmaskToPolicies(fulfilled_policies))
+ << " enforce_overlayable="
+ << (enforce_overlayable ? "true" : "false"));
+ }
+
AssetManager2 target_asset_manager;
if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true /* invalidate_caches */,
false /* filter_incompatible_configs*/)) {
@@ -333,7 +344,7 @@
string_pool_offset = overlay_arsc->GetStringPool()->size();
resource_mapping = CreateResourceMapping(&target_asset_manager, target_pkg, overlay_pkg,
- string_pool_offset, *(*parser));
+ string_pool_offset, *(*parser), log_info);
} else {
// If no file is specified using android:resourcesMap, it is assumed that the overlay only
// defines resources intended to override target resources of the same type and name.
@@ -349,7 +360,7 @@
// Filter out resources the overlay is not allowed to override.
(*resource_mapping)
.FilterOverlayableResources(&target_asset_manager, target_pkg, overlay_pkg, overlay_info,
- fulfilled_policies);
+ fulfilled_policies, log_info);
}
resource_mapping->target_package_id_ = target_pkg->GetPackageId();
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index cd816dd..4bc6255 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -30,6 +30,7 @@
#include "idmap2/BinaryStreamVisitor.h"
#include "idmap2/CommandLineOptions.h"
#include "idmap2/Idmap.h"
+#include "idmap2/LogInfo.h"
using android::Res_value;
using ::testing::IsNull;
@@ -57,11 +58,12 @@
std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
ASSERT_THAT(header, NotNull());
ASSERT_EQ(header->GetMagic(), 0x504d4449U);
- ASSERT_EQ(header->GetVersion(), 0x02U);
+ ASSERT_EQ(header->GetVersion(), 0x03U);
ASSERT_EQ(header->GetTargetCrc(), 0x1234U);
ASSERT_EQ(header->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(header->GetTargetPath().to_string(), "targetX.apk");
ASSERT_EQ(header->GetOverlayPath().to_string(), "overlayX.apk");
+ ASSERT_EQ(header->GetDebugInfo(), "debug");
}
TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) {
@@ -76,7 +78,7 @@
}
TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
- const size_t offset = 0x210;
+ const size_t offset = 0x21c;
std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
idmap_raw_data_len - offset);
std::istringstream stream(raw);
@@ -88,7 +90,7 @@
}
TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
- const size_t offset = 0x210;
+ const size_t offset = 0x21c;
std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
idmap_raw_data_len - offset);
std::istringstream stream(raw);
@@ -122,7 +124,7 @@
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
- ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x02U);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x03U);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U);
ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "targetX.apk");
@@ -174,7 +176,7 @@
ASSERT_THAT(idmap->GetHeader(), NotNull());
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
- ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x02U);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x03U);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x76a20829);
ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0xc054fb26);
ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path);
@@ -197,8 +199,9 @@
return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
}
+ LogInfo log_info;
auto mapping = ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info,
- fulfilled_policies, enforce_overlayable);
+ fulfilled_policies, enforce_overlayable, log_info);
if (!mapping) {
return mapping.GetError();
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index d387880..b22fdaf 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -16,6 +16,7 @@
#include <cstdio> // fclose
#include <memory>
+#include <regex>
#include <sstream>
#include <string>
@@ -29,7 +30,16 @@
namespace android::idmap2 {
+#define ASSERT_CONTAINS_REGEX(pattern, str) \
+ do { \
+ ASSERT_TRUE(std::regex_search(str, std::regex(pattern))) \
+ << "pattern '" << pattern << "' not found in\n--------\n" \
+ << str << "--------"; \
+ } while (0)
+
TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
+ fclose(stderr); // silence expected warnings
+
const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
ASSERT_THAT(target_apk, NotNull());
@@ -46,22 +56,24 @@
RawPrintVisitor visitor(stream);
(*idmap)->accept(&visitor);
- ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000004: 00000002 version\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000008: 76a20829 target crc\n"), std::string::npos);
- ASSERT_NE(stream.str().find("0000000c: c054fb26 overlay crc\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000210: 7f target package id\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000211: 7f overlay package id\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000212: 00000004 target entry count\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000216: 00000004 overlay entry count\n"), std::string::npos);
- ASSERT_NE(stream.str().find("0000021a: 00000008 string pool index offset\n"), std::string::npos);
- ASSERT_NE(stream.str().find("0000021e: 000000b4 string pool byte length\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000222: 7f010000 target id: integer/int1\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000226: 07 type: reference (dynamic)\n"),
- std::string::npos);
- ASSERT_NE(stream.str().find("00000227: 7f010000 value: integer/int1\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000246: 7f010000 overlay id: integer/int1\n"), std::string::npos);
- ASSERT_NE(stream.str().find("0000024a: 7f010000 target id: integer/int1\n"), std::string::npos);
+#define ADDRESS "[0-9a-f]{8}: "
+ ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000003 version\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "76a20829 target crc\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "c054fb26 overlay crc\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000004 overlay entry count\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "00000008 string pool index offset\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "000000b4 string pool byte length\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS " 07 type: reference \\(dynamic\\)\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 value: integer/int1\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1\n", stream.str());
+ ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1\n", stream.str());
+#undef ADDRESS
}
TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
@@ -78,21 +90,21 @@
(*idmap)->accept(&visitor);
ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000004: 00000002 version\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000004: 00000003 version\n"), std::string::npos);
ASSERT_NE(stream.str().find("00000008: 00001234 target crc\n"), std::string::npos);
ASSERT_NE(stream.str().find("0000000c: 00005678 overlay crc\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000210: 7f target package id\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000211: 7f overlay package id\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000212: 00000003 target entry count\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000216: 00000003 overlay entry count\n"), std::string::npos);
- ASSERT_NE(stream.str().find("0000021a: 00000000 string pool index offset\n"), std::string::npos);
- ASSERT_NE(stream.str().find("0000021e: 00000000 string pool byte length\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000222: 7f020000 target id\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000226: 01 type: reference\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000227: 7f020000 value\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000021c: 7f target package id\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000021d: 7f overlay package id\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000021e: 00000003 target entry count\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000222: 00000003 overlay entry count\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000226: 00000000 string pool index offset\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000022a: 00000000 string pool byte length\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000022e: 7f020000 target id\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000232: 01 type: reference\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000233: 7f020000 value\n"), std::string::npos);
- ASSERT_NE(stream.str().find("0000023d: 7f020000 overlay id\n"), std::string::npos);
- ASSERT_NE(stream.str().find("00000241: 7f020000 target id\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000249: 7f020000 overlay id\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000024d: 7f020000 target id\n"), std::string::npos);
}
} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 64304f6..39c4937 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -25,6 +25,7 @@
#include "TestHelpers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "idmap2/LogInfo.h"
#include "idmap2/ResourceMapping.h"
using android::Res_value;
@@ -55,8 +56,9 @@
return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
}
+ LogInfo log_info;
return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info, fulfilled_policies,
- enforce_overlayable);
+ enforce_overlayable, log_info);
}
Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path,
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index 8868b53..e899589 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -30,7 +30,7 @@
0x49, 0x44, 0x4d, 0x50,
// 0x4: version
- 0x02, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00,
// 0x8: target crc
0x34, 0x12, 0x00, 0x00,
@@ -74,64 +74,71 @@
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x210: debug string
+ // string length, including terminating null
+ 0x08, 0x00, 0x00, 0x00,
+
+ // string contents "debug\0\0\0" (padded to word alignment)
+ 0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00,
+
// DATA HEADER
- // 0x210: target_package_id
+ // 0x21c: target_package_id
0x7f,
- // 0x211: overlay_package_id
+ // 0x21d: overlay_package_id
0x7f,
- // 0x212: target_entry_count
+ // 0x21e: target_entry_count
0x03, 0x00, 0x00, 0x00,
- // 0x216: overlay_entry_count
+ // 0x222: overlay_entry_count
0x03, 0x00, 0x00, 0x00,
- // 0x21a: string_pool_offset
+ // 0x226: string_pool_offset
0x00, 0x00, 0x00, 0x00,
- // 0x21e: string_pool_byte_length
+ // 0x22a: string_pool_byte_length
0x00, 0x00, 0x00, 0x00,
// TARGET ENTRIES
- // 0x222: 0x7f020000
+ // 0x22e: 0x7f020000
0x00, 0x00, 0x02, 0x7f,
- // 0x226: TYPE_REFERENCE
+ // 0x232: TYPE_REFERENCE
0x01,
- // 0x227: 0x7f020000
+ // 0x233: 0x7f020000
0x00, 0x00, 0x02, 0x7f,
- // 0x22b: 0x7f030000
+ // 0x237: 0x7f030000
0x00, 0x00, 0x03, 0x7f,
- // 0x22f: TYPE_REFERENCE
+ // 0x23b: TYPE_REFERENCE
0x01,
- // 0x230: 0x7f030000
+ // 0x23c: 0x7f030000
0x00, 0x00, 0x03, 0x7f,
- // 0x234: 0x7f030002
+ // 0x240: 0x7f030002
0x02, 0x00, 0x03, 0x7f,
- // 0x238: TYPE_REFERENCE
+ // 0x244: TYPE_REFERENCE
0x01,
- // 0x239: 0x7f030001
+ // 0x245: 0x7f030001
0x01, 0x00, 0x03, 0x7f,
// OVERLAY ENTRIES
- // 0x23d: 0x7f020000 -> 0x7f020000
+ // 0x249: 0x7f020000 -> 0x7f020000
0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f,
- // 0x245: 0x7f030000 -> 0x7f030000
+ // 0x251: 0x7f030000 -> 0x7f030000
0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f,
- // 0x24d: 0x7f030001 -> 0x7f030002
+ // 0x259: 0x7f030001 -> 0x7f030002
0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f};
-const unsigned int idmap_raw_data_len = 0x255;
+const unsigned int idmap_raw_data_len = 0x261;
std::string GetTestDataPath();
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index 6c3d197..eb2b98a 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -375,7 +375,7 @@
if (destination == DEST_STDOUT) {
// Call into the service
sp<StatusListener> listener(new StatusListener());
- status = service->reportIncidentToStream(args, listener, writeEnd);
+ status = service->reportIncidentToStream(args, listener, std::move(writeEnd));
if (!status.isOk()) {
fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
@@ -388,7 +388,7 @@
} else if (destination == DEST_DUMPSTATE) {
// Call into the service
sp<StatusListener> listener(new StatusListener());
- status = service->reportIncidentToDumpstate(writeEnd, listener);
+ status = service->reportIncidentToDumpstate(std::move(writeEnd), listener);
if (!status.isOk()) {
fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
return 1;
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 999936b..cfd77c2 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -279,7 +279,7 @@
Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener,
- const unique_fd& stream) {
+ unique_fd stream) {
IncidentReportArgs argsCopy(args);
// Streaming reports can not also be broadcast.
@@ -306,7 +306,7 @@
return Status::ok();
}
-Status IncidentService::reportIncidentToDumpstate(const unique_fd& stream,
+Status IncidentService::reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener) {
uid_t caller = IPCThreadState::self()->getCallingUid();
if (caller != AID_ROOT && caller != AID_SHELL) {
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index fb013d0..b2c7f23 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -121,9 +121,9 @@
virtual Status reportIncidentToStream(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener,
- const unique_fd& stream);
+ unique_fd stream);
- virtual Status reportIncidentToDumpstate(const unique_fd& stream,
+ virtual Status reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener);
virtual Status systemRunning();
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index 7e7c642..9963533 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -666,7 +666,7 @@
clock_gettime(CLOCK_REALTIME, &spec);
timestampNs = int64_t(spec.tv_sec) * 1000 + spec.tv_nsec;
} while (file_exists_locked(timestampNs));
- return timestampNs;
+ return (timestampNs >= 0)? timestampNs : -timestampNs;
}
/**
diff --git a/cmds/media/Android.bp b/cmds/media/Android.bp
deleted file mode 100644
index 7879c53..0000000
--- a/cmds/media/Android.bp
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2013 The Android Open Source Project
-//
-
-java_binary {
- name: "media",
- wrapper: "media",
- srcs: ["**/*.java"],
-}
diff --git a/cmds/media/MODULE_LICENSE_APACHE2 b/cmds/media/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/cmds/media/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/cmds/media/NOTICE b/cmds/media/NOTICE
deleted file mode 100644
index c5b1efa..0000000
--- a/cmds/media/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
- Copyright (c) 2005-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.
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
diff --git a/cmds/media/media b/cmds/media/media
deleted file mode 100755
index 00c3915..0000000
--- a/cmds/media/media
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/system/bin/sh
-export CLASSPATH=/system/framework/media.jar
-exec app_process /system/bin com.android.commands.media.Media "$@"
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 17427a2..887d17c 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -146,6 +146,7 @@
"libprotoutil",
"libservices",
"libstatslog",
+ "libstatsmetadata",
"libstatssocket",
"libsysutils",
"libtimestats_proto",
@@ -153,6 +154,63 @@
],
}
+// ================
+// libstatsmetadata
+// ================
+
+genrule {
+ name: "atoms_info.h",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --atomsInfoHeader $(genDir)/atoms_info.h",
+ out: [
+ "atoms_info.h",
+ ],
+}
+
+genrule {
+ name: "atoms_info.cpp",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --atomsInfoCpp $(genDir)/atoms_info.cpp",
+ out: [
+ "atoms_info.cpp",
+ ],
+}
+
+cc_library_shared {
+ name: "libstatsmetadata",
+ host_supported: true,
+ generated_sources: [
+ "atoms_info.cpp",
+ ],
+ generated_headers: [
+ "atoms_info.h",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ export_generated_headers: [
+ "atoms_info.h",
+ ],
+ shared_libs: [
+ "libcutils",
+ "libstatslog",
+ ],
+ target: {
+ android: {
+ shared_libs: [
+ "libutils",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libutils",
+ ],
+ },
+ },
+}
+
+
// =========
// statsd
// =========
@@ -250,6 +308,7 @@
"tests/external/GpuStatsPuller_test.cpp",
"tests/external/IncidentReportArgs_test.cpp",
"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",
@@ -350,7 +409,7 @@
// ==== java proto device library (for test only) ==============================
java_library {
name: "statsdprotolite",
- sdk_version: "core_platform",
+ sdk_version: "core_current",
proto: {
type: "lite",
include_dirs: ["external/protobuf/src"],
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 84a0607..4385964 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -18,8 +18,8 @@
#include "Log.h"
#include "FieldValue.h"
#include "HashableDimensionKey.h"
+#include "atoms_info.h"
#include "math.h"
-#include "statslog.h"
namespace android {
namespace os {
@@ -435,6 +435,25 @@
return eq;
}
+bool subsetDimensions(const std::vector<Matcher>& dimension_a,
+ const std::vector<Matcher>& dimension_b) {
+ if (dimension_a.size() > dimension_b.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < dimension_a.size(); ++i) {
+ bool found = false;
+ for (size_t j = 0; j < dimension_b.size(); ++j) {
+ if (dimension_a[i] == dimension_b[j]) {
+ found = true;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool HasPositionANY(const FieldMatcher& matcher) {
if (matcher.has_position() && matcher.position() == Position::ANY) {
return true;
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 0e033e0..6fc1e23 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -396,6 +396,10 @@
bool equalDimensions(const std::vector<Matcher>& dimension_a,
const std::vector<Matcher>& dimension_b);
+
+// Returns true if dimension_a is a subset of dimension_b.
+bool subsetDimensions(const std::vector<Matcher>& dimension_a,
+ const std::vector<Matcher>& dimension_b);
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 98d41c2..3481814 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -27,6 +27,7 @@
#include <utils/SystemClock.h>
#include "android-base/stringprintf.h"
+#include "atoms_info.h"
#include "external/StatsPullerManager.h"
#include "guardrail/StatsdStats.h"
#include "metrics/CountMetricProducer.h"
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 3c5ad42..bb3a094 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -272,7 +272,7 @@
}
return NO_ERROR;
}
- default: { return BnStatsManager::onTransact(code, data, reply, flags); }
+ default: { return BnStatsd::onTransact(code, data, reply, flags); }
}
}
@@ -862,13 +862,13 @@
int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10);
int options = 0;
if (args[3] == "1") {
- options = options | IStatsManager::FLAG_REQUIRE_STAGING;
+ options = options | IStatsd::FLAG_REQUIRE_STAGING;
}
if (args[4] == "1") {
- options = options | IStatsManager::FLAG_ROLLBACK_ENABLED;
+ options = options | IStatsd::FLAG_ROLLBACK_ENABLED;
}
if (args[5] == "1") {
- options = options | IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
+ options = options | IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
}
int32_t state = atoi(args[6].c_str());
vector<int64_t> experimentIds;
@@ -1320,6 +1320,13 @@
return Status::ok();
}
+Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) {
+ ENFORCE_UID(AID_SYSTEM);
+ VLOG("StatsService::unregisterPullAtomCallback called.");
+ mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
+ return Status::ok();
+}
+
Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn,
const int64_t trainVersionCodeIn,
const int options,
@@ -1406,9 +1413,9 @@
StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds);
userid_t userId = multiuser_get_user_id(uid);
- bool requiresStaging = options & IStatsManager::FLAG_REQUIRE_STAGING;
- bool rollbackEnabled = options & IStatsManager::FLAG_ROLLBACK_ENABLED;
- bool requiresLowLatencyMonitor = options & IStatsManager::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
+ bool requiresStaging = options & IStatsd::FLAG_REQUIRE_STAGING;
+ bool rollbackEnabled = options & IStatsd::FLAG_ROLLBACK_ENABLED;
+ bool requiresLowLatencyMonitor = options & IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled,
requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId);
mProcessor->OnLogEvent(&event);
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 50b1014..de55ca9 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -29,9 +29,9 @@
#include <android/frameworks/stats/1.0/IStats.h>
#include <android/frameworks/stats/1.0/types.h>
-#include <android/os/BnStatsManager.h>
+#include <android/os/BnStatsd.h>
#include <android/os/IStatsCompanionService.h>
-#include <android/os/IStatsManager.h>
+#include <android/os/IStatsd.h>
#include <binder/IResultReceiver.h>
#include <binder/ParcelFileDescriptor.h>
#include <utils/Looper.h>
@@ -52,7 +52,7 @@
using android::hardware::Return;
-class StatsService : public BnStatsManager,
+class StatsService : public BnStatsd,
public IStats,
public IBinder::DeathRecipient {
public:
@@ -199,6 +199,11 @@
virtual Status unregisterPullerCallback(int32_t atomTag, const String16& packageName) override;
/**
+ * Binder call to unregister any existing callback for the given uid and atom.
+ */
+ virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override;
+
+ /**
* Binder call to log BinaryPushStateChanged atom.
*/
virtual Status sendBinaryPushStateChangedAtom(
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f1ea4c9..2270974 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -113,8 +113,8 @@
WakeupAlarmOccurred wakeup_alarm_occurred = 35;
KernelWakeupReported kernel_wakeup_reported = 36;
WifiLockStateChanged wifi_lock_state_changed = 37 [(log_from_module) = "wifi"];
- WifiSignalStrengthChanged wifi_signal_strength_changed = 38;
- WifiScanStateChanged wifi_scan_state_changed = 39;
+ WifiSignalStrengthChanged wifi_signal_strength_changed = 38 [(log_from_module) = "wifi"];
+ WifiScanStateChanged wifi_scan_state_changed = 39 [(log_from_module) = "wifi"];
PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
SettingChanged setting_changed = 41;
ActivityForegroundStateChanged activity_foreground_state_changed = 42;
@@ -128,7 +128,7 @@
AppStartFullyDrawn app_start_fully_drawn = 50;
LmkKillOccurred lmk_kill_occurred = 51;
PictureInPictureStateChanged picture_in_picture_state_changed = 52;
- WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53;
+ WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(log_from_module) = "wifi"];
LmkStateChanged lmk_state_changed = 54;
AppStartMemoryStateCaptured app_start_memory_state_captured = 55;
ShutdownSequenceReported shutdown_sequence_reported = 56;
@@ -356,7 +356,7 @@
}
// Pulled events will start at field 10000.
- // Next: 10067
+ // Next: 10068
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -424,6 +424,7 @@
ProcessMemorySnapshot process_memory_snapshot = 10064;
VmsClientStats vms_client_stats = 10065;
NotificationRemoteViews notification_remote_views = 10066;
+ DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -5994,6 +5995,14 @@
USER_GRANTED_ONE_TIME = 10;
// user ignored request by leaving the request screen without choosing any option
USER_IGNORED = 11;
+ // user granted the permission after being linked to settings
+ USER_GRANTED_IN_SETTINGS = 12;
+ // user denied the permission after being linked to settings
+ USER_DENIED_IN_SETTINGS = 13;
+ // user denied the permission with prejudice after being linked to settings
+ USER_DENIED_WITH_PREJUDICE_IN_SETTINGS = 14;
+ // permission was automatically revoked after one-time permission expired
+ AUTO_ONE_TIME_PERMISSION_REVOKED = 15;
}
// The result of the permission grant
optional Result result = 6;
@@ -6490,7 +6499,8 @@
/**
* State of a dangerous permission requested by a package
- */
+ * Pulled from: StatsCompanionService
+*/
message DangerousPermissionState {
// Name of the permission
optional string permission_name = 1;
@@ -7141,7 +7151,7 @@
// Event type of this event.
optional android.stats.textclassifier.EventType event_type = 2;
- // Name of the model that is involved in this event.
+ // Name of the annotator model that is involved in this event.
optional string model_name = 3;
// Type of widget that was involved in triggering this event.
@@ -7181,7 +7191,7 @@
// Event type of this event.
optional android.stats.textclassifier.EventType event_type = 2;
- // Name of the model that is involved in this event.
+ // Name of the annotator model that is involved in this event.
optional string model_name = 3;
// Type of widget that was involved in triggering this event.
@@ -7221,7 +7231,7 @@
// Event type of this event.
optional android.stats.textclassifier.EventType event_type = 2;
- // Name of the model that is involved in this event.
+ // Name of the actions model that is involved in this event.
optional string model_name = 3;
// Type of widget that was involved in triggering this event.
@@ -7241,6 +7251,9 @@
// Name of source package.
optional string package_name = 9;
+
+ // Name of the annotator model that is involved in this event.
+ optional string annotator_model_name = 10;
}
/**
@@ -7255,7 +7268,7 @@
// Event type of this event.
optional android.stats.textclassifier.EventType event_type = 2;
- // Name of the model that is involved in this event.
+ // Name of the language detection model that is involved in this event.
optional string model_name = 3;
// Type of widget that was involved in triggering this event.
@@ -7538,3 +7551,22 @@
optional int64 dropped_bytes = 9;
optional int64 dropped_packets = 10;
}
+
+/**
+ * State of a dangerous permission requested by a package - sampled
+ * Pulled from: StatsCompanionService.java with data obtained from PackageManager API
+*/
+message DangerousPermissionStateSampled {
+ // Name of the permission
+ optional string permission_name = 1;
+
+ // Uid of the package
+ optional int32 uid = 2 [(is_uid) = true];
+
+ // If the permission is granted to the uid
+ optional bool is_granted = 3;
+
+ // Permission flags as per android.content.pm.PermissionFlags
+ optional int32 permission_flags = 4;
+}
+
diff --git a/cmds/statsd/src/external/PowerStatsPuller.cpp b/cmds/statsd/src/external/PowerStatsPuller.cpp
index b142cac..dc69b78 100644
--- a/cmds/statsd/src/external/PowerStatsPuller.cpp
+++ b/cmds/statsd/src/external/PowerStatsPuller.cpp
@@ -22,6 +22,7 @@
#include <vector>
#include "PowerStatsPuller.h"
+#include "statslog.h"
#include "stats_log_util.h"
using android::hardware::hidl_vec;
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index f5b1e7f..0e6b677 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -35,8 +35,9 @@
namespace os {
namespace statsd {
-StatsCallbackPuller::StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& callback)
- : StatsPuller(tagId), mCallback(callback) {
+StatsCallbackPuller::StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& callback,
+ int64_t timeoutNs)
+ : StatsPuller(tagId), mCallback(callback), mTimeoutNs(timeoutNs) {
VLOG("StatsCallbackPuller created for tag %d", tagId);
}
@@ -64,10 +65,9 @@
{
lock_guard<mutex> lk(*cv_mutex);
for (const StatsEventParcel& parcel: output) {
- shared_ptr<LogEvent> event =
- make_shared<LogEvent>(const_cast<uint8_t*>(parcel.buffer.data()),
- parcel.buffer.size(),
- /*uid=*/ -1);
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(
+ const_cast<uint8_t*>(parcel.buffer.data()), parcel.buffer.size(),
+ /*uid=*/-1, /*useNewSchema=*/true);
sharedData->push_back(event);
}
*pullSuccess = success;
@@ -76,7 +76,8 @@
cv->notify_one();
});
- // Initiate the pull.
+ // Initiate the pull. This is a oneway call to a different process, except
+ // in unit tests. In process calls are not oneway.
Status status = mCallback->onPullAtom(mTagId, resultReceiver);
if (!status.isOk()) {
return false;
@@ -84,10 +85,8 @@
{
unique_lock<mutex> unique_lk(*cv_mutex);
- int64_t pullTimeoutNs =
- StatsPullerManager::kAllPullAtomInfo.at({.atomTag = mTagId}).pullTimeoutNs;
// Wait until the pull finishes, or until the pull timeout.
- cv->wait_for(unique_lk, chrono::nanoseconds(pullTimeoutNs),
+ cv->wait_for(unique_lk, chrono::nanoseconds(mTimeoutNs),
[pullFinish] { return *pullFinish; });
if (!*pullFinish) {
// Note: The parent stats puller will also note that there was a timeout and that the
@@ -96,7 +95,7 @@
return true;
} else {
// Only copy the data if we did not timeout and the pull was successful.
- if (pullSuccess) {
+ if (*pullSuccess) {
*data = std::move(*sharedData);
}
VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId);
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.h b/cmds/statsd/src/external/StatsCallbackPuller.h
index ce506c7..d943f9d 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.h
+++ b/cmds/statsd/src/external/StatsCallbackPuller.h
@@ -27,11 +27,17 @@
class StatsCallbackPuller : public StatsPuller {
public:
- explicit StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& callback);
+ explicit StatsCallbackPuller(int tagId, const sp<IPullAtomCallback>& callback,
+ int64_t timeoutNs);
private:
bool PullInternal(vector<std::shared_ptr<LogEvent> >* data) override;
const sp<IPullAtomCallback> mCallback;
+ const int64_t mTimeoutNs;
+
+ FRIEND_TEST(StatsCallbackPullerTest, PullFail);
+ FRIEND_TEST(StatsCallbackPullerTest, PullSuccess);
+ FRIEND_TEST(StatsCallbackPullerTest, PullTimeout);
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp
index 3c6bc2d..883bd28 100644
--- a/cmds/statsd/src/external/StatsPuller.cpp
+++ b/cmds/statsd/src/external/StatsPuller.cpp
@@ -47,6 +47,7 @@
if (mHasGoodData) {
(*data) = mCachedData;
StatsdStats::getInstance().notePullFromCache(mTagId);
+
}
return mHasGoodData;
}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 615af89..50896f8 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -284,12 +284,21 @@
// NotiifcationRemoteViews.
{{.atomTag = android::util::NOTIFICATION_REMOTE_VIEWS},
{.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
+ // PermissionStateSampled.
+ {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE_SAMPLED},
+ {.puller =
+ new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE_SAMPLED)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
}
bool StatsPullerManager::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
+ AutoMutex _l(mLock);
+ return PullLocked(tagId, data);
+}
+
+bool StatsPullerManager::PullLocked(int tagId, vector<shared_ptr<LogEvent>>* data) {
VLOG("Initiating pulling %d", tagId);
if (kAllPullAtomInfo.find({.atomTag = tagId}) != kAllPullAtomInfo.end()) {
@@ -418,7 +427,7 @@
for (const auto& pullInfo : needToPull) {
vector<shared_ptr<LogEvent>> data;
- bool pullSuccess = Pull(pullInfo.first, &data);
+ bool pullSuccess = PullLocked(pullInfo.first, &data);
if (pullSuccess) {
StatsdStats::getInstance().notePullDelay(
pullInfo.first, getElapsedRealtimeNs() - elapsedTimeNs);
@@ -500,10 +509,11 @@
VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
// TODO: linkToDeath with the callback so that we can remove it and delete the puller.
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
- kAllPullAtomInfo[{.atomTag = atomTag}] = {.additiveFields = additiveFields,
- .coolDownNs = coolDownNs,
- .puller = new StatsCallbackPuller(atomTag, callback),
- .pullTimeoutNs = timeoutNs,
+ kAllPullAtomInfo[{.atomTag = atomTag}] = {
+ .additiveFields = additiveFields,
+ .coolDownNs = coolDownNs,
+ .puller = new StatsCallbackPuller(atomTag, callback, timeoutNs),
+ .pullTimeoutNs = timeoutNs,
};
}
@@ -517,6 +527,12 @@
kAllPullAtomInfo.erase({.atomTag = atomTag});
}
+void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
+ AutoMutex _l(mLock);
+ StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false);
+ kAllPullAtomInfo.erase({.atomTag = atomTag});
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index 1bd9f92..349fd47 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -125,6 +125,8 @@
void UnregisterPullerCallback(int32_t atomTag);
+ void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
+
static std::map<PullerKey, PullAtomInfo> kAllPullAtomInfo;
private:
@@ -139,6 +141,8 @@
// mapping from simple matcher tagId to receivers
std::map<int, std::list<ReceiverInfo>> mReceivers;
+ bool PullLocked(int tagId, vector<std::shared_ptr<LogEvent>>* data);
+
// locks for data receiver and StatsCompanionService changes
Mutex mLock;
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
index 53fa630..031c437 100644
--- a/cmds/statsd/src/external/puller_util.cpp
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -18,8 +18,8 @@
#include "Log.h"
#include "StatsPullerManager.h"
+#include "atoms_info.h"
#include "puller_util.h"
-#include "statslog.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 23d2ace..692d91e 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -16,7 +16,7 @@
#pragma once
#include "config/ConfigKey.h"
-#include "statslog.h"
+#include "atoms_info.h"
#include <gtest/gtest_prod.h>
#include <log/log_time.h>
@@ -181,6 +181,8 @@
static const int64_t kInt64Max = 0x7fffffffffffffffLL;
+ static const int32_t kMaxLoggedBucketDropEvents = 10;
+
/**
* Report a new config has been received and report the static stats about the config.
*
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 67022a0..36f4623 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -52,6 +52,17 @@
#endif
}
+LogEvent::LogEvent(uint8_t* msg, uint32_t len, uint32_t uid, bool useNewSchema)
+ : mBuf(msg), mRemainingLen(len), mLogdTimestampNs(time(nullptr)), mLogUid(uid) {
+ if (useNewSchema) {
+ initNew();
+ } else {
+ mContext = create_android_log_parser((char*)msg, len);
+ init(mContext);
+ if (mContext) android_log_destroy(&mContext); // set mContext to NULL
+ }
+}
+
LogEvent::LogEvent(const LogEvent& event) {
mTagId = event.mTagId;
mLogUid = event.mLogUid;
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 1ff95f7..596d623 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -75,6 +75,11 @@
explicit LogEvent(uint8_t* msg, uint32_t len, uint32_t uid);
/**
+ * Temp constructor to use for pulled atoms until we flip the socket schema.
+ */
+ explicit LogEvent(uint8_t* msg, uint32_t len, uint32_t uid, bool useNewSchema);
+
+ /**
* Creates LogEvent from StatsLogEventWrapper.
*/
static void createLogEvents(const StatsLogEventWrapper& statsLogEventWrapper,
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 64344e8..4ab6ec3 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -52,8 +52,13 @@
// for GaugeMetricDataWrapper
const int FIELD_ID_DATA = 1;
const int FIELD_ID_SKIPPED = 2;
+// for SkippedBuckets
const int FIELD_ID_SKIPPED_START_MILLIS = 3;
const int FIELD_ID_SKIPPED_END_MILLIS = 4;
+const int FIELD_ID_SKIPPED_DROP_EVENT = 5;
+// for DumpEvent Proto
+const int FIELD_ID_BUCKET_DROP_REASON = 1;
+const int FIELD_ID_DROP_TIME = 2;
// for GaugeMetricData
const int FIELD_ID_DIMENSION_IN_WHAT = 1;
const int FIELD_ID_BUCKET_INFO = 3;
@@ -193,7 +198,7 @@
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
- if (mPastBuckets.empty()) {
+ if (mPastBuckets.empty() && mSkippedBuckets.empty()) {
return;
}
@@ -212,13 +217,21 @@
uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
- for (const auto& pair : mSkippedBuckets) {
+ for (const auto& skippedBucket : mSkippedBuckets) {
uint64_t wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS,
- (long long)(NanoToMillis(pair.first)));
+ (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs)));
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS,
- (long long)(NanoToMillis(pair.second)));
+ (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs)));
+
+ for (const auto& dropEvent : skippedBucket.dropEvents) {
+ uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+ FIELD_ID_SKIPPED_DROP_EVENT);
+ protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason);
+ protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, (long long) (NanoToMillis(dropEvent.dropTimeNs)));
+ protoOutput->end(dropEventToken);
+ }
protoOutput->end(wrapperToken);
}
@@ -545,7 +558,10 @@
info.mBucketEndNs = fullBucketEndTimeNs;
}
- if (info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs) {
+ // Add bucket to mPastBuckets if bucket is large enough.
+ // Otherwise, drop the bucket data and add bucket metadata to mSkippedBuckets.
+ bool isBucketLargeEnough = info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs;
+ if (isBucketLargeEnough) {
for (const auto& slice : *mCurrentSlicedBucket) {
info.mGaugeAtoms = slice.second;
auto& bucketList = mPastBuckets[slice.first];
@@ -554,7 +570,13 @@
slice.first.toString().c_str());
}
} else {
- mSkippedBuckets.emplace_back(info.mBucketStartNs, info.mBucketEndNs);
+ mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
+ mCurrentSkippedBucket.bucketEndTimeNs = eventTimeNs;
+ if (!maxDropEventsReached()) {
+ mCurrentSkippedBucket.dropEvents.emplace_back(
+ buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL));
+ }
+ mSkippedBuckets.emplace_back(mCurrentSkippedBucket);
}
// If we have anomaly trackers, we need to update the partial bucket values.
@@ -573,6 +595,7 @@
StatsdStats::getInstance().noteBucketCount(mMetricId);
mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
+ mCurrentSkippedBucket.reset();
}
size_t GaugeMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 640a02a..12dcaa4 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -158,9 +158,6 @@
// this slice (ie, for partial buckets, we use the last partial bucket in this full bucket).
std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly;
- // Pairs of (elapsed start, elapsed end) denoting buckets that were skipped.
- std::list<std::pair<int64_t, int64_t>> mSkippedBuckets;
-
const int64_t mMinBucketSizeNs;
// Translate Atom based bucket to single numeric value bucket for anomaly and updates the map
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 2c8f0e3..cf1d2f3 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -19,6 +19,7 @@
#include "MetricProducer.h"
+#include "../guardrail/StatsdStats.h"
#include "state/StateTracker.h"
using android::util::FIELD_COUNT_REPEATED;
@@ -289,6 +290,17 @@
}
}
+DropEvent MetricProducer::buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason) {
+ DropEvent event;
+ event.reason = reason;
+ event.dropTimeNs = dropTimeNs;
+ return event;
+}
+
+bool MetricProducer::maxDropEventsReached() {
+ return mCurrentSkippedBucket.dropEvents.size() >= StatsdStats::kMaxLoggedBucketDropEvents;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 3512f18..30675fc 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -70,6 +70,22 @@
NO_TIME_CONSTRAINTS = 2
};
+// Keep this in sync with BucketDropReason enum in stats_log.proto
+enum BucketDropReason {
+ // For ValueMetric, a bucket is dropped during a dump report request iff
+ // current bucket should be included, a pull is needed (pulled metric and
+ // condition is true), and we are under fast time constraints.
+ DUMP_REPORT_REQUESTED = 1,
+ EVENT_IN_WRONG_BUCKET = 2,
+ CONDITION_UNKNOWN = 3,
+ PULL_FAILED = 4,
+ PULL_DELAYED = 5,
+ DIMENSION_GUARDRAIL_REACHED = 6,
+ MULTIPLE_BUCKETS_SKIPPED = 7,
+ // Not an invalid bucket case, but the bucket is dropped.
+ BUCKET_TOO_SMALL = 8
+};
+
struct Activation {
Activation(const ActivationType& activationType, const int64_t ttlNs)
: ttl_ns(ttlNs),
@@ -83,6 +99,28 @@
const ActivationType activationType;
};
+struct DropEvent {
+ // Reason for dropping the bucket and/or marking the bucket invalid.
+ BucketDropReason reason;
+ // The timestamp of the drop event.
+ int64_t dropTimeNs;
+};
+
+struct SkippedBucket {
+ // Start time of the dropped bucket.
+ int64_t bucketStartTimeNs;
+ // End time of the dropped bucket.
+ int64_t bucketEndTimeNs;
+ // List of events that invalidated this bucket.
+ std::vector<DropEvent> dropEvents;
+
+ void reset() {
+ bucketStartTimeNs = 0;
+ bucketEndTimeNs = 0;
+ dropEvents.clear();
+ }
+};
+
// A MetricProducer is responsible for compute one single metrics, creating stats log report, and
// writing the report to dropbox. MetricProducers should respond to package changes as required in
// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
@@ -342,6 +380,12 @@
void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
FieldValue* value);
+ DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason);
+
+ // Returns true if the number of drop events in the current bucket has
+ // exceeded the maximum number allowed, which is currently capped at 10.
+ bool maxDropEventsReached();
+
const int64_t mMetricId;
const ConfigKey mConfigKey;
@@ -403,6 +447,10 @@
// atom to fields in the "what" atom.
std::vector<Metric2State> mMetric2StateLinks;
+ SkippedBucket mCurrentSkippedBucket;
+ // Buckets that were invalidated and had their data dropped.
+ std::vector<SkippedBucket> mSkippedBuckets;
+
FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 464cec3..088f607 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -23,6 +23,7 @@
#include <utils/SystemClock.h>
#include "CountMetricProducer.h"
+#include "atoms_info.h"
#include "condition/CombinationConditionTracker.h"
#include "condition/SimpleConditionTracker.h"
#include "guardrail/StatsdStats.h"
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index b7d169d..d8f399f 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -55,8 +55,13 @@
// for ValueMetricDataWrapper
const int FIELD_ID_DATA = 1;
const int FIELD_ID_SKIPPED = 2;
+// for SkippedBuckets
const int FIELD_ID_SKIPPED_START_MILLIS = 3;
const int FIELD_ID_SKIPPED_END_MILLIS = 4;
+const int FIELD_ID_SKIPPED_DROP_EVENT = 5;
+// for DumpEvent Proto
+const int FIELD_ID_BUCKET_DROP_REASON = 1;
+const int FIELD_ID_DROP_TIME = 2;
// for ValueMetricData
const int FIELD_ID_DIMENSION_IN_WHAT = 1;
const int FIELD_ID_BUCKET_INFO = 3;
@@ -183,11 +188,9 @@
void ValueMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
StatsdStats::getInstance().noteBucketDropped(mMetricId);
- // We are going to flush the data without doing a pull first so we need to invalidte the data.
- bool pullNeeded = mIsPulled && mCondition == ConditionState::kTrue;
- if (pullNeeded) {
- invalidateCurrentBucket();
- }
+
+ // The current partial bucket is not flushed and does not require a pull,
+ // so the data is still valid.
flushIfNeededLocked(dropTimeNs);
clearPastBucketsLocked(dropTimeNs);
}
@@ -213,7 +216,7 @@
if (pullNeeded) {
switch (dumpLatency) {
case FAST:
- invalidateCurrentBucket();
+ invalidateCurrentBucket(dumpTimeNs, BucketDropReason::DUMP_REPORT_REQUESTED);
break;
case NO_TIME_CONSTRAINTS:
pullAndMatchEventsLocked(dumpTimeNs);
@@ -242,13 +245,22 @@
uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_VALUE_METRICS);
- for (const auto& pair : mSkippedBuckets) {
+ for (const auto& skippedBucket : mSkippedBuckets) {
uint64_t wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS,
- (long long)(NanoToMillis(pair.first)));
+ (long long)(NanoToMillis(skippedBucket.bucketStartTimeNs)));
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS,
- (long long)(NanoToMillis(pair.second)));
+ (long long)(NanoToMillis(skippedBucket.bucketEndTimeNs)));
+ for (const auto& dropEvent : skippedBucket.dropEvents) {
+ uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+ FIELD_ID_SKIPPED_DROP_EVENT);
+ protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason);
+ protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME,
+ (long long)(NanoToMillis(dropEvent.dropTimeNs)));
+ ;
+ protoOutput->end(dropEventToken);
+ }
protoOutput->end(wrapperToken);
}
@@ -323,16 +335,25 @@
}
}
-void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase() {
+void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs,
+ const BucketDropReason reason) {
if (!mCurrentBucketIsInvalid) {
- // Only report once per invalid bucket.
+ // Only report to StatsdStats once per invalid bucket.
StatsdStats::getInstance().noteInvalidatedBucket(mMetricId);
+
+ mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
+ mCurrentSkippedBucket.bucketEndTimeNs = getCurrentBucketEndTimeNs();
+ }
+
+ if (!maxDropEventsReached()) {
+ mCurrentSkippedBucket.dropEvents.emplace_back(buildDropEvent(dropTimeNs, reason));
}
mCurrentBucketIsInvalid = true;
}
-void ValueMetricProducer::invalidateCurrentBucket() {
- invalidateCurrentBucketWithoutResetBase();
+void ValueMetricProducer::invalidateCurrentBucket(const int64_t dropTimeNs,
+ const BucketDropReason reason) {
+ invalidateCurrentBucketWithoutResetBase(dropTimeNs, reason);
resetBase();
}
@@ -353,7 +374,8 @@
bool isEventTooLate = eventTimeNs < mCurrentBucketStartTimeNs;
if (isEventTooLate) {
// Drop bucket because event arrived too late, ie. we are missing data for this bucket.
- invalidateCurrentBucket();
+ StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId);
+ invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
}
// Call parent method once we've verified the validity of current bucket.
@@ -396,8 +418,9 @@
if (isEventTooLate) {
VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
(long long)mCurrentBucketStartTimeNs);
+ StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId);
StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId);
- invalidateCurrentBucket();
+ invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
mCondition = ConditionState::kUnknown;
mConditionTimer.onConditionChanged(mCondition, eventTimeNs);
return;
@@ -410,7 +433,7 @@
//
// We still want to pull to set the base.
if (mCondition == ConditionState::kUnknown) {
- invalidateCurrentBucket();
+ invalidateCurrentBucket(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN);
}
// Pull and match for the following condition change cases:
@@ -447,7 +470,7 @@
vector<std::shared_ptr<LogEvent>> allData;
if (!mPullerManager->Pull(mPullTagId, &allData)) {
ALOGE("Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs);
- invalidateCurrentBucket();
+ invalidateCurrentBucket(timestampNs, BucketDropReason::PULL_FAILED);
return;
}
@@ -467,7 +490,7 @@
if (mCondition == ConditionState::kTrue) {
// If the pull failed, we won't be able to compute a diff.
if (!pullSuccess) {
- invalidateCurrentBucket();
+ invalidateCurrentBucket(originalPullTimeNs, BucketDropReason::PULL_FAILED);
} else {
bool isEventLate = originalPullTimeNs < getCurrentBucketEndTimeNs();
if (isEventLate) {
@@ -504,11 +527,12 @@
VLOG("Skip bucket end pull due to late arrival: %lld vs %lld",
(long long)eventElapsedTimeNs, (long long)mCurrentBucketStartTimeNs);
StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId);
- invalidateCurrentBucket();
+ invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
return;
}
- const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs;
+ const int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
+ const int64_t pullDelayNs = elapsedRealtimeNs - originalPullTimeNs;
StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs);
if (pullDelayNs > mMaxPullDelayNs) {
ALOGE("Pull finish too late for atom %d, longer than %lld", mPullTagId,
@@ -516,7 +540,7 @@
StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId);
// We are missing one pull from the bucket which means we will not have a complete view of
// what's going on.
- invalidateCurrentBucket();
+ invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::PULL_DELAYED);
return;
}
@@ -555,7 +579,7 @@
// incorrectly compute the diff when mUseZeroDefaultBase is true since an existing key
// might be missing from mCurrentSlicedBucket.
if (hasReachedGuardRailLimit()) {
- invalidateCurrentBucket();
+ invalidateCurrentBucket(eventElapsedTimeNs, BucketDropReason::DIMENSION_GUARDRAIL_REACHED);
mCurrentSlicedBucket.clear();
}
}
@@ -841,7 +865,8 @@
StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId);
// Something went wrong. Maybe the device was sleeping for a long time. It is better
// to mark the current bucket as invalid. The last pull might have been successful through.
- invalidateCurrentBucketWithoutResetBase();
+ invalidateCurrentBucketWithoutResetBase(eventTimeNs,
+ BucketDropReason::MULTIPLE_BUCKETS_SKIPPED);
}
VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs,
@@ -851,6 +876,18 @@
// Close the current bucket.
int64_t conditionTrueDuration = mConditionTimer.newBucketStart(bucketEndTime);
bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs;
+ if (!isBucketLargeEnough) {
+ // If the bucket is valid, this is the only drop reason and we need to
+ // set the skipped bucket start and end times.
+ if (!mCurrentBucketIsInvalid) {
+ mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
+ mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime;
+ }
+ if (!maxDropEventsReached()) {
+ mCurrentSkippedBucket.dropEvents.emplace_back(
+ buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL));
+ }
+ }
if (isBucketLargeEnough && !mCurrentBucketIsInvalid) {
// The current bucket is large enough to keep.
for (const auto& slice : mCurrentSlicedBucket) {
@@ -863,7 +900,7 @@
}
}
} else {
- mSkippedBuckets.emplace_back(mCurrentBucketStartTimeNs, bucketEndTime);
+ mSkippedBuckets.emplace_back(mCurrentSkippedBucket);
}
appendToFullBucket(eventTimeNs, fullBucketEndTimeNs);
@@ -919,6 +956,8 @@
}
mCurrentBucketIsInvalid = false;
+ mCurrentSkippedBucket.reset();
+
// If we do not have a global base when the condition is true,
// we will have incomplete bucket for the next bucket.
if (mUseDiff && !mHasGlobalBase && mCondition) {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 2033a2a..4eae99b 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -130,8 +130,9 @@
int64_t calcBucketsForwardCount(const int64_t& eventTimeNs) const;
// Mark the data as invalid.
- void invalidateCurrentBucket();
- void invalidateCurrentBucketWithoutResetBase();
+ void invalidateCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason);
+ void invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs,
+ const BucketDropReason reason);
const int mWhatMatcherIndex;
@@ -177,9 +178,6 @@
// Save the past buckets and we can clear when the StatsLogReport is dumped.
std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>> mPastBuckets;
- // Pairs of (elapsed start, elapsed end) denoting buckets that were skipped.
- std::list<std::pair<int64_t, int64_t>> mSkippedBuckets;
-
const int64_t mMinBucketSizeNs;
// Util function to check whether the specified dimension hits the guardrail.
@@ -248,7 +246,6 @@
FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition);
FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition);
FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2);
- FRIEND_TEST(ValueMetricProducerTest, TestBucketIncludingUnknownConditionIsInvalid);
FRIEND_TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet);
FRIEND_TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime);
FRIEND_TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged);
@@ -258,10 +255,6 @@
FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket);
FRIEND_TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid);
- FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenGuardRailHit);
- FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed);
- FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed);
- FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed);
FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff);
FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff);
FRIEND_TEST(ValueMetricProducerTest, TestPartialBucketCreated);
@@ -295,6 +288,13 @@
FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey);
FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase);
FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures);
+
+ FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed);
+ FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed);
+ FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed);
+ FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit);
+ FRIEND_TEST(ValueMetricProducerTest_BucketDrop,
+ TestInvalidBucketWhenAccumulateEventWrongBucket);
friend class ValueMetricProducerTestHelper;
};
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 9131802..2ad8217 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -22,6 +22,7 @@
#include <inttypes.h>
+#include "atoms_info.h"
#include "condition/CombinationConditionTracker.h"
#include "condition/SimpleConditionTracker.h"
#include "condition/StateConditionTracker.h"
@@ -36,7 +37,6 @@
#include "metrics/ValueMetricProducer.h"
#include "state/StateManager.h"
#include "stats_util.h"
-#include "statslog.h"
using std::set;
using std::string;
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
index 2fa28c9..80d3983 100644
--- a/cmds/statsd/src/state/StateManager.cpp
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -29,18 +29,15 @@
}
void StateManager::onLogEvent(const LogEvent& event) {
- std::lock_guard<std::mutex> lock(mMutex);
if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) {
mStateTrackers[event.GetTagId()]->onLogEvent(event);
}
}
bool StateManager::registerListener(int32_t atomId, wp<StateListener> listener) {
- std::lock_guard<std::mutex> lock(mMutex);
-
- // Check if state tracker already exists
+ // Check if state tracker already exists.
if (mStateTrackers.find(atomId) == mStateTrackers.end()) {
- // Create a new state tracker iff atom is a state atom
+ // Create a new state tracker iff atom is a state atom.
auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(atomId);
if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
mStateTrackers[atomId] = new StateTracker(atomId, it->second);
@@ -79,8 +76,6 @@
bool StateManager::getStateValue(int32_t atomId, const HashableDimensionKey& key,
FieldValue* output) const {
- std::lock_guard<std::mutex> lock(mMutex);
-
auto it = mStateTrackers.find(atomId);
if (it != mStateTrackers.end()) {
return it->second->getStateValue(key, output);
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
index 272724c..a6053e6 100644
--- a/cmds/statsd/src/state/StateManager.h
+++ b/cmds/statsd/src/state/StateManager.h
@@ -27,6 +27,10 @@
namespace os {
namespace statsd {
+/**
+ * This class is NOT thread safe.
+ * It should only be used while StatsLogProcessor's lock is held.
+ */
class StateManager : public virtual RefBase {
public:
StateManager(){};
@@ -56,13 +60,10 @@
FieldValue* output) const;
inline int getStateTrackersCount() const {
- std::lock_guard<std::mutex> lock(mMutex);
return mStateTrackers.size();
}
inline int getListenersCount(int32_t atomId) const {
- std::lock_guard<std::mutex> lock(mMutex);
-
auto it = mStateTrackers.find(atomId);
if (it != mStateTrackers.end()) {
return it->second->getListenersCount();
@@ -71,10 +72,10 @@
}
private:
- mutable std::mutex mMutex;
+ mutable std::mutex mMutex;
- // Maps state atom ids to StateTrackers
- std::unordered_map<int32_t, sp<StateTracker>> mStateTrackers;
+ // Maps state atom ids to StateTrackers
+ std::unordered_map<int32_t, sp<StateTracker>> mStateTrackers;
};
} // namespace statsd
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index 544857f..7453370 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -15,7 +15,7 @@
*/
#pragma once
-#include <statslog.h>
+#include <atoms_info.h>
#include <utils/RefBase.h>
#include "HashableDimensionKey.h"
#include "logd/LogEvent.h"
@@ -80,10 +80,12 @@
// Set of all StateListeners (objects listening for state changes)
std::set<wp<StateListener>> mListeners;
- // Reset all state values in map to default state
+ // Reset all state values in map to default state.
void handleReset(const int64_t eventTimeNs);
- // Reset only the state value mapped to primary key to default state
+ // Reset only the state value mapped to the given primary key to default state.
+ // Partial resets are used when we only need to update the state of one primary
+ // key instead of clearing/reseting every key in the map.
void handlePartialReset(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey);
// Update the StateMap based on the received state value.
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index e45e24fe..8b4d781 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -191,11 +191,40 @@
// Fields 2 and 3 are reserved.
+ // Keep this in sync with BucketDropReason enum in MetricProducer.h.
+ enum BucketDropReason {
+ // For ValueMetric, a bucket is dropped during a dump report request iff
+ // current bucket should be included, a pull is needed (pulled metric and
+ // condition is true), and we are under fast time constraints.
+ DUMP_REPORT_REQUESTED = 1;
+ EVENT_IN_WRONG_BUCKET = 2;
+ CONDITION_UNKNOWN = 3;
+ PULL_FAILED = 4;
+ PULL_DELAYED = 5;
+ DIMENSION_GUARDRAIL_REACHED = 6;
+ MULTIPLE_BUCKETS_SKIPPED = 7;
+ // Not an invalid bucket case, but the bucket is dropped.
+ BUCKET_TOO_SMALL = 8;
+ };
+
+ message DropEvent {
+ optional BucketDropReason drop_reason = 1;
+
+ optional int64 drop_time_millis = 2;
+ }
+
message SkippedBuckets {
optional int64 start_bucket_elapsed_nanos = 1;
+
optional int64 end_bucket_elapsed_nanos = 2;
+
optional int64 start_bucket_elapsed_millis = 3;
+
optional int64 end_bucket_elapsed_millis = 4;
+
+ // The number of drop events is capped by StatsdStats::kMaxLoggedBucketDropEvents.
+ // The current maximum is 10 drop events.
+ repeated DropEvent drop_event = 5;
}
message EventMetricDataWrapper {
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 0a86363..f3e9433 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -19,9 +19,9 @@
#include <android/util/ProtoOutputStream.h>
#include "FieldValue.h"
#include "HashableDimensionKey.h"
+#include "atoms_info.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "guardrail/StatsdStats.h"
-#include "statslog.h"
namespace android {
namespace os {
diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp
index f1cad92..f4a59ed 100644
--- a/cmds/statsd/tests/FieldValue_test.cpp
+++ b/cmds/statsd/tests/FieldValue_test.cpp
@@ -480,6 +480,137 @@
EXPECT_EQ(999, atom.num_results());
}
+/*
+ * Test two Matchers is not a subset of one Matcher.
+ * Test one Matcher is subset of two Matchers.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions1) {
+ // Initialize first set of matchers
+ FieldMatcher matcher1;
+ matcher1.set_field(10);
+
+ FieldMatcher* child = matcher1.add_child();
+ child->set_field(1);
+ child->set_position(Position::ALL);
+ child->add_child()->set_field(1);
+ child->add_child()->set_field(2);
+
+ vector<Matcher> matchers1;
+ translateFieldMatcher(matcher1, &matchers1);
+ EXPECT_EQ(2, matchers1.size());
+
+ // Initialize second set of matchers
+ FieldMatcher matcher2;
+ matcher2.set_field(10);
+
+ child = matcher2.add_child();
+ child->set_field(1);
+ child->set_position(Position::ALL);
+ child->add_child()->set_field(1);
+
+ vector<Matcher> matchers2;
+ translateFieldMatcher(matcher2, &matchers2);
+ EXPECT_EQ(1, matchers2.size());
+
+ EXPECT_FALSE(subsetDimensions(matchers1, matchers2));
+ EXPECT_TRUE(subsetDimensions(matchers2, matchers1));
+}
+/*
+ * Test not a subset with one matching Matcher, one non-matching Matcher.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions2) {
+ // Initialize first set of matchers
+ FieldMatcher matcher1;
+ matcher1.set_field(10);
+
+ FieldMatcher* child = matcher1.add_child();
+ child->set_field(1);
+
+ child = matcher1.add_child();
+ child->set_field(2);
+
+ vector<Matcher> matchers1;
+ translateFieldMatcher(matcher1, &matchers1);
+
+ // Initialize second set of matchers
+ FieldMatcher matcher2;
+ matcher2.set_field(10);
+
+ child = matcher2.add_child();
+ child->set_field(1);
+
+ child = matcher2.add_child();
+ child->set_field(3);
+
+ vector<Matcher> matchers2;
+ translateFieldMatcher(matcher2, &matchers2);
+
+ EXPECT_FALSE(subsetDimensions(matchers1, matchers2));
+}
+
+/*
+ * Test not a subset if parent field is not equal.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions3) {
+ // Initialize first set of matchers
+ FieldMatcher matcher1;
+ matcher1.set_field(10);
+
+ FieldMatcher* child = matcher1.add_child();
+ child->set_field(1);
+
+ vector<Matcher> matchers1;
+ translateFieldMatcher(matcher1, &matchers1);
+
+ // Initialize second set of matchers
+ FieldMatcher matcher2;
+ matcher2.set_field(5);
+
+ child = matcher2.add_child();
+ child->set_field(1);
+
+ vector<Matcher> matchers2;
+ translateFieldMatcher(matcher2, &matchers2);
+
+ EXPECT_FALSE(subsetDimensions(matchers1, matchers2));
+}
+
+/*
+ * Test is subset with two matching Matchers.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions4) {
+ // Initialize first set of matchers
+ FieldMatcher matcher1;
+ matcher1.set_field(10);
+
+ FieldMatcher* child = matcher1.add_child();
+ child->set_field(1);
+
+ child = matcher1.add_child();
+ child->set_field(2);
+
+ vector<Matcher> matchers1;
+ translateFieldMatcher(matcher1, &matchers1);
+
+ // Initialize second set of matchers
+ FieldMatcher matcher2;
+ matcher2.set_field(10);
+
+ child = matcher2.add_child();
+ child->set_field(1);
+
+ child = matcher2.add_child();
+ child->set_field(2);
+
+ child = matcher2.add_child();
+ child->set_field(3);
+
+ vector<Matcher> matchers2;
+ translateFieldMatcher(matcher2, &matchers2);
+
+ EXPECT_TRUE(subsetDimensions(matchers1, matchers2));
+ EXPECT_FALSE(subsetDimensions(matchers2, matchers1));
+}
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
index e91fb0d..6abdfa3 100644
--- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
@@ -24,6 +24,7 @@
#include <log/log.h>
#include "src/external/GpuStatsPuller.h"
+#include "statslog.h"
#ifdef __ANDROID__
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
new file mode 100644
index 0000000..2b0590d
--- /dev/null
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -0,0 +1,210 @@
+// 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 "src/external/StatsCallbackPuller.h"
+
+#include <android/os/BnPullAtomCallback.h>
+#include <android/os/IPullAtomResultReceiver.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <chrono>
+#include <thread>
+#include <vector>
+
+#include "../metrics/metrics_test_helper.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using namespace testing;
+using std::make_shared;
+using std::shared_ptr;
+using std::vector;
+using std::this_thread::sleep_for;
+using testing::Contains;
+
+namespace {
+int pullTagId = -12;
+bool pullSuccess;
+vector<int64_t> values;
+int64_t pullDelayNs;
+int64_t pullTimeoutNs;
+int64_t pullCoolDownNs;
+std::thread pullThread;
+
+stats_event* createSimpleEvent(int64_t value) {
+ stats_event* event = stats_event_obtain();
+ stats_event_set_atom_id(event, pullTagId);
+ stats_event_write_int64(event, value);
+ stats_event_build(event);
+ return event;
+}
+
+void executePull(const sp<IPullAtomResultReceiver>& resultReceiver) {
+ // Convert stats_events into StatsEventParcels.
+ std::vector<android::util::StatsEventParcel> parcels;
+ for (int i = 0; i < values.size(); i++) {
+ stats_event* event = createSimpleEvent(values[i]);
+ size_t size;
+ uint8_t* buffer = stats_event_get_buffer(event, &size);
+
+ android::util::StatsEventParcel p;
+ // vector.assign() creates a copy, but this is inevitable unless
+ // stats_event.h/c uses a vector as opposed to a buffer.
+ p.buffer.assign(buffer, buffer + size);
+ parcels.push_back(std::move(p));
+ stats_event_release(event);
+ }
+
+ sleep_for(std::chrono::nanoseconds(pullDelayNs));
+ resultReceiver->pullFinished(pullTagId, pullSuccess, parcels);
+}
+
+class FakePullAtomCallback : public BnPullAtomCallback {
+public:
+ binder::Status onPullAtom(int atomTag,
+ const sp<IPullAtomResultReceiver>& resultReceiver) override {
+ // Force pull to happen in separate thread to simulate binder.
+ pullThread = std::thread(executePull, resultReceiver);
+ return binder::Status::ok();
+ }
+};
+
+class StatsCallbackPullerTest : public ::testing::Test {
+public:
+ StatsCallbackPullerTest() {
+ }
+
+ void SetUp() override {
+ pullSuccess = false;
+ pullDelayNs = 0;
+ values.clear();
+ pullTimeoutNs = 10000000000LL; // 10 seconds.
+ pullCoolDownNs = 1000000000; // 1 second.
+ }
+
+ void TearDown() override {
+ if (pullThread.joinable()) {
+ pullThread.join();
+ }
+ values.clear();
+ }
+};
+} // Anonymous namespace.
+
+TEST_F(StatsCallbackPullerTest, PullSuccess) {
+ sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+ int64_t value = 43;
+ pullSuccess = true;
+ values.push_back(value);
+
+ StatsCallbackPuller puller(pullTagId, cb, pullTimeoutNs);
+
+ vector<std::shared_ptr<LogEvent>> dataHolder;
+ int64_t startTimeNs = getElapsedRealtimeNs();
+ EXPECT_TRUE(puller.PullInternal(&dataHolder));
+ int64_t endTimeNs = getElapsedRealtimeNs();
+
+ EXPECT_EQ(1, dataHolder.size());
+ EXPECT_EQ(pullTagId, dataHolder[0]->GetTagId());
+ EXPECT_LT(startTimeNs, dataHolder[0]->GetElapsedTimestampNs());
+ EXPECT_GT(endTimeNs, dataHolder[0]->GetElapsedTimestampNs());
+ EXPECT_EQ(1, dataHolder[0]->size());
+ EXPECT_EQ(value, dataHolder[0]->getValues()[0].mValue.int_value);
+}
+
+TEST_F(StatsCallbackPullerTest, PullFail) {
+ sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+ pullSuccess = false;
+ int64_t value = 1234;
+ values.push_back(value);
+
+ StatsCallbackPuller puller(pullTagId, cb, pullTimeoutNs);
+
+ vector<std::shared_ptr<LogEvent>> dataHolder;
+ EXPECT_FALSE(puller.PullInternal(&dataHolder));
+ EXPECT_EQ(0, dataHolder.size());
+}
+
+TEST_F(StatsCallbackPullerTest, PullTimeout) {
+ sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+ pullSuccess = true;
+ pullDelayNs = 500000000; // 500ms.
+ pullTimeoutNs = 10000; // 10 microseconds.
+ int64_t value = 4321;
+ values.push_back(value);
+
+ StatsCallbackPuller puller(pullTagId, cb, pullTimeoutNs);
+
+ vector<std::shared_ptr<LogEvent>> dataHolder;
+ int64_t startTimeNs = getElapsedRealtimeNs();
+ // Returns true to let StatsPuller code evaluate the timeout.
+ EXPECT_TRUE(puller.PullInternal(&dataHolder));
+ int64_t endTimeNs = getElapsedRealtimeNs();
+ int64_t actualPullDurationNs = endTimeNs - startTimeNs;
+
+ // Pull should take at least the timeout amount of time, but should stop early because the delay
+ // is bigger.
+ EXPECT_LT(pullTimeoutNs, actualPullDurationNs);
+ EXPECT_GT(pullDelayNs, actualPullDurationNs);
+ EXPECT_EQ(0, dataHolder.size());
+
+ // Let the pull return and make sure that the dataHolder is not modified.
+ pullThread.join();
+ EXPECT_EQ(0, dataHolder.size());
+}
+
+// Register a puller and ensure that the timeout logic works.
+TEST_F(StatsCallbackPullerTest, RegisterAndTimeout) {
+ sp<FakePullAtomCallback> cb = new FakePullAtomCallback();
+ pullSuccess = true;
+ pullDelayNs = 500000000; // 500 ms.
+ pullTimeoutNs = 10000; // 10 microsseconds.
+ int64_t value = 4321;
+ values.push_back(value);
+
+ StatsPullerManager pullerManager;
+ pullerManager.RegisterPullAtomCallback(/*uid=*/-1, pullTagId, pullCoolDownNs, pullTimeoutNs,
+ vector<int32_t>(), cb);
+ vector<std::shared_ptr<LogEvent>> dataHolder;
+ int64_t startTimeNs = getElapsedRealtimeNs();
+ // Returns false, since StatsPuller code will evaluate the timeout.
+ EXPECT_FALSE(pullerManager.Pull(pullTagId, &dataHolder));
+ int64_t endTimeNs = getElapsedRealtimeNs();
+ int64_t actualPullDurationNs = endTimeNs - startTimeNs;
+
+ // Pull should take at least the timeout amount of time, but should stop early because the delay
+ // is bigger.
+ EXPECT_LT(pullTimeoutNs, actualPullDurationNs);
+ EXPECT_GT(pullDelayNs, actualPullDurationNs);
+ EXPECT_EQ(0, dataHolder.size());
+
+ // Let the pull return and make sure that the dataHolder is not modified.
+ pullThread.join();
+ EXPECT_EQ(0, dataHolder.size());
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/external/StatsPuller_test.cpp b/cmds/statsd/tests/external/StatsPuller_test.cpp
index 76e2097..c40719a 100644
--- a/cmds/statsd/tests/external/StatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsPuller_test.cpp
@@ -35,6 +35,7 @@
using std::this_thread::sleep_for;
using testing::Contains;
+namespace {
// cooldown time 1sec.
int pullTagId = 10014;
@@ -76,7 +77,9 @@
}
};
-TEST_F(StatsPullerTest, PullSucces) {
+} // Anonymous namespace.
+
+TEST_F(StatsPullerTest, PullSuccess) {
pullData.push_back(createSimpleEvent(1111L, 33));
pullSuccess = true;
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
index 5c9636f..5b7a30d 100644
--- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "SurfaceflingerStatsPuller_test"
#include "src/external/SurfaceflingerStatsPuller.h"
+#include "statslog.h"
#include <gtest/gtest.h>
#include <log/log.h>
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
index 266ea35..6730828 100644
--- a/cmds/statsd/tests/external/puller_util_test.cpp
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -17,6 +17,7 @@
#include <gtest/gtest.h>
#include <stdio.h>
#include <vector>
+#include "statslog.h"
#include "../metrics/metrics_test_helper.h"
#ifdef __ANDROID__
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index b027e8e..308c43d 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -12,19 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "src/matchers/SimpleLogMatchingTracker.h"
#include "src/metrics/GaugeMetricProducer.h"
-#include "src/stats_log_util.h"
-#include "logd/LogEvent.h"
-#include "metrics_test_helper.h"
-#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <math.h>
#include <stdio.h>
+
#include <vector>
+#include "logd/LogEvent.h"
+#include "metrics_test_helper.h"
+#include "src/matchers/SimpleLogMatchingTracker.h"
+#include "src/metrics/MetricProducer.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
using namespace testing;
using android::sp;
using std::set;
@@ -784,6 +787,70 @@
EXPECT_EQ(6, bucketIt->second.back().mGaugeAtoms[1].mFields->begin()->mValue.int_value);
}
+/*
+ * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size
+ * is smaller than the "min_bucket_size_nanos" specified in the metric config.
+ */
+TEST(GaugeMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) {
+ GaugeMetric metric;
+ metric.set_id(metricId);
+ metric.set_bucket(FIVE_MINUTES);
+ metric.set_sampling_type(GaugeMetric::FIRST_N_SAMPLES);
+ metric.set_min_bucket_size_nanos(10000000000); // 10 seconds
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ UidMap uidMap;
+ SimpleAtomMatcher atomMatcher;
+ atomMatcher.set_atom_id(tagId);
+ sp<EventMatcherWizard> eventMatcherWizard = new EventMatcherWizard({
+ new SimpleLogMatchingTracker(atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Bucket start.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ int triggerId = 5;
+ GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
+ logEventMatcherIndex, eventMatcherWizard,
+ tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
+ pullerManager);
+
+ LogEvent trigger(triggerId, bucketStartTimeNs + 3);
+ trigger.init();
+ gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, trigger);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ gaugeProducer.onDumpReport(bucketStartTimeNs + 9000000, true /* include recent buckets */,
+ true, FAST /* dump_latency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_gauge_metrics());
+ EXPECT_EQ(0, report.gauge_metrics().data_size());
+ EXPECT_EQ(1, report.gauge_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.gauge_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000),
+ report.gauge_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.gauge_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.gauge_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), dropEvent.drop_time_millis());
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 4b9d0c0..da0a672 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -12,21 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "src/matchers/SimpleLogMatchingTracker.h"
#include "src/metrics/ValueMetricProducer.h"
-#include "src/stats_log_util.h"
-#include "metrics_test_helper.h"
-#include "tests/statsd_test_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <math.h>
#include <stdio.h>
+
#include <vector>
+#include "metrics_test_helper.h"
+#include "src/matchers/SimpleLogMatchingTracker.h"
+#include "src/metrics/MetricProducer.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
using namespace testing;
using android::sp;
-using android::util::ProtoReader;
using std::make_shared;
using std::set;
using std::shared_ptr;
@@ -128,6 +130,24 @@
return valueProducer;
}
+ static sp<ValueMetricProducer> createValueProducerWithNoInitialCondition(
+ sp<MockStatsPullerManager>& pullerManager, ValueMetric& metric) {
+ UidMap uidMap;
+ SimpleAtomMatcher atomMatcher;
+ atomMatcher.set_atom_id(tagId);
+ sp<EventMatcherWizard> eventMatcherWizard =
+ new EventMatcherWizard({new SimpleLogMatchingTracker(
+ atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
+
+ sp<ValueMetricProducer> valueProducer = new ValueMetricProducer(
+ kConfigKey, metric, 1, wizard, logEventMatcherIndex, eventMatcherWizard, tagId,
+ bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+ return valueProducer;
+ }
+
static ValueMetric createMetric() {
ValueMetric metric;
metric.set_id(metricId);
@@ -206,7 +226,7 @@
* Tests pulled atoms with no conditions
*/
TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
EXPECT_CALL(*pullerManager, Pull(tagId, _))
.WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
@@ -290,7 +310,7 @@
}
TEST(ValueMetricProducerTest, TestPartialBucketCreated) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
EXPECT_CALL(*pullerManager, Pull(tagId, _))
// Initialize bucket.
@@ -347,7 +367,7 @@
* Tests pulled atoms with filtering
*/
TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
UidMap uidMap;
SimpleAtomMatcher atomMatcher;
@@ -696,7 +716,7 @@
}
TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
UidMap uidMap;
SimpleAtomMatcher atomMatcher;
@@ -1026,7 +1046,7 @@
// Test value metric no condition, the pull on bucket boundary come in time and too late
TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Return(true));
sp<ValueMetricProducer> valueProducer =
@@ -2104,7 +2124,10 @@
EXPECT_EQ(true, valueProducer->mHasGlobalBase);
}
-TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed) {
+/*
+ * Tests that a bucket is marked invalid when a condition change pull fails.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
@@ -2162,9 +2185,33 @@
EXPECT_EQ(140, curInterval.base.long_value);
EXPECT_EQ(false, curInterval.hasValue);
EXPECT_EQ(true, valueProducer->mHasGlobalBase);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket2StartTimeNs + 10, false /* include partial bucket */, true,
+ FAST /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis());
}
-TEST(ValueMetricProducerTest, TestInvalidBucketWhenGuardRailHit) {
+/*
+ * Tests that a bucket is marked invalid when the guardrail is hit.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
metric.mutable_dimensions_in_what()->set_field(tagId);
metric.mutable_dimensions_in_what()->add_child()->set_field(1);
@@ -2191,9 +2238,47 @@
valueProducer->onConditionChanged(true, bucketStartTimeNs + 2);
EXPECT_EQ(true, valueProducer->mCurrentBucketIsInvalid);
EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+ EXPECT_EQ(0UL, valueProducer->mSkippedBuckets.size());
+
+ // Bucket 2 start.
+ vector<shared_ptr<LogEvent>> allData;
+ allData.clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+ event->write(1);
+ event->write(10);
+ event->init();
+ allData.push_back(event);
+ valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
+
+ // First bucket added to mSkippedBuckets after flush.
+ EXPECT_EQ(1UL, valueProducer->mSkippedBuckets.size());
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */,
+ true, FAST /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::DIMENSION_GUARDRAIL_REACHED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis());
}
-TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed) {
+/*
+ * Tests that a bucket is marked invalid when the bucket's initial pull fails.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
@@ -2257,9 +2342,34 @@
EXPECT_EQ(140, curInterval.base.long_value);
EXPECT_EQ(false, curInterval.hasValue);
EXPECT_EQ(true, valueProducer->mHasGlobalBase);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */,
+ true, FAST /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 2), dropEvent.drop_time_millis());
}
-TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed) {
+/*
+ * Tests that a bucket is marked invalid when the bucket's final pull fails
+ * (i.e. failed pull on bucket boundary).
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenLastPullFailed) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
@@ -2300,8 +2410,6 @@
allData.push_back(event);
valueProducer->onDataPulled(allData, /** succeed */ true, bucketStartTimeNs);
- // This will fail and should invalidate the whole bucket since we do not have all the data
- // needed to compute the metric value when the screen was on.
valueProducer->onConditionChanged(false, bucketStartTimeNs + 2);
valueProducer->onConditionChanged(true, bucketStartTimeNs + 3);
@@ -2317,17 +2425,38 @@
valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1);
EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
- // Last pull failed so based has been reset.
+ // Last pull failed so base has been reset.
EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
ValueMetricProducer::Interval& curInterval =
valueProducer->mCurrentSlicedBucket.begin()->second[0];
EXPECT_EQ(false, curInterval.hasBase);
EXPECT_EQ(false, curInterval.hasValue);
EXPECT_EQ(false, valueProducer->mHasGlobalBase);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket2StartTimeNs + 10000, false /* include recent buckets */,
+ true, FAST /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis());
}
TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
EXPECT_CALL(*pullerManager, Pull(tagId, _))
// Start bucket.
@@ -2520,48 +2649,6 @@
EXPECT_EQ(true, valueProducer->mHasGlobalBase);
}
-TEST(ValueMetricProducerTest, TestBucketIncludingUnknownConditionIsInvalid) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
- metric.mutable_dimensions_in_what()->set_field(tagId);
- metric.mutable_dimensions_in_what()->add_child()->set_field(1);
- metric.set_condition(StringToId("SCREEN_ON"));
-
- sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
- EXPECT_CALL(*pullerManager, Pull(tagId, _))
- // Second onConditionChanged.
- .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
- data->clear();
- shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs);
- event->write(tagId);
- event->write(2);
- event->write(2);
- event->init();
- data->push_back(event);
- return true;
- }));
-
- sp<ValueMetricProducer> valueProducer =
- ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
- valueProducer->mCondition = ConditionState::kUnknown;
-
- valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
- valueProducer->onConditionChanged(true, bucketStartTimeNs + 20);
-
- // End of bucket
- vector<shared_ptr<LogEvent>> allData;
- allData.clear();
- shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
- event->write(4);
- event->write(4);
- event->init();
- allData.push_back(event);
- valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-
- // Bucket is incomplete so it is mark as invalid, however the base is fine since the last pull
- // succeeded.
- EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
-}
-
TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
@@ -2750,6 +2837,7 @@
assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2});
}
+// TODO: b/145705635 fix or delete this test
TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
@@ -2797,25 +2885,8 @@
assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
}
-static StatsLogReport outputStreamToProto(ProtoOutputStream* proto) {
- vector<uint8_t> bytes;
- bytes.resize(proto->size());
- size_t pos = 0;
- sp<ProtoReader> reader = proto->data();
- while (reader->readBuffer() != NULL) {
- size_t toRead = reader->currentToRead();
- std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead);
- pos += toRead;
- reader->move(toRead);
- }
-
- StatsLogReport report;
- report.ParseFromArray(bytes.data(), bytes.size());
- return report;
-}
-
TEST(ValueMetricProducerTest, TestPullNeededFastDump) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
UidMap uidMap;
SimpleAtomMatcher atomMatcher;
@@ -2857,7 +2928,7 @@
}
TEST(ValueMetricProducerTest, TestFastDumpWithoutCurrentBucket) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
UidMap uidMap;
SimpleAtomMatcher atomMatcher;
@@ -2910,7 +2981,7 @@
}
TEST(ValueMetricProducerTest, TestPullNeededNoTimeConstraints) {
- ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
UidMap uidMap;
SimpleAtomMatcher atomMatcher;
@@ -3103,6 +3174,600 @@
assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
}
+/*
+ * Test that DUMP_REPORT_REQUESTED dump reason is logged.
+ *
+ * For the bucket to be marked invalid during a dump report requested,
+ * three things must be true:
+ * - we want to include the current partial bucket
+ * - we need a pull (metric is pulled and condition is true)
+ * - the dump latency must be FAST
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenDumpReportRequested) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Condition change to true.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 20);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+
+ // Condition change event.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 20);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucketStartTimeNs + 40, true /* include recent buckets */, true,
+ FAST /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 40), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late condition
+ * change event (i.e. the condition change occurs in the wrong bucket).
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionEventWrongBucket) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Condition change to true.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 50);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+
+ // Condition change event.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+
+ // Bucket boundary pull.
+ vector<shared_ptr<LogEvent>> allData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs);
+ event->write("field1");
+ event->write(15);
+ event->init();
+ allData.push_back(event);
+ valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1);
+
+ // Late condition change event.
+ valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true,
+ NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(1, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket3StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that EVENT_IN_WRONG_BUCKET dump reason is logged for a late accumulate
+ * event (i.e. the accumulate events call occurs in the wrong bucket).
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenAccumulateEventWrongBucket) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Condition change to true.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 50);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }))
+ // Dump report requested.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 100);
+ event->write("field1");
+ event->write(15);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+
+ // Condition change event.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+
+ // Bucket boundary pull.
+ vector<shared_ptr<LogEvent>> allData;
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs);
+ event->write("field1");
+ event->write(15);
+ event->init();
+ allData.push_back(event);
+ valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1);
+
+ allData.clear();
+ event = make_shared<LogEvent>(tagId, bucket2StartTimeNs - 100);
+ event->write("field1");
+ event->write(20);
+ event->init();
+ allData.push_back(event);
+
+ // Late accumulateEvents event.
+ valueProducer->accumulateEvents(allData, bucket2StartTimeNs - 100, bucket2StartTimeNs - 100);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket2StartTimeNs + 100, true /* include recent buckets */, true,
+ NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(1, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket3StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that CONDITION_UNKNOWN dump reason is logged due to an unknown condition
+ * when a metric is initialized.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionUnknown) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Condition change to true.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 50);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }))
+ // Dump report requested.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 100);
+ event->write("field1");
+ event->write(15);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithNoInitialCondition(pullerManager,
+ metric);
+
+ // Condition change event.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucketStartTimeNs + 100, true /* include recent buckets */, true,
+ NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 100), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that PULL_FAILED dump reason is logged due to a pull failure in
+ * #pullAndMatchEventsLocked.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenPullFailed) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Condition change to true.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 50);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }))
+ // Dump report requested, pull fails.
+ .WillOnce(Return(false));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+
+ // Condition change event.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucketStartTimeNs + 100, true /* include recent buckets */, true,
+ NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 100), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that MULTIPLE_BUCKETS_SKIPPED dump reason is logged when a log event
+ * skips over more than one bucket.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenMultipleBucketsSkipped) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Condition change to true.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }))
+ // Dump report requested.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event =
+ make_shared<LogEvent>(tagId, bucket4StartTimeNs + 1000);
+ event->write("field1");
+ event->write(15);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+
+ // Condition change event.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
+
+ // Condition change event that skips forward by three buckets.
+ valueProducer->onConditionChanged(false, bucket4StartTimeNs + 10);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket4StartTimeNs + 1000, true /* include recent buckets */, true,
+ NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::MULTIPLE_BUCKETS_SKIPPED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucket4StartTimeNs + 10), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that BUCKET_TOO_SMALL dump reason is logged when a flushed bucket size
+ * is smaller than the "min_bucket_size_nanos" specified in the metric config.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+ metric.set_min_bucket_size_nanos(10000000000); // 10 seconds
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Condition change to true.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }))
+ // Dump report requested.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event =
+ make_shared<LogEvent>(tagId, bucketStartTimeNs + 9000000);
+ event->write("field1");
+ event->write(15);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+
+ // Condition change event.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucketStartTimeNs + 9000000, true /* include recent buckets */,
+ true, NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::BUCKET_TOO_SMALL, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 9000000), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test multiple bucket drop events in the same bucket.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestMultipleBucketDropEvents) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // Condition change to true.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithNoInitialCondition(pullerManager,
+ metric);
+
+ // Condition change event.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucketStartTimeNs + 1000, true /* include recent buckets */, true,
+ FAST /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(2, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(1);
+ EXPECT_EQ(BucketDropReason::DUMP_REPORT_REQUESTED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 1000), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that the number of logged bucket drop events is capped at the maximum.
+ * The maximum is currently 10 and is set in MetricProducer::maxDropEventsReached().
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestMaxBucketDropEvents) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, _))
+ // First condition change event.
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ for (int i = 0; i < 2000; i++) {
+ shared_ptr<LogEvent> event =
+ make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
+ event->write(i);
+ event->write(i);
+ event->init();
+ data->push_back(event);
+ }
+ return true;
+ }))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Return(false))
+ .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 220);
+ event->write("field1");
+ event->write(10);
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithNoInitialCondition(pullerManager,
+ metric);
+
+ // First condition change event causes guardrail to be reached.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 10);
+
+ // 2-10 condition change events result in failed pulls.
+ valueProducer->onConditionChanged(false, bucketStartTimeNs + 30);
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 50);
+ valueProducer->onConditionChanged(false, bucketStartTimeNs + 70);
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 90);
+ valueProducer->onConditionChanged(false, bucketStartTimeNs + 100);
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 150);
+ valueProducer->onConditionChanged(false, bucketStartTimeNs + 170);
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 190);
+ valueProducer->onConditionChanged(false, bucketStartTimeNs + 200);
+
+ // Condition change event 11
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 220);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ // Because we already have 10 dump events in the current bucket,
+ // this case should not be added to the list of dump events.
+ valueProducer->onDumpReport(bucketStartTimeNs + 1000, true /* include recent buckets */, true,
+ FAST /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ EXPECT_EQ(0, report.value_metrics().data_size());
+ EXPECT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ EXPECT_EQ(10, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 10), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(1);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 30), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(2);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 50), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(3);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 70), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(4);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 90), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(5);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 100), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(6);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 150), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(7);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 170), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(8);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 190), dropEvent.drop_time_millis());
+
+ dropEvent = report.value_metrics().skipped(0).drop_event(9);
+ EXPECT_EQ(BucketDropReason::PULL_FAILED, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs + 200), dropEvent.drop_time_millis());
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index d154b1b..7b651df 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -18,6 +18,23 @@
namespace os {
namespace statsd {
+StatsLogReport outputStreamToProto(ProtoOutputStream* proto) {
+ vector<uint8_t> bytes;
+ bytes.resize(proto->size());
+ size_t pos = 0;
+ sp<ProtoReader> reader = proto->data();
+
+ while (reader->readBuffer() != NULL) {
+ size_t toRead = reader->currentToRead();
+ std::memcpy(&((bytes)[pos]), reader->readBuffer(), toRead);
+ pos += toRead;
+ reader->move(toRead);
+ }
+
+ StatsLogReport report;
+ report.ParseFromArray(bytes.data(), bytes.size());
+ return report;
+}
AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
AtomMatcher atom_matcher;
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index e1e134b..010c194 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -27,8 +27,12 @@
namespace os {
namespace statsd {
+using android::util::ProtoReader;
using google::protobuf::RepeatedPtrField;
+// Converts a ProtoOutputStream to a StatsLogReport proto.
+StatsLogReport outputStreamToProto(ProtoOutputStream* proto);
+
// Create AtomMatcher proto to simply match a specific atom type.
AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
diff --git a/cmds/svc/src/com/android/commands/svc/DataCommand.java b/cmds/svc/src/com/android/commands/svc/DataCommand.java
deleted file mode 100644
index 35510cf..0000000
--- a/cmds/svc/src/com/android/commands/svc/DataCommand.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.commands.svc;
-
-import android.os.ServiceManager;
-import android.os.RemoteException;
-import android.content.Context;
-import com.android.internal.telephony.ITelephony;
-
-public class DataCommand extends Svc.Command {
- public DataCommand() {
- super("data");
- }
-
- public String shortHelp() {
- return "Control mobile data connectivity";
- }
-
- public String longHelp() {
- return shortHelp() + "\n"
- + "\n"
- + "usage: svc data [enable|disable]\n"
- + " Turn mobile data on or off.\n\n";
- }
-
- public void run(String[] args) {
- boolean validCommand = false;
- if (args.length >= 2) {
- boolean flag = false;
- if ("enable".equals(args[1])) {
- flag = true;
- validCommand = true;
- } else if ("disable".equals(args[1])) {
- flag = false;
- validCommand = true;
- }
- if (validCommand) {
- ITelephony phoneMgr
- = ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
- try {
- if (flag) {
- phoneMgr.enableDataConnectivity();
- } else
- phoneMgr.disableDataConnectivity();
- }
- catch (RemoteException e) {
- System.err.println("Mobile data operation failed: " + e);
- }
- return;
- }
- }
- System.err.println(longHelp());
- }
-}
diff --git a/cmds/svc/src/com/android/commands/svc/Svc.java b/cmds/svc/src/com/android/commands/svc/Svc.java
index e602e2a..2ed2678 100644
--- a/cmds/svc/src/com/android/commands/svc/Svc.java
+++ b/cmds/svc/src/com/android/commands/svc/Svc.java
@@ -93,7 +93,6 @@
public static final Command[] COMMANDS = new Command[] {
COMMAND_HELP,
new PowerCommand(),
- new DataCommand(),
// `svc wifi` has been migrated to WifiShellCommand
new UsbCommand(),
new NfcCommand(),
diff --git a/cmds/svc/svc b/cmds/svc/svc
index 60c95c7..95265e8 100755
--- a/cmds/svc/svc
+++ b/cmds/svc/svc
@@ -19,6 +19,20 @@
exit 1
fi
+if [ "x$1" == "xdata" ]; then
+ if [ "x$2" == "xenable" ]; then
+ exec cmd phone data enable
+ elif [ "x$2" == "xdisable" ]; then
+ exec cmd phone data disable
+ else
+ echo "Enable/Disable Mobile Data Connectivity"
+ echo ""
+ echo "usage: svc data [enable|disable]"
+ echo ""
+ fi
+ exit 1
+fi
+
export CLASSPATH=/system/framework/svc.jar
exec app_process /system/bin com.android.commands.svc.Svc "$@"
diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp
index 1173d57..3a26063 100644
--- a/cmds/uiautomator/library/Android.bp
+++ b/cmds/uiautomator/library/Android.bp
@@ -22,6 +22,7 @@
"android.test.runner",
"junit",
"android.test.base",
+ "unsupportedappusage",
],
custom_template: "droiddoc-templates-sdk",
installable: false,
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 8b62e2f..d82b151 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -17,6 +17,8 @@
package android.accessibilityservice;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
@@ -58,6 +60,8 @@
/** @hide */
@IntDef(prefix = { "GESTURE_" }, value = {
+ GESTURE_DOUBLE_TAP,
+ GESTURE_DOUBLE_TAP_AND_HOLD,
GESTURE_SWIPE_UP,
GESTURE_SWIPE_UP_AND_LEFT,
GESTURE_SWIPE_UP_AND_DOWN,
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 47fdcde..c0fee6e 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -21,8 +21,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.UnsupportedAppUsage;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
@@ -300,6 +300,18 @@
public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
/**
+ * The user has performed a double tap gesture on the touch screen.
+ * @hide
+ */
+ public static final int GESTURE_DOUBLE_TAP = 17;
+
+ /**
+ * The user has performed a double tap and hold gesture on the touch screen.
+ * @hide
+ */
+ public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18;
+
+ /**
* The {@link Intent} that must be declared as handled by the service.
*/
public static final String SERVICE_INTERFACE =
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 7fd01db..5e2c1fa 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -22,9 +22,9 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
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.pm.PackageManager;
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index f4cadfd..d79740b 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -141,6 +141,16 @@
}
/**
+ * The {@link ComponentName} of the accessibility shortcut target.
+ *
+ * @return The component name
+ */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
* The localized summary of the accessibility shortcut target.
*
* @return The localized summary if available, and {@code null} if a summary
diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java
index c822d20..9a18880 100644
--- a/core/java/android/accounts/Account.java
+++ b/core/java/android/accounts/Account.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.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/accounts/AccountAndUser.java b/core/java/android/accounts/AccountAndUser.java
index b0d5343..fd67394 100644
--- a/core/java/android/accounts/AccountAndUser.java
+++ b/core/java/android/accounts/AccountAndUser.java
@@ -16,7 +16,7 @@
package android.accounts;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Used to store the Account and the UserId this account is associated with.
diff --git a/core/java/android/accounts/AccountAuthenticatorResponse.java b/core/java/android/accounts/AccountAuthenticatorResponse.java
index bb2e327..a2a5799 100644
--- a/core/java/android/accounts/AccountAuthenticatorResponse.java
+++ b/core/java/android/accounts/AccountAuthenticatorResponse.java
@@ -16,10 +16,10 @@
package android.accounts;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index f4e465a..8fe2f12 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -25,8 +25,8 @@
import android.annotation.Size;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -1943,35 +1943,6 @@
}
/**
- * @hide
- * Removes the shared account.
- * @param account the account to remove
- * @param user the user to remove the account from
- * @return
- */
- public boolean removeSharedAccount(final Account account, UserHandle user) {
- try {
- boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
- return val;
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
- * @hide
- * @param user
- * @return
- */
- public Account[] getSharedAccounts(UserHandle user) {
- try {
- return mService.getSharedAccountsAsUser(user.getIdentifier());
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- }
-
- /**
* Confirms that the user knows the password for an account to make extra
* sure they are the owner of the account. The user-entered password can
* be supplied directly, otherwise the authenticator for this account type
diff --git a/core/java/android/accounts/AuthenticatorDescription.java b/core/java/android/accounts/AuthenticatorDescription.java
index 5556394..b7bf11d 100644
--- a/core/java/android/accounts/AuthenticatorDescription.java
+++ b/core/java/android/accounts/AuthenticatorDescription.java
@@ -16,10 +16,10 @@
package android.accounts;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
/**
* A {@link Parcelable} value type that contains information about an account authenticator.
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 4cf0a20..0127138 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -80,14 +80,11 @@
String authTokenType);
/* Shared accounts */
- Account[] getSharedAccountsAsUser(int userId);
- boolean removeSharedAccountAsUser(in Account account, int userId);
void addSharedAccountsFromParentUser(int parentUserId, int userId, String opPackageName);
/* Account renaming. */
void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName);
String getPreviousName(in Account account);
- boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
/* Add account in two steps. */
void startAddAccountSession(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 17d54d2..3cdd691 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -17,7 +17,7 @@
package android.animation;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ConstantState;
diff --git a/core/java/android/animation/ArgbEvaluator.java b/core/java/android/animation/ArgbEvaluator.java
index 5b69d18..9519ddd 100644
--- a/core/java/android/animation/ArgbEvaluator.java
+++ b/core/java/android/animation/ArgbEvaluator.java
@@ -16,7 +16,7 @@
package android.animation;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* This evaluator can be used to perform type interpolation between integer
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index c753710..21f0b6b 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -16,7 +16,7 @@
package android.animation;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 764e599..ca37e9b 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -19,7 +19,7 @@
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Looper;
import android.os.Trace;
diff --git a/core/java/android/annotation/CallbackExecutor.java b/core/java/android/annotation/CallbackExecutor.java
index 5671a3d..4258f73 100644
--- a/core/java/android/annotation/CallbackExecutor.java
+++ b/core/java/android/annotation/CallbackExecutor.java
@@ -19,9 +19,6 @@
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.content.Context;
-import android.os.AsyncTask;
-
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.concurrent.Executor;
@@ -30,9 +27,10 @@
* @paramDoc Callback and listener events are dispatched through this
* {@link Executor}, providing an easy way to control which thread is
* used. To dispatch events through the main thread of your
- * application, you can use {@link Context#getMainExecutor()}. To
- * dispatch events through a shared thread pool, you can use
- * {@link AsyncTask#THREAD_POOL_EXECUTOR}.
+ * application, you can use
+ * {@link android.content.Context#getMainExecutor() Context.getMainExecutor()}.
+ * To dispatch events through a shared thread pool, you can use
+ * {@link android.os.AsyncTask#THREAD_POOL_EXECUTOR AsyncTask#THREAD_POOL_EXECUTOR}.
* @hide
*/
@Retention(SOURCE)
diff --git a/core/java/android/annotation/Hide.java b/core/java/android/annotation/Hide.java
new file mode 100644
index 0000000..c8e5a4a
--- /dev/null
+++ b/core/java/android/annotation/Hide.java
@@ -0,0 +1,41 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that an API is hidden by default, in a similar fashion to the
+ * <pre>@hide</pre> javadoc tag.
+ *
+ * <p>Note that, in order for this to work, metalava has to be invoked with
+ * the flag {@code --hide-annotation android.annotation.Hide}.
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.CLASS)
+public @interface Hide {
+}
diff --git a/core/java/android/annotation/OWNERS b/core/java/android/annotation/OWNERS
index e07028b..8aceb56 100644
--- a/core/java/android/annotation/OWNERS
+++ b/core/java/android/annotation/OWNERS
@@ -1,2 +1,3 @@
tnorbye@google.com
+aurimas@google.com
per-file UnsupportedAppUsage.java = mathewi@google.com, dbrazdil@google.com, atrost@google.com, andreionea@google.com
diff --git a/core/java/android/annotation/RequiresPermission.java b/core/java/android/annotation/RequiresPermission.java
index 59d419f..1d89e31 100644
--- a/core/java/android/annotation/RequiresPermission.java
+++ b/core/java/android/annotation/RequiresPermission.java
@@ -15,11 +15,6 @@
*/
package android.annotation;
-import android.content.Intent;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
@@ -27,6 +22,9 @@
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
/**
* Denotes that the annotated element requires (or may require) one or more permissions.
* <p/>
@@ -57,7 +55,8 @@
* <p>
* When specified on a parameter, the annotation indicates that the method requires
* a permission which depends on the value of the parameter. For example, consider
- * {@link android.app.Activity#startActivity(Intent)}:
+ * {@link android.app.Activity#startActivity(android.content.Intent)
+ * Activity#startActivity(Intent)}:
* <pre>{@code
* public void startActivity(@RequiresPermission Intent intent) { ... }
* }</pre>
diff --git a/core/java/android/annotation/SystemApi.java b/core/java/android/annotation/SystemApi.java
index e96ff01..2cb93e4 100644
--- a/core/java/android/annotation/SystemApi.java
+++ b/core/java/android/annotation/SystemApi.java
@@ -41,4 +41,49 @@
@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemApi {
+ enum Client {
+ /**
+ * Specifies that the intended clients of a SystemApi are privileged apps.
+ * This is the default value for {@link #client}. This implies
+ * MODULE_APPS and MODULE_LIBRARIES as well, which means that APIs will also
+ * be available to module apps and jars.
+ */
+ PRIVILEGED_APPS,
+
+ /**
+ * Specifies that the intended clients of a SystemApi are modules implemented
+ * as apps, like the NetworkStack app. This implies MODULE_LIBRARIES as well,
+ * which means that APIs will also be available to module jars.
+ */
+ MODULE_APPS,
+
+ /**
+ * Specifies that the intended clients of a SystemApi are modules implemented
+ * as libraries, like the conscrypt.jar in the conscrypt APEX.
+ */
+ MODULE_LIBRARIES
+ }
+
+ enum Process {
+ /**
+ * Specifies that the SystemAPI is available in every Java processes.
+ * This is the default value for {@link #process}.
+ */
+ ALL,
+
+ /**
+ * Specifies that the SystemAPI is available only in the system server process.
+ */
+ SYSTEM_SERVER
+ }
+
+ /**
+ * The intended client of this SystemAPI.
+ */
+ Client client() default android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+ /**
+ * The process(es) that this SystemAPI is available
+ */
+ Process process() default android.annotation.SystemApi.Process.ALL;
}
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index e573279..504364c 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -22,7 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 577272e..9e0c2fc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -33,10 +33,10 @@
import android.annotation.StyleRes;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.VoiceInteractor.Request;
import android.app.admin.DevicePolicyManager;
import android.app.assist.AssistContent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -766,9 +766,19 @@
private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";
private static final String AUTO_FILL_AUTH_WHO_PREFIX = "@android:autoFillAuth:";
-
private static final String KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME = "com.android.systemui";
+ private static final int LOG_AM_ON_CREATE_CALLED = 30057;
+ private static final int LOG_AM_ON_START_CALLED = 30059;
+ private static final int LOG_AM_ON_RESUME_CALLED = 30022;
+ private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
+ private static final int LOG_AM_ON_STOP_CALLED = 30049;
+ private static final int LOG_AM_ON_RESTART_CALLED = 30058;
+ private static final int LOG_AM_ON_DESTROY_CALLED = 30060;
+ private static final int LOG_AM_ON_ACTIVITY_RESULT_CALLED = 30062;
+ private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
+ private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
+
private static class ManagedDialog {
Dialog mDialog;
Bundle mArgs;
@@ -2439,8 +2449,11 @@
* {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
*/
public final void requestShowKeyboardShortcuts() {
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
- intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME);
+ intent.setPackage(sysuiComponent.getPackageName());
sendBroadcastAsUser(intent, Process.myUserHandle());
}
@@ -2448,8 +2461,11 @@
* Dismiss the Keyboard Shortcuts screen.
*/
public final void dismissKeyboardShortcutsHelper() {
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
Intent intent = new Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS);
- intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME);
+ intent.setPackage(sysuiComponent.getPackageName());
sendBroadcastAsUser(intent, Process.myUserHandle());
}
@@ -8658,7 +8674,6 @@
* @hide
*/
@RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
- @UnsupportedAppUsage
public void registerRemoteAnimations(RemoteAnimationDefinition definition) {
try {
ActivityTaskManager.getService().registerRemoteAnimations(mToken, definition);
@@ -8667,6 +8682,20 @@
}
}
+ /**
+ * Unregisters all remote animations for this activity.
+ *
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public void unregisterRemoteAnimations() {
+ try {
+ ActivityTaskManager.getService().unregisterRemoteAnimations(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
diff --git a/core/java/android/app/ActivityGroup.java b/core/java/android/app/ActivityGroup.java
index d4aa01b..cb06eea 100644
--- a/core/java/android/app/ActivityGroup.java
+++ b/core/java/android/app/ActivityGroup.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Bundle;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 590b3db..3f9f7fb 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -28,7 +28,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;
@@ -1120,8 +1120,8 @@
}
/**
- * Copies this the values from another TaskDescription, but preserves the hidden fields
- * if they weren't set on {@code other}
+ * Copies values from another TaskDescription, but preserves the hidden fields if they
+ * weren't set on {@code other}. Public fields will be overwritten anyway.
* @hide
*/
public void copyFromPreserveHiddenFields(TaskDescription other) {
@@ -1130,6 +1130,7 @@
mIconRes = other.mIconRes;
mIconFilename = other.mIconFilename;
mColorPrimary = other.mColorPrimary;
+
if (other.mColorBackground != 0) {
mColorBackground = other.mColorBackground;
}
@@ -1139,12 +1140,20 @@
if (other.mNavigationBarColor != 0) {
mNavigationBarColor = other.mNavigationBarColor;
}
+
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
other.mEnsureNavigationBarContrastWhenTransparent;
- mResizeMode = other.mResizeMode;
- mMinWidth = other.mMinWidth;
- mMinHeight = other.mMinHeight;
+
+ if (other.mResizeMode != RESIZE_MODE_RESIZEABLE) {
+ mResizeMode = other.mResizeMode;
+ }
+ if (other.mMinWidth != -1) {
+ mMinWidth = other.mMinWidth;
+ }
+ if (other.mMinHeight != -1) {
+ mMinHeight = other.mMinHeight;
+ }
}
private TaskDescription(Parcel source) {
@@ -2097,6 +2106,113 @@
return new TaskSnapshot[size];
}
};
+
+ /** Builder for a {@link TaskSnapshot} object */
+ public static final class Builder {
+ private long mId;
+ private ComponentName mTopActivity;
+ private GraphicBuffer mSnapshot;
+ private ColorSpace mColorSpace;
+ private int mOrientation;
+ private Rect mContentInsets;
+ private boolean mReducedResolution;
+ private float mScaleFraction;
+ private boolean mIsRealSnapshot;
+ private int mWindowingMode;
+ private int mSystemUiVisibility;
+ private boolean mIsTranslucent;
+ private int mPixelFormat;
+
+ public Builder setId(long id) {
+ mId = id;
+ return this;
+ }
+
+ public Builder setTopActivityComponent(ComponentName name) {
+ mTopActivity = name;
+ return this;
+ }
+
+ public Builder setSnapshot(GraphicBuffer buffer) {
+ mSnapshot = buffer;
+ return this;
+ }
+
+ public Builder setColorSpace(ColorSpace colorSpace) {
+ mColorSpace = colorSpace;
+ return this;
+ }
+
+ public Builder setOrientation(int orientation) {
+ mOrientation = orientation;
+ return this;
+ }
+
+ public Builder setContentInsets(Rect contentInsets) {
+ mContentInsets = contentInsets;
+ return this;
+ }
+
+ public Builder setReducedResolution(boolean reducedResolution) {
+ mReducedResolution = reducedResolution;
+ return this;
+ }
+
+ public float getScaleFraction() {
+ return mScaleFraction;
+ }
+
+ public Builder setScaleFraction(float scaleFraction) {
+ mScaleFraction = scaleFraction;
+ return this;
+ }
+
+ public Builder setIsRealSnapshot(boolean realSnapshot) {
+ mIsRealSnapshot = realSnapshot;
+ return this;
+ }
+
+ public Builder setWindowingMode(int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+
+ public Builder setSystemUiVisibility(int systemUiVisibility) {
+ mSystemUiVisibility = systemUiVisibility;
+ return this;
+ }
+
+ public Builder setIsTranslucent(boolean isTranslucent) {
+ mIsTranslucent = isTranslucent;
+ return this;
+ }
+
+ public int getPixelFormat() {
+ return mPixelFormat;
+ }
+
+ public Builder setPixelFormat(int pixelFormat) {
+ mPixelFormat = pixelFormat;
+ return this;
+ }
+
+ public TaskSnapshot build() {
+ return new TaskSnapshot(
+ mId,
+ mTopActivity,
+ mSnapshot,
+ mColorSpace,
+ mOrientation,
+ mContentInsets,
+ mReducedResolution,
+ mScaleFraction,
+ mIsRealSnapshot,
+ mWindowingMode,
+ mSystemUiVisibility,
+ mIsTranslucent);
+
+ }
+ }
}
/** @hide */
@@ -4417,4 +4533,31 @@
}
}
}
+
+ /**
+ * Get packages of bugreport-whitelisted apps to handle a bug report.
+ *
+ * @return packages of bugreport-whitelisted apps to handle a bug report.
+ * @hide
+ */
+ public List<String> getBugreportWhitelistedPackages() {
+ try {
+ return getService().getBugreportWhitelistedPackages();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Method for the app to tell system that it's wedged and would like to trigger an ANR.
+ *
+ * @param reason The description of that what happened
+ */
+ public void appNotResponding(@NonNull final String reason) {
+ try {
+ getService().appNotResponding(reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 607ef18..b9eb957 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -15,7 +15,7 @@
*/
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.IBinder;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index f91453e..4aacf48 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -25,7 +25,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
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;
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index ac8c9f4..122004c 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -19,7 +19,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 49a8e2f3..be14556 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -32,7 +32,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.BackupAgent;
@@ -46,6 +45,7 @@
import android.app.servertransaction.PendingTransactionActions.StopInfo;
import android.app.servertransaction.TransactionExecutor;
import android.app.servertransaction.TransactionExecutorHelper;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
@@ -112,6 +112,7 @@
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.TelephonyServiceManager;
import android.os.Trace;
import android.os.UserHandle;
import android.permission.IPermissionManager;
@@ -128,6 +129,7 @@
import android.system.ErrnoException;
import android.system.OsConstants;
import android.system.StructStat;
+import android.telephony.TelephonyFrameworkInitializer;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -3415,7 +3417,7 @@
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
final int displayId;
try {
- displayId = ActivityTaskManager.getService().getActivityDisplayId(r.token);
+ displayId = ActivityTaskManager.getService().getDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4405,7 +4407,9 @@
r.newConfig = null;
}
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
- WindowManager.LayoutParams l = r.window.getAttributes();
+ ViewRootImpl impl = r.window.getDecorView().getViewRootImpl();
+ WindowManager.LayoutParams l = impl != null
+ ? impl.mWindowAttributes : r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
@@ -7395,6 +7399,24 @@
super.remove(path);
}
}
+
+ @Override
+ public void rename(String oldPath, String newPath) throws ErrnoException {
+ try {
+ super.rename(oldPath, newPath);
+ } catch (ErrnoException e) {
+ if (e.errno == OsConstants.EXDEV && oldPath.startsWith("/storage/")) {
+ Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath);
+ try {
+ Files.move(new File(oldPath).toPath(), new File(newPath).toPath());
+ } catch (IOException e2) {
+ throw e;
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
}
public static void main(String[] args) {
@@ -7414,6 +7436,9 @@
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
+ // Call per-process mainline module initialization.
+ initializeMainlineModules();
+
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
@@ -7448,6 +7473,14 @@
throw new RuntimeException("Main thread loop unexpectedly exited");
}
+ /**
+ * Call various initializer APIs in mainline modules that need to be called when each process
+ * starts.
+ */
+ public static void initializeMainlineModules() {
+ TelephonyFrameworkInitializer.setTelephonyServiceManager(new TelephonyServiceManager());
+ }
+
private void purgePendingResources() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "purgePendingResources");
nPurgePendingResources();
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index d8ddf218..c03413c 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -21,7 +21,7 @@
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index bfc216a..4c34737 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -21,7 +21,7 @@
import android.annotation.DrawableRes;
import android.annotation.StringRes;
import android.annotation.StyleRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.ResourceId;
diff --git a/core/java/android/app/AppGlobals.java b/core/java/android/app/AppGlobals.java
index 8312327..81e1565 100644
--- a/core/java/android/app/AppGlobals.java
+++ b/core/java/android/app/AppGlobals.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.IPackageManager;
import android.permission.IPermissionManager;
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
index 2240302..b4dee2e 100644
--- a/core/java/android/app/AppOpsManager.aidl
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -17,6 +17,9 @@
package android.app;
parcelable AppOpsManager.PackageOps;
+parcelable AppOpsManager.NoteOpEventProxyInfo;
+parcelable AppOpsManager.NoteOpEvent;
+parcelable AppOpsManager.OpFeatureEntry;
parcelable AppOpsManager.OpEntry;
parcelable AppOpsManager.HistoricalOp;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5032f77..86a5c76 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -26,8 +26,8 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.usage.UsageStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -48,9 +48,9 @@
import android.os.RemoteException;
import android.os.UserManager;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
-import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -63,6 +63,8 @@
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.ZygoteInit;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
import com.android.internal.util.Preconditions;
import java.lang.annotation.ElementType;
@@ -83,7 +85,6 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;
-import java.util.function.ToLongFunction;
/**
* API for interacting with "application operation" tracking.
@@ -138,7 +139,7 @@
@GuardedBy("sLock")
private static @Nullable AppOpsCollector sNotedAppOpsCollector;
- static IBinder sToken;
+ static IBinder sClientId;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -846,10 +847,12 @@
public static final int OP_ACCESS_MEDIA_LOCATION = 90;
/** @hide Query all apps on device, regardless of declarations in the calling app manifest */
public static final int OP_QUERY_ALL_PACKAGES = 91;
+ /** @hide Access all external storage */
+ public static final int OP_MANAGE_EXTERNAL_STORAGE = 92;
/** @hide */
@UnsupportedAppUsage
- public static final int _NUM_OP = 92;
+ public static final int _NUM_OP = 93;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1135,6 +1138,10 @@
public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
/** @hide Query all packages on device */
public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
+ /** @hide Access all external storage */
+ @SystemApi
+ public static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
+ "android:manage_external_storage";
/** {@link #sAppOpsToNote} not initialized yet for this op */
@@ -1317,6 +1324,7 @@
OP_READ_DEVICE_IDENTIFIERS, // READ_DEVICE_IDENTIFIERS
OP_ACCESS_MEDIA_LOCATION, // ACCESS_MEDIA_LOCATION
OP_QUERY_ALL_PACKAGES, // QUERY_ALL_PACKAGES
+ OP_MANAGE_EXTERNAL_STORAGE, // MANAGE_EXTERNAL_STORAGE
};
/**
@@ -1415,6 +1423,7 @@
OPSTR_READ_DEVICE_IDENTIFIERS,
OPSTR_ACCESS_MEDIA_LOCATION,
OPSTR_QUERY_ALL_PACKAGES,
+ OPSTR_MANAGE_EXTERNAL_STORAGE,
};
/**
@@ -1514,6 +1523,7 @@
"READ_DEVICE_IDENTIFIERS",
"ACCESS_MEDIA_LOCATION",
"QUERY_ALL_PACKAGES",
+ "MANAGE_EXTERNAL_STORAGE"
};
/**
@@ -1614,6 +1624,7 @@
null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
Manifest.permission.ACCESS_MEDIA_LOCATION,
null, // no permission for OP_QUERY_ALL_PACKAGES
+ null, // no permission for OP_MANAGE_EXTERNAL_STORAGE
};
/**
@@ -1714,6 +1725,7 @@
null, // READ_DEVICE_IDENTIFIERS
null, // ACCESS_MEDIA_LOCATION
null, // QUERY_ALL_PACKAGES
+ null, // MANAGE_EXTERNAL_STORAGE
};
/**
@@ -1813,6 +1825,7 @@
false, // READ_DEVICE_IDENTIFIERS
false, // ACCESS_MEDIA_LOCATION
false, // QUERY_ALL_PACKAGES
+ false, // MANAGE_EXTERNAL_STORAGE
};
/**
@@ -1911,6 +1924,7 @@
AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS
AppOpsManager.MODE_ALLOWED, // ALLOW_MEDIA_LOCATION
AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
+ AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE
};
/**
@@ -2008,11 +2022,12 @@
false, // WRITE_MEDIA_VIDEO
false, // READ_MEDIA_IMAGES
false, // WRITE_MEDIA_IMAGES
- false, // LEGACY_STORAGE
+ true, // LEGACY_STORAGE
false, // ACCESS_ACCESSIBILITY
false, // READ_DEVICE_IDENTIFIERS
false, // ACCESS_MEDIA_LOCATION
false, // QUERY_ALL_PACKAGES
+ false, // MANAGE_EXTERNAL_STORAGE
};
/**
@@ -2245,6 +2260,7 @@
* Class holding all of the operation information associated with an app.
* @hide
*/
+ @TestApi
@SystemApi
public static final class PackageOps implements Parcelable {
private final String mPackageName;
@@ -2288,7 +2304,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mPackageName);
dest.writeInt(mUid);
dest.writeInt(mEntries.size());
@@ -2319,537 +2335,878 @@
}
/**
- * Class holding the information about one unique operation of a
- * {@link Context#createFeatureContext(String) feature}.
+ * Proxy information for a {@link #noteOp} event
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ @Immutable
+ @DataClass(genHiddenConstructor = true)
+ public static final class OpEventProxyInfo implements Parcelable {
+ /** UID of the proxy app that noted the op */
+ private final @IntRange(from = 0) int mUid;
+ /** Package of the proxy that noted the op */
+ private final @Nullable String mPackageName;
+ /** ID of the feature of the proxy that noted the op */
+ private final @Nullable String mFeatureId;
+
+
+
+ // 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 OpEventProxyInfo.
+ *
+ * @param uid
+ * UID of the proxy app that noted the op
+ * @param packageName
+ * Package of the proxy that noted the op
+ * @param featureId
+ * ID of the feature of the proxy that noted the op
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public OpEventProxyInfo(
+ @IntRange(from = 0) int uid,
+ @Nullable String packageName,
+ @Nullable String featureId) {
+ this.mUid = uid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mUid,
+ "from", 0);
+ this.mPackageName = packageName;
+ this.mFeatureId = featureId;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * UID of the proxy app that noted the op
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Package of the proxy that noted the op
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * ID of the feature of the proxy that noted the op
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getFeatureId() {
+ return mFeatureId;
+ }
+
+ @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 (mPackageName != null) flg |= 0x2;
+ if (mFeatureId != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mUid);
+ if (mPackageName != null) dest.writeString(mPackageName);
+ if (mFeatureId != null) dest.writeString(mFeatureId);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ OpEventProxyInfo(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int uid = in.readInt();
+ String packageName = (flg & 0x2) == 0 ? null : in.readString();
+ String featureId = (flg & 0x4) == 0 ? null : in.readString();
+
+ this.mUid = uid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mUid,
+ "from", 0);
+ this.mPackageName = packageName;
+ this.mFeatureId = featureId;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<OpEventProxyInfo> CREATOR
+ = new Parcelable.Creator<OpEventProxyInfo>() {
+ @Override
+ public OpEventProxyInfo[] newArray(int size) {
+ return new OpEventProxyInfo[size];
+ }
+
+ @Override
+ public OpEventProxyInfo createFromParcel(@NonNull Parcel in) {
+ return new OpEventProxyInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1576194071700L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=0L) int mUid\nprivate final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nclass OpEventProxyInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
+ /**
+ * Description of a {@link #noteOp} or {@link #startOp} event
+ *
+ * @hide
+ */
+ @Immutable
+ //@DataClass codegen verifier is broken
+ public static final class NoteOpEvent implements Parcelable {
+ /** Time of noteOp event */
+ public final @IntRange(from = 0) long noteTime;
+ /** The duration of this event (in case this is a startOp event, -1 otherwise). */
+ public final @IntRange(from = -1) long duration;
+ /** Proxy information of the noteOp event */
+ public final @Nullable OpEventProxyInfo proxy;
+
+
+ // 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 NoteOpEvent.
+ *
+ * @param noteTime
+ * Time of noteOp event
+ * @param duration
+ * The duration of this event (in case this is a startOp event, -1 otherwise).
+ * @param proxy
+ * Proxy information of the noteOp event
+ */
+ @DataClass.Generated.Member
+ public NoteOpEvent(
+ @IntRange(from = 0) long noteTime,
+ @IntRange(from = -1) long duration,
+ @Nullable OpEventProxyInfo proxy) {
+ this.noteTime = noteTime;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, noteTime,
+ "from", 0);
+ this.duration = duration;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, duration,
+ "from", -1);
+ this.proxy = proxy;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @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 (proxy != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeLong(noteTime);
+ dest.writeLong(duration);
+ if (proxy != null) dest.writeTypedObject(proxy, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ NoteOpEvent(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ long _noteTime = in.readLong();
+ long _duration = in.readLong();
+ OpEventProxyInfo _proxy = (flg & 0x4) == 0 ? null : (OpEventProxyInfo) in.readTypedObject(
+ OpEventProxyInfo.CREATOR);
+
+ this.noteTime = _noteTime;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, noteTime,
+ "from", 0);
+ this.duration = _duration;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, duration,
+ "from", -1);
+ this.proxy = _proxy;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<NoteOpEvent> CREATOR
+ = new Parcelable.Creator<NoteOpEvent>() {
+ @Override
+ public NoteOpEvent[] newArray(int size) {
+ return new NoteOpEvent[size];
+ }
+
+ @Override
+ public NoteOpEvent createFromParcel(@NonNull Parcel in) {
+ return new NoteOpEvent(in);
+ }
+ };
+
+ /*
+ @DataClass.Generated(
+ time = 1574809856220L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java",
+ inputSignatures = "public final @android.annotation.IntRange(from=0L) long noteTime\npublic final @android.annotation.IntRange(from=-1) long duration\npublic final @android.annotation.Nullable android.app.NoteOpEventProxyInfo proxy\nclass NoteOpEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass")
+ @Deprecated
+ private void __metadata() {}
+ */
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
+ /**
+ * Last {@link #noteOp} and {@link #startOp} events performed for a single op and a specific
+ * {@link Context#createFeatureContext(String) feature} for all uidModes and opFlags.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ @Immutable
+ // @DataClass(genHiddenConstructor = true) codegen verifier is broken
+ @DataClass.Suppress({"getAccessEvents", "getRejectEvents", "getOp"})
+ public static final class OpFeatureEntry implements Parcelable {
+ /** The code of the op */
+ private final @IntRange(from = 0, to = _NUM_OP - 1) int mOp;
+ /** Whether the op is running */
+ private final boolean mRunning;
+ /** The access events */
+ @DataClass.ParcelWith(LongSparseArrayParceling.class)
+ private final @Nullable LongSparseArray<NoteOpEvent> mAccessEvents;
+ /** The rejection events */
+ @DataClass.ParcelWith(LongSparseArrayParceling.class)
+ private final @Nullable LongSparseArray<NoteOpEvent> mRejectEvents;
+
+ /**
+ * Returns all keys for which we have events.
+ *
+ * @hide
+ */
+ public @NonNull ArraySet<Long> collectKeys() {
+ ArraySet<Long> keys = new ArraySet<>();
+
+ if (mAccessEvents != null) {
+ int numEvents = mAccessEvents.size();
+ for (int i = 0; i < numEvents; i++) {
+ keys.add(mAccessEvents.keyAt(i));
+ }
+ }
+
+ if (mRejectEvents != null) {
+ int numEvents = mRejectEvents.size();
+ for (int i = 0; i < numEvents; i++) {
+ keys.add(mRejectEvents.keyAt(i));
+ }
+ }
+
+ return keys;
+ }
+
+ /**
+ * Return the last access time.
+ *
+ * @param flags The op flags
+ *
+ * @return the last access time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastAccessForegroundTime(int)
+ * @see #getLastAccessBackgroundTime(int)
+ * @see #getLastAccessTime(int, int, int)
+ * @see OpEntry#getLastAccessTime(int)
+ */
+ public long getLastAccessTime(@OpFlags int flags) {
+ return getLastAccessTime(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, flags);
+ }
+
+ /**
+ * Return the last foreground access time.
+ *
+ * @param flags The op flags
+ *
+ * @return the last access time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastAccessTime(int)
+ * @see #getLastAccessBackgroundTime(int)
+ * @see #getLastAccessTime(int, int, int)
+ * @see OpEntry#getLastAccessForegroundTime(int)
+ */
+ public long getLastAccessForegroundTime(@OpFlags int flags) {
+ return getLastAccessTime(MAX_PRIORITY_UID_STATE, resolveFirstUnrestrictedUidState(mOp),
+ flags);
+ }
+
+ /**
+ * Return the last background access time.
+ *
+ * @param flags The op flags
+ *
+ * @return the last access time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastAccessTime(int)
+ * @see #getLastAccessForegroundTime(int)
+ * @see #getLastAccessTime(int, int, int)
+ * @see OpEntry#getLastAccessBackgroundTime(int)
+ */
+ public long getLastAccessBackgroundTime(@OpFlags int flags) {
+ return getLastAccessTime(resolveLastRestrictedUidState(mOp), MIN_PRIORITY_UID_STATE,
+ flags);
+ }
+
+ /**
+ * Return the last access event.
+ *
+ * @param flags The op flags
+ *
+ * @return the last access event of {@code null}
+ */
+ private @Nullable NoteOpEvent getLastAccessEvent(@UidState int fromUidState,
+ @UidState int toUidState, @OpFlags int flags) {
+ return getLastEvent(mAccessEvents, fromUidState, toUidState, flags);
+ }
+
+ /**
+ * Return the last access time.
+ *
+ * @param fromUidState The lowest UID state for which to query
+ * @param toUidState The highest UID state for which to query (inclusive)
+ * @param flags The op flags
+ *
+ * @return the last access time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastAccessTime(int)
+ * @see #getLastAccessForegroundTime(int)
+ * @see #getLastAccessBackgroundTime(int)
+ * @see OpEntry#getLastAccessTime(int, int, int)
+ */
+ public long getLastAccessTime(@UidState int fromUidState, @UidState int toUidState,
+ @OpFlags int flags) {
+ NoteOpEvent lastEvent = getLastAccessEvent(fromUidState, toUidState, flags);
+ if (lastEvent == null) {
+ return -1;
+ }
+
+ return lastEvent.noteTime;
+ }
+
+ /**
+ * Return the last rejection time.
+ *
+ * @param flags The op flags
+ *
+ * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastRejectForegroundTime(int)
+ * @see #getLastRejectBackgroundTime(int)
+ * @see #getLastRejectTime(int, int, int)
+ * @see OpEntry#getLastRejectTime(int)
+ */
+ public long getLastRejectTime(@OpFlags int flags) {
+ return getLastRejectTime(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, flags);
+ }
+
+ /**
+ * Return the last foreground rejection time.
+ *
+ * @param flags The op flags
+ *
+ * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastRejectTime(int)
+ * @see #getLastRejectBackgroundTime(int)
+ * @see #getLastRejectTime(int, int, int)
+ * @see OpEntry#getLastRejectForegroundTime(int)
+ */
+ public long getLastRejectForegroundTime(@OpFlags int flags) {
+ return getLastRejectTime(MAX_PRIORITY_UID_STATE, resolveFirstUnrestrictedUidState(mOp),
+ flags);
+ }
+
+ /**
+ * Return the last background rejection time.
+ *
+ * @param flags The op flags
+ *
+ * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastRejectTime(int)
+ * @see #getLastRejectForegroundTime(int)
+ * @see #getLastRejectTime(int, int, int)
+ * @see OpEntry#getLastRejectBackgroundTime(int)
+ */
+ public long getLastRejectBackgroundTime(@OpFlags int flags) {
+ return getLastRejectTime(resolveLastRestrictedUidState(mOp), MIN_PRIORITY_UID_STATE,
+ flags);
+ }
+
+ /**
+ * Return the last background rejection event.
+ *
+ * @param flags The op flags
+ *
+ * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastRejectTime(int)
+ * @see #getLastRejectForegroundTime(int)
+ * @see #getLastRejectBackgroundTime(int)
+ * @see OpEntry#getLastRejectTime(int, int, int)
+ */
+ private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState,
+ @UidState int toUidState, @OpFlags int flags) {
+ return getLastEvent(mRejectEvents, fromUidState, toUidState, flags);
+ }
+
+ /**
+ * Return the last rejection time.
+ *
+ * @param fromUidState The lowest UID state for which to query
+ * @param toUidState The highest UID state for which to query (inclusive)
+ * @param flags The op flags
+ *
+ * @return the last access time (in milliseconds since epoch) or {@code -1}
+ *
+ * @see #getLastRejectTime(int)
+ * @see #getLastRejectForegroundTime(int)
+ * @see #getLastRejectForegroundTime(int)
+ * @see #getLastRejectTime(int, int, int)
+ * @see OpEntry#getLastRejectTime(int, int, int)
+ */
+ public long getLastRejectTime(@UidState int fromUidState, @UidState int toUidState,
+ @OpFlags int flags) {
+ NoteOpEvent lastEvent = getLastRejectEvent(fromUidState, toUidState, flags);
+ if (lastEvent == null) {
+ return -1;
+ }
+
+ return lastEvent.noteTime;
+ }
+
+ /**
+ * Return the duration in milliseconds of the last the access.
+ *
+ * @param flags The op flags
+ *
+ * @return the duration in milliseconds or {@code -1}
+ *
+ * @see #getLastForegroundDuration(int)
+ * @see #getLastBackgroundDuration(int)
+ * @see #getLastDuration(int, int, int)
+ * @see OpEntry#getLastDuration(int)
+ */
+ public long getLastDuration(@OpFlags int flags) {
+ return getLastDuration(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, flags);
+ }
+
+ /**
+ * Return the duration in milliseconds of the last foreground access.
+ *
+ * @param flags The op flags
+ *
+ * @return the duration in milliseconds or {@code -1}
+ *
+ * @see #getLastDuration(int)
+ * @see #getLastBackgroundDuration(int)
+ * @see #getLastDuration(int, int, int)
+ * @see OpEntry#getLastForegroundDuration(int)
+ */
+ public long getLastForegroundDuration(@OpFlags int flags) {
+ return getLastDuration(MAX_PRIORITY_UID_STATE, resolveFirstUnrestrictedUidState(mOp),
+ flags);
+ }
+
+ /**
+ * Return the duration in milliseconds of the last background access.
+ *
+ * @param flags The op flags
+ *
+ * @return the duration in milliseconds or {@code -1}
+ *
+ * @see #getLastDuration(int)
+ * @see #getLastForegroundDuration(int)
+ * @see #getLastDuration(int, int, int)
+ * @see OpEntry#getLastBackgroundDuration(int)
+ */
+ public long getLastBackgroundDuration(@OpFlags int flags) {
+ return getLastDuration(resolveLastRestrictedUidState(mOp), MIN_PRIORITY_UID_STATE,
+ flags);
+ }
+
+ /**
+ * Return the duration in milliseconds of the last access.
+ *
+ * @param fromUidState The lowest UID state for which to query
+ * @param toUidState The highest UID state for which to query (inclusive)
+ * @param flags The op flags
+ *
+ * @return the duration in milliseconds or {@code -1}
+ *
+ * @see #getLastDuration(int)
+ * @see #getLastForegroundDuration(int)
+ * @see #getLastBackgroundDuration(int)
+ * @see #getLastDuration(int, int, int)
+ * @see OpEntry#getLastDuration(int, int, int)
+ */
+ public long getLastDuration(@UidState int fromUidState, @UidState int toUidState,
+ @OpFlags int flags) {
+ NoteOpEvent lastEvent = getLastAccessEvent(fromUidState, toUidState, flags);;
+ if (lastEvent == null) {
+ return -1;
+ }
+
+ return lastEvent.duration;
+ }
+
+ /**
+ * Gets the proxy info of the app that performed the last access on behalf of this feature
+ * and as a result blamed the op on this app.
+ *
+ * @param flags The op flags
+ *
+ * @return The proxy name or {@code null}
+ *
+ * @see #getLastForegroundProxyInfo(int)
+ * @see #getLastBackgroundProxyInfo(int)
+ * @see #getLastProxyInfo(int, int, int)
+ * @see OpEntry#getLastProxyInfo(int)
+ */
+ public @Nullable OpEventProxyInfo getLastProxyInfo(@OpFlags int flags) {
+ return getLastProxyInfo(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, flags);
+ }
+
+ /**
+ * Gets the proxy info of the app that performed the last foreground access on behalf of
+ * this feature and as a result blamed the op on this app.
+ *
+ * @param flags The op flags
+ *
+ * @return The proxy name or {@code null}
+ *
+ * @see #getLastProxyInfo(int)
+ * @see #getLastBackgroundProxyInfo(int)
+ * @see #getLastProxyInfo(int, int, int)
+ * @see OpEntry#getLastForegroundProxyInfo(int)
+ */
+ public @Nullable OpEventProxyInfo getLastForegroundProxyInfo(@OpFlags int flags) {
+ return getLastProxyInfo(MAX_PRIORITY_UID_STATE, resolveFirstUnrestrictedUidState(mOp),
+ flags);
+ }
+
+ /**
+ * Gets the proxy info of the app that performed the last background access on behalf of
+ * this feature and as a result blamed the op on this app.
+ *
+ * @param flags The op flags
+ *
+ * @return The proxy name or {@code null}
+ *
+ * @see #getLastProxyInfo(int)
+ * @see #getLastForegroundProxyInfo(int)
+ * @see #getLastProxyInfo(int, int, int)
+ * @see OpEntry#getLastBackgroundProxyInfo(int)
+ */
+ public @Nullable OpEventProxyInfo getLastBackgroundProxyInfo(@OpFlags int flags) {
+ return getLastProxyInfo(resolveLastRestrictedUidState(mOp), MIN_PRIORITY_UID_STATE,
+ flags);
+ }
+
+ /**
+ * Gets the proxy info of the app that performed the last access on behalf of this feature
+ * and as a result blamed the op on this app.
+ *
+ * @param fromUidState The lowest UID state for which to query
+ * @param toUidState The highest UID state for which to query (inclusive)
+ * @param flags The op flags
+ *
+ * @return The proxy name or {@code null}
+ *
+ * @see #getLastProxyInfo(int)
+ * @see #getLastForegroundProxyInfo(int)
+ * @see #getLastBackgroundProxyInfo(int)
+ * @see OpEntry#getLastProxyInfo(int, int, int)
+ */
+ public @Nullable OpEventProxyInfo getLastProxyInfo(@UidState int fromUidState,
+ @UidState int toUidState, @OpFlags int flags) {
+ NoteOpEvent lastEvent = getLastAccessEvent(fromUidState, toUidState, flags);
+ if (lastEvent == null) {
+ return null;
+ }
+
+ return lastEvent.proxy;
+ }
+
+ private static class LongSparseArrayParceling implements
+ Parcelling<LongSparseArray<NoteOpEvent>> {
+ @Override
+ public void parcel(@Nullable LongSparseArray<NoteOpEvent> array, @NonNull Parcel dest,
+ int parcelFlags) {
+ if (array == null) {
+ dest.writeInt(-1);
+ return;
+ }
+
+ int numEntries = array.size();
+ dest.writeInt(numEntries);
+
+ for (int i = 0; i < numEntries; i++) {
+ dest.writeLong(array.keyAt(i));
+ dest.writeParcelable(array.valueAt(i), parcelFlags);
+ }
+ }
+
+ @Override
+ public @Nullable LongSparseArray<NoteOpEvent> unparcel(@NonNull Parcel source) {
+ int numEntries = source.readInt();
+ if (numEntries == -1) {
+ return null;
+ }
+
+ LongSparseArray<NoteOpEvent> array = new LongSparseArray<>(numEntries);
+
+ for (int i = 0; i < numEntries; i++) {
+ array.put(source.readLong(), source.readParcelable(null));
+ }
+
+ return array;
+ }
+ }
+
+
+
+ // 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 OpFeatureEntry.
+ *
+ * @param op
+ * The code of the op
+ * @param running
+ * Whether the op is running
+ * @param accessEvents
+ * The access events
+ * @param rejectEvents
+ * The rejection events
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public OpFeatureEntry(
+ @IntRange(from = 0, to = _NUM_OP - 1) int op,
+ boolean running,
+ @Nullable LongSparseArray<NoteOpEvent> accessEvents,
+ @Nullable LongSparseArray<NoteOpEvent> rejectEvents) {
+ this.mOp = op;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOp,
+ "from", 0,
+ "to", _NUM_OP - 1);
+ this.mRunning = running;
+ this.mAccessEvents = accessEvents;
+ this.mRejectEvents = rejectEvents;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Whether the op is running
+ */
+ @DataClass.Generated.Member
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<LongSparseArray<NoteOpEvent>> sParcellingForAccessEvents =
+ Parcelling.Cache.get(
+ LongSparseArrayParceling.class);
+ static {
+ if (sParcellingForAccessEvents == null) {
+ sParcellingForAccessEvents = Parcelling.Cache.put(
+ new LongSparseArrayParceling());
+ }
+ }
+
+ @DataClass.Generated.Member
+ static Parcelling<LongSparseArray<NoteOpEvent>> sParcellingForRejectEvents =
+ Parcelling.Cache.get(
+ LongSparseArrayParceling.class);
+ static {
+ if (sParcellingForRejectEvents == null) {
+ sParcellingForRejectEvents = Parcelling.Cache.put(
+ new LongSparseArrayParceling());
+ }
+ }
+
+ @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 (mRunning) flg |= 0x2;
+ if (mAccessEvents != null) flg |= 0x4;
+ if (mRejectEvents != null) flg |= 0x8;
+ dest.writeByte(flg);
+ dest.writeInt(mOp);
+ sParcellingForAccessEvents.parcel(mAccessEvents, dest, flags);
+ sParcellingForRejectEvents.parcel(mRejectEvents, dest, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ OpFeatureEntry(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean running = (flg & 0x2) != 0;
+ int op = in.readInt();
+ LongSparseArray<NoteOpEvent> accessEvents = sParcellingForAccessEvents.unparcel(in);
+ LongSparseArray<NoteOpEvent> rejectEvents = sParcellingForRejectEvents.unparcel(in);
+
+ this.mOp = op;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOp,
+ "from", 0,
+ "to", _NUM_OP - 1);
+ this.mRunning = running;
+ this.mAccessEvents = accessEvents;
+ this.mRejectEvents = rejectEvents;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<OpFeatureEntry> CREATOR
+ = new Parcelable.Creator<OpFeatureEntry>() {
+ @Override
+ public OpFeatureEntry[] newArray(int size) {
+ return new OpFeatureEntry[size];
+ }
+
+ @Override
+ public OpFeatureEntry createFromParcel(@NonNull Parcel in) {
+ return new OpFeatureEntry(in);
+ }
+ };
+
+ /*
+ @DataClass.Generated(
+ time = 1574809856239L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=0L, to=_NUM_OP - 1) int mOp\nprivate final boolean mRunning\nprivate final @com.android.internal.util.DataClass.ParcelWith(android.app.OpFeatureEntry.LongSparseArrayParceling.class) @android.annotation.Nullable android.util.LongSparseArray<android.app.NoteOpEvent> mAccessEvents\nprivate final @com.android.internal.util.DataClass.ParcelWith(android.app.OpFeatureEntry.LongSparseArrayParceling.class) @android.annotation.Nullable android.util.LongSparseArray<android.app.NoteOpEvent> mRejectEvents\npublic @android.annotation.NonNull android.util.ArraySet<java.lang.Long> collectKeys()\npublic @android.app.UidState int getLastAccessUidState(int)\npublic @android.app.UidState int getLastForegroundAccessUidState(int)\npublic @android.app.UidState int getLastBackgroundAccessUidState(int)\npublic @android.app.UidState int getLastRejectUidState(int)\npublic @android.app.UidState int getLastForegroundRejectUidState(int)\npublic @android.app.UidState int getLastBackgroundRejectUidState(int)\npublic long getAccessTime(int,int)\npublic long getRejectTime(int,int)\npublic long getDuration(int,int)\npublic int getProxyUid(int,int)\npublic @android.annotation.Nullable java.lang.String getProxyPackageName(int,int)\npublic @android.annotation.Nullable java.lang.String getProxyFeatureId(int,int)\nclass OpFeatureEntry extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+ */
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
+ /**
+ * Last {@link #noteOp} and {@link #startOp} events performed for a single op for all uidModes
+ * and opFlags.
*
* @hide
*/
@TestApi
@Immutable
@SystemApi
- public static final class OpFeatureEntry {
- private final @NonNull OpEntry mParent;
- private final boolean mRunning;
-
- private final @Nullable LongSparseLongArray mAccessTimes;
- private final @Nullable LongSparseLongArray mRejectTimes;
- private final @Nullable LongSparseLongArray mDurations;
- private final @Nullable LongSparseLongArray mProxyUids;
- private final @Nullable LongSparseArray<String> mProxyPackageNames;
- private final @Nullable LongSparseArray<String> mProxyFeatureIds;
-
- /**
- * @hide
- */
- public OpFeatureEntry(@NonNull OpEntry parent, boolean running,
- @Nullable LongSparseLongArray accessTimes,
- @Nullable LongSparseLongArray rejectTimes,
- @Nullable LongSparseLongArray durations, @Nullable LongSparseLongArray proxyUids,
- @Nullable LongSparseArray<String> proxyPackageNames,
- @Nullable LongSparseArray<String> proxyFeatureIds) {
- mParent = Preconditions.checkNotNull(parent);
- mRunning = running;
- mAccessTimes = accessTimes;
- mRejectTimes = rejectTimes;
- mDurations = durations;
- mProxyUids = proxyUids;
- mProxyPackageNames = proxyPackageNames;
- mProxyFeatureIds = proxyFeatureIds;
- }
-
- /**
- * Returns all keys for which we have mapped state in any of the data buckets -
- * access time, reject time, duration.
- * @hide */
- public @Nullable LongSparseArray<Object> collectKeys() {
- LongSparseArray<Object> result = AppOpsManager.collectKeys(mAccessTimes, null);
- result = AppOpsManager.collectKeys(mRejectTimes, result);
- result = AppOpsManager.collectKeys(mDurations, result);
- return result;
- }
-
- /**
- * @hide
- */
- public long getTime() {
- return getLastAccessTime(OP_FLAGS_ALL);
- }
-
- /**
- * Return the last wall clock time in milliseconds this op was accessed.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
- *
- * @see #getLastAccessForegroundTime(int)
- * @see #getLastAccessBackgroundTime(int)
- * @see #getLastAccessTime(int, int, int)
- */
- public long getLastAccessTime(@OpFlags int flags) {
- return maxForFlagsInStates(mAccessTimes, MAX_PRIORITY_UID_STATE,
- MIN_PRIORITY_UID_STATE, flags);
- }
-
- /**
- * Return the last wall clock time in milliseconds this op was accessed
- * by the app while in the foreground.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
- *
- * @see #getLastAccessBackgroundTime(int)
- * @see #getLastAccessTime(int)
- * @see #getLastAccessTime(int, int, int)
- */
- public long getLastAccessForegroundTime(@OpFlags int flags) {
- return maxForFlagsInStates(mAccessTimes, MAX_PRIORITY_UID_STATE,
- resolveFirstUnrestrictedUidState(mParent.mOp), flags);
- }
-
- /**
- * Return the last wall clock time in milliseconds this op was accessed
- * by the app while in the background.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
- *
- * @see #getLastAccessForegroundTime(int)
- * @see #getLastAccessTime(int)
- * @see #getLastAccessTime(int, int, int)
- */
- public long getLastAccessBackgroundTime(@OpFlags int flags) {
- return maxForFlagsInStates(mAccessTimes, resolveLastRestrictedUidState(mParent.mOp),
- MIN_PRIORITY_UID_STATE, flags);
- }
-
- /**
- * Return the last wall clock time in milliseconds this op was accessed
- * by the app for a given range of UID states.
- *
- * @param fromUidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param toUidState The UID state for which to query.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- *
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
- *
- * @see #getLastAccessForegroundTime(int)
- * @see #getLastAccessBackgroundTime(int)
- * @see #getLastAccessTime(int)
- */
- public long getLastAccessTime(@UidState int fromUidState, @UidState int toUidState,
- @OpFlags int flags) {
- return maxForFlagsInStates(mAccessTimes, fromUidState, toUidState, flags);
- }
-
- /**
- * @hide
- */
- public long getRejectTime() {
- return getLastRejectTime(OP_FLAGS_ALL);
- }
-
- /**
- * Return the last wall clock time in milliseconds the app made an attempt
- * to access this op but was rejected.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last reject time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
- *
- * @see #getLastRejectBackgroundTime(int)
- * @see #getLastRejectForegroundTime(int)
- * @see #getLastRejectTime(int, int, int)
- */
- public long getLastRejectTime(@OpFlags int flags) {
- return maxForFlagsInStates(mRejectTimes, MAX_PRIORITY_UID_STATE,
- MIN_PRIORITY_UID_STATE, flags);
- }
-
- /**
- * Return the last wall clock time in milliseconds the app made an attempt
- * to access this op while in the foreground but was rejected.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last foreground reject time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
- *
- * @see #getLastRejectBackgroundTime(int)
- * @see #getLastRejectTime(int, int, int)
- * @see #getLastRejectTime(int)
- */
- public long getLastRejectForegroundTime(@OpFlags int flags) {
- return maxForFlagsInStates(mRejectTimes, MAX_PRIORITY_UID_STATE,
- resolveFirstUnrestrictedUidState(mParent.mOp), flags);
- }
-
- /**
- * Return the last wall clock time in milliseconds the app made an attempt
- * to access this op while in the background but was rejected.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last background reject time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
- *
- * @see #getLastRejectForegroundTime(int)
- * @see #getLastRejectTime(int, int, int)
- * @see #getLastRejectTime(int)
- */
- public long getLastRejectBackgroundTime(@OpFlags int flags) {
- return maxForFlagsInStates(mRejectTimes, resolveLastRestrictedUidState(mParent.mOp),
- MIN_PRIORITY_UID_STATE, flags);
- }
-
- /**
- * Return the last wall clock time state in milliseconds the app made an
- * attempt to access this op for a given range of UID states.
- *
- * @param fromUidState The UID state from which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param toUidState The UID state to which to query.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
- *
- * @see #getLastRejectForegroundTime(int)
- * @see #getLastRejectBackgroundTime(int)
- * @see #getLastRejectTime(int)
- */
- public long getLastRejectTime(@UidState int fromUidState, @UidState int toUidState,
- @OpFlags int flags) {
- return maxForFlagsInStates(mRejectTimes, fromUidState, toUidState, flags);
- }
-
- /**
- * @return Whether the operation is running.
- */
- public boolean isRunning() {
- return mRunning;
- }
-
- /**
- * @return The duration of the operation in milliseconds. The duration is in wall time.
- */
- public long getDuration() {
- return getLastDuration(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
- }
-
- /**
- * Return the duration in milliseconds the app accessed this op while
- * in the foreground. The duration is in wall time.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the foreground access duration in milliseconds.
- *
- * @see #getLastBackgroundDuration(int)
- * @see #getLastDuration(int, int, int)
- */
- public long getLastForegroundDuration(@OpFlags int flags) {
- return sumForFlagsInStates(mDurations, MAX_PRIORITY_UID_STATE,
- resolveFirstUnrestrictedUidState(mParent.mOp), flags);
- }
-
- /**
- * Return the duration in milliseconds the app accessed this op while
- * in the background. The duration is in wall time.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the background access duration in milliseconds.
- *
- * @see #getLastForegroundDuration(int)
- * @see #getLastDuration(int, int, int)
- */
- public long getLastBackgroundDuration(@OpFlags int flags) {
- return sumForFlagsInStates(mDurations, resolveLastRestrictedUidState(mParent.mOp),
- MIN_PRIORITY_UID_STATE, flags);
- }
-
- /**
- * Return the duration in milliseconds the app accessed this op for
- * a given range of UID states. The duration is in wall time.
- *
- * @param fromUidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param toUidState The UID state for which to query.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the access duration in milliseconds.
- */
- public long getLastDuration(@UidState int fromUidState, @UidState int toUidState,
- @OpFlags int flags) {
- return sumForFlagsInStates(mDurations, fromUidState, toUidState, flags);
- }
-
- /**
- * Gets the UID of the app that performed the op on behalf of this app and
- * as a result blamed the op on this app or {@link Process#INVALID_UID} if
- * there is no proxy.
- *
- * @return The proxy UID.
- */
- public int getProxyUid() {
- return (int) findFirstNonNegativeForFlagsInStates(mProxyUids,
- MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
- }
-
- /**
- * Gets the UID of the app that performed the op on behalf of this app and
- * as a result blamed the op on this app or {@link Process#INVALID_UID} if
- * there is no proxy.
- *
- * @param uidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- *
- * @return The proxy UID.
- */
- public int getProxyUid(@UidState int uidState, @OpFlags int flags) {
- return (int) findFirstNonNegativeForFlagsInStates(mProxyUids,
- uidState, uidState, flags);
- }
-
- /**
- * Gets the package name of the app that performed the op on behalf of this
- * app and as a result blamed the op on this app or {@code null}
- * if there is no proxy.
- *
- * @return The proxy package name.
- */
- public @Nullable String getProxyPackageName() {
- return findFirstNonNullForFlagsInStates(mProxyPackageNames, MAX_PRIORITY_UID_STATE,
- MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
- }
-
- /**
- * Gets the package name of the app that performed the op on behalf of this
- * app and as a result blamed the op on this app for a UID state or
- * {@code null} if there is no proxy.
- *
- * @param uidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return The feature id.
- */
- public @Nullable String getProxyPackageName(@UidState int uidState, @OpFlags int flags) {
- return findFirstNonNullForFlagsInStates(mProxyPackageNames, uidState, uidState, flags);
- }
-
- /**
- * Gets the feature of the app that performed the op on behalf of this
- * app and as a result blamed the op on this app or {@code null}
- * if there is no proxy.
- *
- * @return The proxy package name.
- */
- public @Nullable String getProxyFeatureId() {
- return findFirstNonNullForFlagsInStates(mProxyFeatureIds, MAX_PRIORITY_UID_STATE,
- MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
- }
-
- /**
- * Gets the feature of the app that performed the op on behalf of this
- * app and as a result blamed the op on this app for a UID state or
- * {@code null} if there is no proxy.
- *
- * @param uidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return The feature id.
- */
- public @Nullable String getProxyFeatureId(@UidState int uidState, @OpFlags int flags) {
- return findFirstNonNullForFlagsInStates(mProxyFeatureIds, uidState, uidState, flags);
- }
-
- /**
- * @hide
- */
- public static class Builder {
- private final boolean mRunning;
-
- private final @Nullable LongSparseLongArray mAccessTimes;
- private final @Nullable LongSparseLongArray mRejectTimes;
- private final @Nullable LongSparseLongArray mDurations;
- private final @Nullable LongSparseLongArray mProxyUids;
- private final @Nullable LongSparseArray<String> mProxyPackageNames;
- private final @Nullable LongSparseArray<String> mProxyFeatureIds;
- private @NonNull OpEntry mParent;
-
- public Builder(boolean running, @Nullable LongSparseLongArray accessTimes,
- @Nullable LongSparseLongArray rejectTimes,
- @Nullable LongSparseLongArray durations,
- @Nullable LongSparseLongArray proxyUids,
- @Nullable LongSparseArray<String> proxyPackageNames,
- @Nullable LongSparseArray<String> proxyFeatureIds) {
- mRunning = running;
- mAccessTimes = accessTimes;
- mRejectTimes = rejectTimes;
- mDurations = durations;
- mProxyUids = proxyUids;
- mProxyPackageNames = proxyPackageNames;
- mProxyFeatureIds = proxyFeatureIds;
- }
-
- public Builder setParent(@NonNull OpEntry parent) {
- mParent = parent;
-
- return this;
- }
-
- /**
- * Create OpFeatureEntry from builder
- */
- public OpFeatureEntry build() {
- Preconditions.checkNotNull(mParent);
-
- return new OpFeatureEntry(mParent, mRunning, mAccessTimes, mRejectTimes,
- mDurations, mProxyUids, mProxyPackageNames, mProxyFeatureIds);
- }
- }
-
- /**
- * @hide
- */
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- LongSparseLongArray.Parcelling longSparseLongArrayParcelling =
- LongSparseLongArray.Parcelling.Cache.getOrCreate(
- LongSparseLongArray.Parcelling.class);
- LongSparseArray.StringParcelling longSparseStringArrayParcelling =
- LongSparseArray.StringParcelling.Cache.getOrCreate(
- LongSparseArray.StringParcelling.class);
-
- dest.writeBoolean(mRunning);
- longSparseLongArrayParcelling.parcel(mAccessTimes, dest, flags);
- longSparseLongArrayParcelling.parcel(mRejectTimes, dest, flags);
- longSparseLongArrayParcelling.parcel(mDurations, dest, flags);
- longSparseLongArrayParcelling.parcel(mProxyUids, dest, flags);
- longSparseStringArrayParcelling.parcel(mProxyPackageNames, dest, flags);
- longSparseStringArrayParcelling.parcel(mProxyFeatureIds, dest, flags);
- }
-
- /**
- * @hide
- */
- public static OpFeatureEntry.Builder createFromParcel(@NonNull Parcel source) {
- LongSparseLongArray.Parcelling longSparseLongArrayParcelling =
- LongSparseLongArray.Parcelling.Cache.getOrCreate(
- LongSparseLongArray.Parcelling.class);
- LongSparseArray.StringParcelling longSparseStringArrayParcelling =
- LongSparseArray.StringParcelling.Cache.getOrCreate(
- LongSparseArray.StringParcelling.class);
-
- return new OpFeatureEntry.Builder(source.readBoolean(),
- longSparseLongArrayParcelling.unparcel(source),
- longSparseLongArrayParcelling.unparcel(source),
- longSparseLongArrayParcelling.unparcel(source),
- longSparseLongArrayParcelling.unparcel(source),
- longSparseStringArrayParcelling.unparcel(source),
- longSparseStringArrayParcelling.unparcel(source));
- }
- }
-
- /**
- * Class holding the information about one unique operation of an application.
- * @hide
- */
- @TestApi
- @Immutable
- @SystemApi
+ // @DataClass(genHiddenConstructor = true) codegen verifier is broken
public static final class OpEntry implements Parcelable {
+ /** The code of the op */
private final @IntRange(from = 0, to = _NUM_OP - 1) int mOp;
+ /** The mode of the op */
private final @Mode int mMode;
- private final @NonNull ArrayMap<String, OpFeatureEntry> mFeatures;
-
- /**
- * @hide
- */
- public OpEntry(@IntRange(from = 0, to = _NUM_OP - 1) int op, @Mode int mode,
- @NonNull Pair<String, OpFeatureEntry.Builder>[] featureBuilders) {
- mOp = Preconditions.checkArgumentInRange(op, 0, _NUM_OP - 1, "op");
- mMode = Preconditions.checkArgumentInRange(mode, 0, MODE_FOREGROUND, "mode");
-
- mFeatures = new ArrayMap<>(featureBuilders.length);
- for (Pair<String, OpFeatureEntry.Builder> feature : featureBuilders) {
- mFeatures.put(feature.first, feature.second.setParent(this).build());
- }
- }
-
- /**
- * @return The mapping from the feature ids to the feature state
- */
- public @NonNull Map<String, OpFeatureEntry> getFeatures() {
- return mFeatures;
- }
+ /** The features that have been used when checking the op */
+ private final @NonNull Map<String, OpFeatureEntry> mFeatures;
/**
* @hide
@@ -2868,16 +3225,9 @@
}
/**
- * @return this entry's current mode, such as {@link #MODE_ALLOWED}.
- */
- public @Mode int getMode() {
- return mMode;
- }
-
- /**
- * @deprecated Use {@link OpEntry#getLastAccessTime(int)} instead
- *
* @hide
+ *
+ * @deprecated Use {@link #getLastAccessTime(int)} instead
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code "
@@ -2886,130 +3236,112 @@
return getLastAccessTime(OP_FLAGS_ALL);
}
- private long getMaxOfFeatures(@NonNull ToLongFunction<OpFeatureEntry> timeGetter) {
- long max = 0;
-
- int numFeatures = mFeatures.size();
- for (int i = 0; i < numFeatures; i++) {
- max = Math.max(max, timeGetter.applyAsLong(mFeatures.valueAt(i)));
- }
-
- return max;
- }
-
- private long getSumOfFeatures(@NonNull ToLongFunction<OpFeatureEntry> getter) {
- long sum = 0;
-
- int numFeatures = mFeatures.size();
- for (int i = 0; i < numFeatures; i++) {
- sum += getter.applyAsLong(mFeatures.valueAt(i));
- }
-
- return sum;
- }
-
/**
- * Return the last wall clock time in milliseconds this op was accessed
- * by the app for a given range of UID states.
+ * Return the last access time.
*
- * @param fromUidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param toUidState The UID state for which to query.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
+ * @param flags The op flags
*
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ * @return the last access time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
*
* @see #getLastAccessForegroundTime(int)
* @see #getLastAccessBackgroundTime(int)
- * @see #getLastAccessTime(int)
+ * @see #getLastAccessTime(int, int, int)
+ * @see OpFeatureEntry#getLastAccessTime(int)
*/
public long getLastAccessTime(@OpFlags int flags) {
- return getMaxOfFeatures(
- (featureEntry -> featureEntry.getLastAccessTime(flags)));
+ return getLastAccessTime(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, flags);
}
/**
- * Return the last wall clock time in milliseconds this op was accessed
- * by the app while in the foreground.
+ * Return the last foreground access time.
*
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ * @param flags The op flags
*
- * @see #getLastAccessBackgroundTime(int)
+ * @return the last access time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
* @see #getLastAccessTime(int)
+ * @see #getLastAccessBackgroundTime(int)
* @see #getLastAccessTime(int, int, int)
+ * @see OpFeatureEntry#getLastAccessForegroundTime(int)
*/
public long getLastAccessForegroundTime(@OpFlags int flags) {
- return getMaxOfFeatures(
- (featureEntry -> featureEntry.getLastAccessForegroundTime(flags)));
+ return getLastAccessTime(MAX_PRIORITY_UID_STATE, resolveFirstUnrestrictedUidState(mOp),
+ flags);
}
/**
- * Return the last wall clock time in milliseconds this op was accessed
- * by the app while in the background.
+ * Return the last background access time.
*
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ * @param flags The op flags
*
- * @see #getLastAccessForegroundTime(int)
+ * @return the last access time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
* @see #getLastAccessTime(int)
+ * @see #getLastAccessForegroundTime(int)
* @see #getLastAccessTime(int, int, int)
+ * @see OpFeatureEntry#getLastAccessBackgroundTime(int)
*/
public long getLastAccessBackgroundTime(@OpFlags int flags) {
- return getMaxOfFeatures(
- (featureEntry -> featureEntry.getLastAccessBackgroundTime(flags)));
+ return getLastAccessTime(resolveLastRestrictedUidState(mOp), MIN_PRIORITY_UID_STATE,
+ flags);
}
/**
- * Return the last wall clock time in milliseconds this op was accessed
- * by the app for a given range of UID states.
+ * Return the last access event.
*
- * @param fromUidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param toUidState The UID state for which to query.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
+ * @param flags The op flags
*
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ * @return the last access event of {@code null}
+ */
+ private @Nullable NoteOpEvent getLastAccessEvent(@UidState int fromUidState,
+ @UidState int toUidState, @OpFlags int flags) {
+ NoteOpEvent lastAccessEvent = null;
+ for (OpFeatureEntry featureEntry : mFeatures.values()) {
+ NoteOpEvent lastFeatureAccessEvent = featureEntry.getLastAccessEvent(fromUidState,
+ toUidState, flags);
+
+ if (lastAccessEvent == null || (lastFeatureAccessEvent != null
+ && lastFeatureAccessEvent.noteTime > lastAccessEvent.noteTime)) {
+ lastAccessEvent = lastFeatureAccessEvent;
+ }
+ }
+
+ return lastAccessEvent;
+ }
+
+ /**
+ * Return the last access time.
*
+ * @param fromUidState the lowest uid state to query
+ * @param toUidState the highest uid state to query (inclusive)
+ * @param flags The op flags
+ *
+ * @return the last access time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastAccessTime(int)
* @see #getLastAccessForegroundTime(int)
* @see #getLastAccessBackgroundTime(int)
- * @see #getLastAccessTime(int)
+ * @see OpFeatureEntry#getLastAccessTime(int, int, int)
*/
public long getLastAccessTime(@UidState int fromUidState, @UidState int toUidState,
@OpFlags int flags) {
- return getMaxOfFeatures(
- (featureEntry -> featureEntry.getLastAccessTime(fromUidState,
- toUidState, flags)));
+ NoteOpEvent lastEvent = getLastAccessEvent(fromUidState, toUidState, flags);;
+
+ if (lastEvent == null) {
+ return -1;
+ }
+
+ return lastEvent.noteTime;
}
/**
- * @deprecated Use {@link OpEntry#getLastRejectTime(int)} instead
- *
* @hide
+ *
+ * @deprecated Use {@link #getLastRejectTime(int)} instead
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@code "
@@ -3019,107 +3351,113 @@
}
/**
- * Return the last wall clock time in milliseconds the app made an attempt
- * to access this op but was rejected.
+ * Return the last rejection time.
*
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last reject time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ * @param flags The op flags
*
- * @see #getLastRejectBackgroundTime(int)
+ * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
* @see #getLastRejectForegroundTime(int)
+ * @see #getLastRejectBackgroundTime(int)
* @see #getLastRejectTime(int, int, int)
+ * @see OpFeatureEntry#getLastRejectTime(int)
*/
public long getLastRejectTime(@OpFlags int flags) {
- return getMaxOfFeatures(
- (featureEntry -> featureEntry.getLastRejectTime(flags)));
+ return getLastRejectTime(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, flags);
}
/**
- * Return the last wall clock time in milliseconds the app made an attempt
- * to access this op while in the foreground but was rejected.
+ * Return the last foreground rejection time.
*
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last foreground reject time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ * @param flags The op flags
*
+ * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastRejectTime(int)
* @see #getLastRejectBackgroundTime(int)
* @see #getLastRejectTime(int, int, int)
- * @see #getLastRejectTime(int)
+ * @see OpFeatureEntry#getLastRejectForegroundTime(int)
*/
public long getLastRejectForegroundTime(@OpFlags int flags) {
- return getMaxOfFeatures(
- (featureEntry -> featureEntry.getLastRejectForegroundTime(flags)));
+ return getLastRejectTime(MAX_PRIORITY_UID_STATE, resolveFirstUnrestrictedUidState(mOp),
+ flags);
}
/**
- * Return the last wall clock time in milliseconds the app made an attempt
- * to access this op while in the background but was rejected.
+ * Return the last background rejection time.
*
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last background reject time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ * @param flags The op flags
*
+ * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastRejectTime(int)
* @see #getLastRejectForegroundTime(int)
* @see #getLastRejectTime(int, int, int)
- * @see #getLastRejectTime(int)
+ * @see OpFeatureEntry#getLastRejectBackgroundTime(int)
*/
public long getLastRejectBackgroundTime(@OpFlags int flags) {
- return getMaxOfFeatures(
- (featureEntry -> featureEntry.getLastRejectBackgroundTime(flags)));
+ return getLastRejectTime(resolveLastRestrictedUidState(mOp), MIN_PRIORITY_UID_STATE,
+ flags);
}
/**
- * Return the last wall clock time state in milliseconds the app made an
- * attempt to access this op for a given range of UID states.
+ * Return the last rejection event.
*
- * @param fromUidState The UID state from which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param toUidState The UID state to which to query.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the last foreground access time in milliseconds since
- * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+ * @param flags The op flags
*
+ * @return the last reject event of {@code null}
+ */
+ private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState,
+ @UidState int toUidState, @OpFlags int flags) {
+ NoteOpEvent lastAccessEvent = null;
+ for (OpFeatureEntry featureEntry : mFeatures.values()) {
+ NoteOpEvent lastFeatureAccessEvent = featureEntry.getLastRejectEvent(fromUidState,
+ toUidState, flags);
+
+ if (lastAccessEvent == null || (lastFeatureAccessEvent != null
+ && lastFeatureAccessEvent.noteTime > lastAccessEvent.noteTime)) {
+ lastAccessEvent = lastFeatureAccessEvent;
+ }
+ }
+
+ return lastAccessEvent;
+ }
+
+ /**
+ * Return the last rejection time.
+ *
+ * @param fromUidState the lowest uid state to query
+ * @param toUidState the highest uid state to query (inclusive)
+ * @param flags The op flags
+ *
+ * @return the last rejection time (in milliseconds since epoch start (January 1, 1970
+ * 00:00:00.000 GMT - Gregorian)) or {@code -1}
+ *
+ * @see #getLastRejectTime(int)
* @see #getLastRejectForegroundTime(int)
* @see #getLastRejectBackgroundTime(int)
- * @see #getLastRejectTime(int)
+ * @see #getLastRejectTime(int, int, int)
+ * @see OpFeatureEntry#getLastRejectTime(int, int, int)
*/
public long getLastRejectTime(@UidState int fromUidState, @UidState int toUidState,
@OpFlags int flags) {
- return getMaxOfFeatures(
- (featureEntry -> featureEntry.getLastRejectTime(fromUidState,
- toUidState, flags)));
+ NoteOpEvent lastEvent = getLastRejectEvent(fromUidState, toUidState, flags);
+ if (lastEvent == null) {
+ return -1;
+ }
+
+ return lastEvent.noteTime;
}
/**
* @return Whether the operation is running.
*/
public boolean isRunning() {
- int numFeatures = mFeatures.size();
- if (mFeatures.isEmpty()) {
- return false;
- }
-
- for (int i = 0; i < numFeatures; i++) {
- if (mFeatures.valueAt(i).mRunning) {
+ for (OpFeatureEntry opFeatureEntry : mFeatures.values()) {
+ if (opFeatureEntry.isRunning()) {
return true;
}
}
@@ -3128,257 +3466,349 @@
}
/**
- * @return The duration of the operation in milliseconds. The duration is in wall time.
+ * @deprecated Use {@link #getLastDuration(int)} instead
*/
+ @Deprecated
public long getDuration() {
- return getLastDuration(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
+ return getLastDuration(OP_FLAGS_ALL);
}
/**
- * Return the duration in milliseconds the app accessed this op while
- * in the foreground. The duration is in wall time.
+ * Return the duration in milliseconds of the last the access.
*
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the foreground access duration in milliseconds.
+ * @param flags The op flags
*
- * @see #getLastBackgroundDuration(int)
- * @see #getLastDuration(int, int, int)
- */
- public long getLastForegroundDuration(@OpFlags int flags) {
- return getSumOfFeatures((featureEntry) ->
- featureEntry.getLastForegroundDuration(flags));
- }
-
- /**
- * Return the duration in milliseconds the app accessed this op while
- * in the background. The duration is in wall time.
- *
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the background access duration in milliseconds.
+ * @return the duration in milliseconds or {@code -1}
*
* @see #getLastForegroundDuration(int)
+ * @see #getLastBackgroundDuration(int)
* @see #getLastDuration(int, int, int)
+ * @see OpFeatureEntry#getLastDuration(int)
*/
- public long getLastBackgroundDuration(@OpFlags int flags) {
- return getSumOfFeatures((featureEntry) ->
- featureEntry.getLastBackgroundDuration(flags));
+ public long getLastDuration(@OpFlags int flags) {
+ return getLastDuration(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, flags);
}
/**
- * Return the duration in milliseconds the app accessed this op for
- * a given range of UID states. The duration is in wall time.
+ * Return the duration in milliseconds of the last foreground access.
*
- * @param fromUidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param toUidState The UID state for which to query.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return the access duration in milliseconds.
+ * @param flags The op flags
+ *
+ * @return the duration in milliseconds or {@code -1}
+ *
+ * @see #getLastDuration(int)
+ * @see #getLastBackgroundDuration(int)
+ * @see #getLastDuration(int, int, int)
+ * @see OpFeatureEntry#getLastForegroundDuration(int)
+ */
+ public long getLastForegroundDuration(@OpFlags int flags) {
+ return getLastDuration(MAX_PRIORITY_UID_STATE, resolveFirstUnrestrictedUidState(mOp),
+ flags);
+ }
+
+ /**
+ * Return the duration in milliseconds of the last background access.
+ *
+ * @param flags The op flags
+ *
+ * @return the duration in milliseconds or {@code -1}
+ *
+ * @see #getLastDuration(int)
+ * @see #getLastForegroundDuration(int)
+ * @see #getLastDuration(int, int, int)
+ * @see OpFeatureEntry#getLastBackgroundDuration(int)
+ */
+ public long getLastBackgroundDuration(@OpFlags int flags) {
+ return getLastDuration(resolveLastRestrictedUidState(mOp), MIN_PRIORITY_UID_STATE,
+ flags);
+ }
+
+ /**
+ * Return the duration in milliseconds of the last access.
+ *
+ * @param fromUidState The lowest UID state for which to query
+ * @param toUidState The highest UID state for which to query (inclusive)
+ * @param flags The op flags
+ *
+ * @return the duration in milliseconds or {@code -1}
+ *
+ * @see #getLastDuration(int)
+ * @see #getLastForegroundDuration(int)
+ * @see #getLastBackgroundDuration(int)
+ * @see OpFeatureEntry#getLastDuration(int, int, int)
*/
public long getLastDuration(@UidState int fromUidState, @UidState int toUidState,
@OpFlags int flags) {
- return getSumOfFeatures((featureEntry) ->
- featureEntry.getLastDuration(fromUidState, toUidState, flags));
- }
-
- /**
- * Like {@link #findFirstNonNegativeForFlagsInStates(LongSparseLongArray, int, int, int)}
- * but for all proxy uid in all features.
- */
- private long findFirstNonNegativeProxyUidInFeatureStates(@UidState int beginUidState,
- @UidState int endUidState, @OpFlags int flags) {
- int numFeatures = mFeatures.size();
-
- if (numFeatures == 0) {
+ NoteOpEvent lastEvent = getLastAccessEvent(fromUidState, toUidState, flags);
+ if (lastEvent == null) {
return -1;
}
- while (flags != 0) {
- final int flag = 1 << Integer.numberOfTrailingZeros(flags);
- flags &= ~flag;
- for (int uidState : UID_STATES) {
- if (uidState < beginUidState || uidState > endUidState) {
- continue;
- }
-
- final long key = makeKey(uidState, flag);
-
- for (int i = 0; i < numFeatures; i++) {
- OpFeatureEntry featureEntry = mFeatures.valueAt(i);
-
- if (featureEntry.mProxyUids == null) {
- continue;
- }
-
- final long proxyUid = featureEntry.mProxyUids.get(key);
- if (proxyUid >= 0) {
- return proxyUid;
- }
- }
- }
- }
-
- return -1;
+ return lastEvent.duration;
}
/**
- * Like {@link #findFirstNonNullForFlagsInStates(LongSparseArray, int, int, int)} but
- * for all proxyPackageNames in all features.
- */
- private @Nullable String findFirstNonNullProxyPackageNameInFeatureStates(
- @OpFlags int flags, @UidState int beginUidState, @UidState int endUidState) {
- int numFeatures = mFeatures.size();
-
- if (numFeatures == 0) {
- return null;
- }
-
- while (flags != 0) {
- final int flag = 1 << Integer.numberOfTrailingZeros(flags);
- flags &= ~flag;
- for (int uidState : UID_STATES) {
- if (uidState < beginUidState || uidState > endUidState) {
- continue;
- }
- final long key = makeKey(uidState, flag);
-
- for (int i = 0; i < numFeatures; i++) {
- OpFeatureEntry featureEntry = mFeatures.valueAt(i);
-
- if (featureEntry.mProxyPackageNames == null) {
- continue;
- }
-
- final String proxyName = featureEntry.mProxyPackageNames.get(key);
- if (proxyName != null) {
- return proxyName;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * @deprecated Use {@link #getProxyUid(int, int)} instead
+ * @deprecated Use {@link #getLastProxyInfo(int)} instead
*/
@Deprecated
public int getProxyUid() {
- return (int) findFirstNonNegativeProxyUidInFeatureStates(MAX_PRIORITY_UID_STATE,
- MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
+ OpEventProxyInfo proxy = getLastProxyInfo(OP_FLAGS_ALL);
+ if (proxy == null) {
+ return Process.INVALID_UID;
+ }
+
+ return proxy.getUid();
}
/**
- * Gets the UID of the app that performed the op on behalf of this app and
- * as a result blamed the op on this app or {@link Process#INVALID_UID} if
- * there is no proxy.
- *
- * @param uidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- *
- * @return The proxy UID.
+ * @deprecated Use {@link #getLastProxyInfo(int)} instead
*/
+ @Deprecated
public int getProxyUid(@UidState int uidState, @OpFlags int flags) {
- return (int) findFirstNonNegativeProxyUidInFeatureStates(uidState, uidState, flags);
+ OpEventProxyInfo proxy = getLastProxyInfo(uidState, uidState, flags);
+ if (proxy == null) {
+ return Process.INVALID_UID;
+ }
+
+ return proxy.getUid();
}
/**
- * @deprecated Use {@link #getProxyPackageName(int, int)} instead
+ * @deprecated Use {@link #getLastProxyInfo(int)} instead
*/
@Deprecated
public @Nullable String getProxyPackageName() {
- return findFirstNonNullProxyPackageNameInFeatureStates(MAX_PRIORITY_UID_STATE,
- MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
+ OpEventProxyInfo proxy = getLastProxyInfo(OP_FLAGS_ALL);
+ if (proxy == null) {
+ return null;
+ }
+
+ return proxy.getPackageName();
}
/**
- * Gets the package name of the app that performed the op on behalf of this
- * app and as a result blamed the op on this app for a UID state or
- * {@code null} if there is no proxy.
- *
- * @param uidState The UID state for which to query. Could be one of
- * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
- * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
- * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
- * @param flags The flags which are any combination of
- * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
- * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
- * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
- * for any flag.
- * @return The proxy package name.
+ * @deprecated Use {@link #getLastProxyInfo(int)} instead
*/
+ @Deprecated
public @Nullable String getProxyPackageName(@UidState int uidState, @OpFlags int flags) {
- return findFirstNonNullProxyPackageNameInFeatureStates(uidState, uidState, flags);
+ OpEventProxyInfo proxy = getLastProxyInfo(uidState, uidState, flags);
+ if (proxy == null) {
+ return null;
+ }
+
+ return proxy.getPackageName();
}
/**
- * Create OpEntry from parcel.
+ * Gets the proxy info of the app that performed the last access on behalf of this app and
+ * as a result blamed the op on this app.
*
+ * @param flags The op flags
+ *
+ * @return The proxy name or {@code null}
+ *
+ * @see #getLastForegroundProxyInfo(int)
+ * @see #getLastBackgroundProxyInfo(int)
+ * @see #getLastProxyInfo(int, int, int)
+ * @see OpFeatureEntry#getLastProxyInfo(int)
+ */
+ public @Nullable OpEventProxyInfo getLastProxyInfo(@OpFlags int flags) {
+ return getLastProxyInfo(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, flags);
+ }
+
+ /**
+ * Gets the proxy info of the app that performed the last foreground access on behalf of
+ * this app and as a result blamed the op on this app.
+ *
+ * @param flags The op flags
+ *
+ * @return The proxy name or {@code null}
+ *
+ * @see #getLastProxyInfo(int)
+ * @see #getLastBackgroundProxyInfo(int)
+ * @see #getLastProxyInfo(int, int, int)
+ * @see OpFeatureEntry#getLastForegroundProxyInfo(int)
+ */
+ public @Nullable OpEventProxyInfo getLastForegroundProxyInfo(@OpFlags int flags) {
+ return getLastProxyInfo(MAX_PRIORITY_UID_STATE, resolveFirstUnrestrictedUidState(mOp),
+ flags);
+ }
+
+ /**
+ * Gets the proxy info of the app that performed the last background access on behalf of
+ * this app and as a result blamed the op on this app.
+ *
+ * @param flags The op flags
+ *
+ * @return The proxy name or {@code null}
+ *
+ * @see #getLastProxyInfo(int)
+ * @see #getLastForegroundProxyInfo(int)
+ * @see #getLastProxyInfo(int, int, int)
+ * @see OpFeatureEntry#getLastBackgroundProxyInfo(int)
+ */
+ public @Nullable OpEventProxyInfo getLastBackgroundProxyInfo(@OpFlags int flags) {
+ return getLastProxyInfo(resolveLastRestrictedUidState(mOp), MIN_PRIORITY_UID_STATE,
+ flags);
+ }
+
+ /**
+ * Gets the proxy info of the app that performed the last access on behalf of this app and
+ * as a result blamed the op on this app.
+ *
+ * @param fromUidState The lowest UID state for which to query
+ * @param toUidState The highest UID state for which to query (inclusive)
+ * @param flags The op flags
+ *
+ * @return The proxy name or {@code null}
+ *
+ * @see #getLastProxyInfo(int)
+ * @see #getLastForegroundProxyInfo(int)
+ * @see #getLastBackgroundProxyInfo(int)
+ * @see OpFeatureEntry#getLastProxyInfo(int, int, int)
+ */
+ public @Nullable OpEventProxyInfo getLastProxyInfo(@UidState int fromUidState,
+ @UidState int toUidState, @OpFlags int flags) {
+ NoteOpEvent lastEvent = getLastAccessEvent(fromUidState, toUidState, flags);
+ if (lastEvent == null) {
+ return null;
+ }
+
+ return lastEvent.proxy;
+ }
+
+
+
+ // 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 OpEntry.
+ *
+ * @param op
+ * The code of the op
+ * @param mode
+ * The mode of the op
+ * @param features
+ * The features that have been used when checking the op
* @hide
*/
- public static OpEntry createFromParcel(@NonNull Parcel source) {
- int op = source.readInt();
- int mode = source.readInt();
+ @DataClass.Generated.Member
+ public OpEntry(
+ @IntRange(from = 0, to = _NUM_OP - 1) int op,
+ @Mode int mode,
+ @NonNull Map<String,OpFeatureEntry> features) {
+ this.mOp = op;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOp,
+ "from", 0,
+ "to", _NUM_OP - 1);
+ this.mMode = mode;
+ com.android.internal.util.AnnotationValidations.validate(
+ Mode.class, null, mMode);
+ this.mFeatures = features;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatures);
- int numFeatures = source.readInt();
- Pair<String, OpFeatureEntry.Builder>[] features = new Pair[numFeatures];
- for (int i = 0; i < numFeatures; i++) {
- features[i] = new Pair<>(source.readString(),
- OpFeatureEntry.createFromParcel(source));
- }
+ // onConstructed(); // You can define this method to get a callback
+ }
- return new OpEntry(op, mode, features);
+ /**
+ * The mode of the op
+ */
+ @DataClass.Generated.Member
+ public @Mode int getMode() {
+ return mMode;
+ }
+
+ /**
+ * The features that have been used when checking the op
+ */
+ @DataClass.Generated.Member
+ public @NonNull Map<String,OpFeatureEntry> getFeatures() {
+ return mFeatures;
}
@Override
- public int describeContents() {
- return 0;
- }
-
- @Override
+ @DataClass.Generated.Member
public void writeToParcel(Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
dest.writeInt(mOp);
dest.writeInt(mMode);
-
- int numFeatures = mFeatures.size();
- dest.writeInt(numFeatures);
- for (int i = 0; i < numFeatures; i++) {
- dest.writeString(mFeatures.keyAt(i));
- mFeatures.valueAt(i).writeToParcel(dest, flags);
- }
+ dest.writeMap(mFeatures);
}
- public static final @NonNull Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ OpEntry(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int op = in.readInt();
+ int mode = in.readInt();
+ Map<String,OpFeatureEntry> features = new java.util.LinkedHashMap<>();
+ in.readMap(features, OpFeatureEntry.class.getClassLoader());
+
+ this.mOp = op;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOp,
+ "from", 0,
+ "to", _NUM_OP - 1);
+ this.mMode = mode;
+ com.android.internal.util.AnnotationValidations.validate(
+ Mode.class, null, mMode);
+ this.mFeatures = features;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatures);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<OpEntry> CREATOR
+ = new Parcelable.Creator<OpEntry>() {
@Override
- public @NonNull OpEntry createFromParcel(@NonNull Parcel parcel) {
- return OpEntry.createFromParcel(parcel);
+ public OpEntry[] newArray(int size) {
+ return new OpEntry[size];
}
@Override
- public @NonNull OpEntry[] newArray(int size) {
- return new OpEntry[size];
+ public OpEntry createFromParcel(@NonNull Parcel in) {
+ return new OpEntry(in);
}
};
+
+ /*
+ @DataClass.Generated(
+ time = 1574809856259L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java",
+ inputSignatures = "private final @android.annotation.IntRange(from=0L, to=_NUM_OP - 1) int mOp\nprivate final @android.app.Mode int mMode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.app.OpFeatureEntry> mFeatures\npublic @android.annotation.UnsupportedAppUsage(maxTargetSdk=Build.VERSION_CODES.Q, publicAlternatives=\"{@code \" + \"#getOpStr()}\") int getOp()\npublic @android.annotation.NonNull java.lang.String getOpStr()\npublic @java.lang.Deprecated @android.annotation.UnsupportedAppUsage(maxTargetSdk=Build.VERSION_CODES.Q, publicAlternatives=\"{@code \" + \"#getAccessTime(int, int)}\") long getTime()\npublic @java.lang.Deprecated long getLastAccessTime(int)\npublic @java.lang.Deprecated long getLastAccessForegroundTime(int)\npublic @java.lang.Deprecated long getLastAccessBackgroundTime(int)\npublic @java.lang.Deprecated long getLastAccessTime(int,int,int)\npublic @java.lang.Deprecated @android.annotation.UnsupportedAppUsage(maxTargetSdk=Build.VERSION_CODES.Q, publicAlternatives=\"{@code \" + \"#getLastRejectTime(int, int, int)}\") long getRejectTime()\npublic @java.lang.Deprecated long getLastRejectTime(int)\npublic @java.lang.Deprecated long getLastRejectForegroundTime(int)\npublic @java.lang.Deprecated long getLastRejectBackgroundTime(int)\npublic @java.lang.Deprecated long getLastRejectTime(int,int,int)\npublic long getAccessTime(int,int)\npublic long getRejectTime(int,int)\npublic boolean isRunning()\nprivate android.app.NoteOpEvent getLastAccessEvent(int,int,int)\npublic @java.lang.Deprecated long getDuration()\npublic @java.lang.Deprecated long getLastForegroundDuration(int)\npublic @java.lang.Deprecated long getLastBackgroundDuration(int)\npublic @java.lang.Deprecated long getLastDuration(int,int,int)\npublic @java.lang.Deprecated int getProxyUid()\npublic @java.lang.Deprecated @android.annotation.Nullable java.lang.String getProxyPackageName()\nprivate @android.app.UidState int getLastAccessUidStateForFlagsInStatesOfAllFeatures(int,int,int)\npublic @android.app.UidState int getLastAccessUidState(int)\npublic @android.app.UidState int getLastForegroundAccessUidState(int)\npublic @android.app.UidState int getLastBackgroundAccessUidState(int)\nprivate @android.app.UidState int getLastRejectUidStateForFlagsInStatesOfAllFeatures(int,int,int)\npublic @android.app.UidState int getLastRejectUidState(int)\npublic @android.app.UidState int getLastForegroundRejectUidState(int)\npublic @android.app.UidState int getLastBackgroundRejectUidState(int)\npublic long getDuration(int,int)\npublic int getProxyUid(int,int)\nprivate int getProxyUid(int,int,int)\npublic @android.annotation.Nullable java.lang.String getProxyPackageName(int,int)\nprivate @android.annotation.Nullable java.lang.String getProxyPackageName(int,int,int)\nclass OpEntry extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+ */
+
+
+ //@formatter:on
+ // End of generated code
+
}
/** @hide */
@@ -4890,71 +5320,6 @@
}
/**
- * Finds the first non-negative value for the given flags in between the begin and
- * end UID states.
- *
- * @param counts The data array.
- * @param beginUidState The beginning UID state (inclusive).
- * @param endUidState The end UID state (inclusive).
- * @param flags The UID flags.
- * @return The non-negative value or -1.
- */
- private static long findFirstNonNegativeForFlagsInStates(@Nullable LongSparseLongArray counts,
- @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) {
- if (counts == null) {
- return -1;
- }
- while (flags != 0) {
- final int flag = 1 << Integer.numberOfTrailingZeros(flags);
- flags &= ~flag;
- for (int uidState : UID_STATES) {
- if (uidState < beginUidState || uidState > endUidState) {
- continue;
- }
- final long key = makeKey(uidState, flag);
- final long value = counts.get(key);
- if (value >= 0) {
- return value;
- }
- }
- }
- return -1;
- }
-
- /**
- * Finds the first non-null value for the given flags in between the begin and
- * end UID states.
- *
- * @param counts The data array.
- * @param beginUidState The beginning UID state (inclusive).
- * @param endUidState The end UID state (inclusive).
- * @param flags The UID flags.
- * @return The non-negative value or -1.
- */
- private static @Nullable String findFirstNonNullForFlagsInStates(
- @Nullable LongSparseArray<String> counts, @UidState int beginUidState,
- @UidState int endUidState, @OpFlags int flags) {
- if (counts == null) {
- return null;
- }
- while (flags != 0) {
- final int flag = 1 << Integer.numberOfTrailingZeros(flags);
- flags &= ~flag;
- for (int uidState : UID_STATES) {
- if (uidState < beginUidState || uidState > endUidState) {
- continue;
- }
- final long key = makeKey(uidState, flag);
- final String value = counts.get(key);
- if (value != null) {
- return value;
- }
- }
- }
- return null;
- }
-
- /**
* Callback for notification of changes to operation state.
*/
public interface OnOpChangedListener {
@@ -5112,6 +5477,7 @@
*
* @hide
*/
+ @TestApi
@SystemApi
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
public @NonNull List<AppOpsManager.PackageOps> getOpsForPackage(int uid,
@@ -6135,22 +6501,28 @@
}
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * @deprecated Use own local {@link android.os.Binder#Binder()}
+ *
+ * @hide
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Create own "
+ + "local {@link android.os.Binder}")
public static IBinder getToken(IAppOpsService service) {
- synchronized (AppOpsManager.class) {
- if (sToken != null) {
- return sToken;
- }
- try {
- sToken = service.getToken(new Binder());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- return sToken;
- }
+ return getClientId();
}
+ /** @hide */
+ public static IBinder getClientId() {
+ synchronized (AppOpsManager.class) {
+ if (sClientId == null) {
+ sClientId = new Binder();
+ }
+
+ return sClientId;
+ }
+ }
/**
* @deprecated use {@link #startOp(String, int, String, String, String)} instead
@@ -6207,7 +6579,7 @@
* the package is not in the passed in UID.
*/
public int startOp(@NonNull String op, int uid, @Nullable String packageName,
- @NonNull String featureId, @Nullable String message) {
+ @Nullable String featureId, @Nullable String message) {
return startOp(strOpToOp(op), uid, packageName, false, featureId, message);
}
@@ -6231,7 +6603,7 @@
* @hide
*/
public int startOp(int op, int uid, @Nullable String packageName, boolean startIfModeDefault,
- @NonNull String featureId, @Nullable String message) {
+ @Nullable String featureId, @Nullable String message) {
final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault, featureId,
message);
if (mode == MODE_ERRORED) {
@@ -6307,7 +6679,7 @@
public int startOpNoThrow(int op, int uid, @NonNull String packageName,
boolean startIfModeDefault, @Nullable String featureId, @Nullable String message) {
try {
- int mode = mService.startOperation(getToken(mService), op, uid, packageName,
+ int mode = mService.startOperation(getClientId(), op, uid, packageName,
featureId, startIfModeDefault);
if (mode == MODE_ALLOWED) {
markAppOpNoted(uid, packageName, op, featureId, message);
@@ -6367,7 +6739,7 @@
public void finishOp(int op, int uid, @NonNull String packageName,
@Nullable String featureId) {
try {
- mService.finishOperation(getToken(mService), op, uid, packageName, featureId);
+ mService.finishOperation(getClientId(), op, uid, packageName, featureId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -6932,22 +7304,23 @@
}
/**
- * Computes the max for the given flags in between the begin and
- * end UID states.
+ * Gets the last of the event.
*
- * @param counts The data array.
- * @param flags The UID flags.
- * @param beginUidState The beginning UID state (exclusive).
- * @param endUidState The end UID state.
- * @return The sum.
+ * @param events The events
+ * @param flags The UID flags
+ * @param beginUidState The maximum UID state (inclusive)
+ * @param endUidState The minimum UID state (inclusive)
+ *
+ * @return The last event of {@code null}
*/
- private static long maxForFlagsInStates(@Nullable LongSparseLongArray counts,
- @UidState int beginUidState, @UidState int endUidState,
- @OpFlags int flags) {
- if (counts == null) {
- return 0;
+ private static @Nullable NoteOpEvent getLastEvent(
+ @Nullable LongSparseArray<NoteOpEvent> events, @UidState int beginUidState,
+ @UidState int endUidState, @OpFlags int flags) {
+ if (events == null) {
+ return null;
}
- long max = 0;
+
+ NoteOpEvent lastEvent = null;
while (flags != 0) {
final int flag = 1 << Integer.numberOfTrailingZeros(flags);
flags &= ~flag;
@@ -6956,12 +7329,16 @@
continue;
}
final long key = makeKey(uidState, flag);
- max = Math.max(max, counts.get(key));
+
+ NoteOpEvent event = events.get(key);
+ if (lastEvent == null || event != null && event.noteTime > lastEvent.noteTime) {
+ lastEvent = event;
+ }
}
}
- return max;
- }
+ return lastEvent;
+ }
private static void writeLongSparseLongArrayToParcel(
@Nullable LongSparseLongArray array, @NonNull Parcel parcel) {
@@ -6990,33 +7367,6 @@
return array;
}
- private static void writeLongSparseStringArrayToParcel(
- @Nullable LongSparseArray<String> array, @NonNull Parcel parcel) {
- if (array != null) {
- final int size = array.size();
- parcel.writeInt(size);
- for (int i = 0; i < size; i++) {
- parcel.writeLong(array.keyAt(i));
- parcel.writeString(array.valueAt(i));
- }
- } else {
- parcel.writeInt(-1);
- }
- }
-
- private static @Nullable LongSparseArray<String> readLongSparseStringArrayFromParcel(
- @NonNull Parcel parcel) {
- final int size = parcel.readInt();
- if (size < 0) {
- return null;
- }
- final LongSparseArray<String> array = new LongSparseArray<>(size);
- for (int i = 0; i < size; i++) {
- array.append(parcel.readLong(), parcel.readString());
- }
- return array;
- }
-
/**
* Collects the keys from an array to the result creating the result if needed.
*
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index e12942f..941467f 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -19,7 +19,7 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 2e59b90..bac432e4 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.SharedLibraryInfo;
import android.os.Build;
import android.os.GraphicsEnvironment;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 034826a..7f26565 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -20,9 +20,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.annotation.XmlRes;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index 958ebae..0e1f921 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -285,7 +285,7 @@
time = 1571327470155L,
codegenVersion = "1.0.9",
sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
- inputSignatures = "private final @android.annotation.IntRange(from=0L, to=91L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\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=92L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\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/ContentProviderHolder.java b/core/java/android/app/ContentProviderHolder.java
index 004dca1a..3d74583 100644
--- a/core/java/android/app/ContentProviderHolder.java
+++ b/core/java/android/app/ContentProviderHolder.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProviderNative;
import android.content.IContentProvider;
import android.content.pm.ProviderInfo;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 46f88d5..bd87fcd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -99,6 +99,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteOrder;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -251,6 +252,8 @@
@GuardedBy("mSync")
private File mFilesDir;
@GuardedBy("mSync")
+ private File mCratesDir;
+ @GuardedBy("mSync")
private File mNoBackupFilesDir;
@GuardedBy("mSync")
private File mCacheDir;
@@ -702,6 +705,24 @@
}
@Override
+ public File getCrateDir(@NonNull String crateId) {
+ Preconditions.checkArgument(FileUtils.isValidExtFilename(crateId), "invalidated crateId");
+ final Path cratesRootPath = getDataDir().toPath().resolve("crates");
+ final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
+ .toAbsolutePath().normalize();
+
+ synchronized (mSync) {
+ if (mCratesDir == null) {
+ mCratesDir = cratesRootPath.toFile();
+ }
+ ensurePrivateDirExists(mCratesDir);
+ }
+
+ File cratedDir = absoluteNormalizedCratePath.toFile();
+ return ensurePrivateDirExists(cratedDir);
+ }
+
+ @Override
public File getNoBackupFilesDir() {
synchronized (mSync) {
if (mNoBackupFilesDir == null) {
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 9d82ffa..195c3e1 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -19,7 +19,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.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 39f1e95..5e05506 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -24,7 +24,7 @@
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.StyleRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index bfc15c2..e4c84d7 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 80c9ba2..49c389a 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -22,7 +22,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.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -1314,8 +1314,8 @@
// TODO: DownloadProvider.update() should take care of updating corresponding
// MediaProvider entries.
- MediaStore.scanFile(context, before);
- MediaStore.scanFile(context, after);
+ MediaStore.scanFile(mResolver, before);
+ MediaStore.scanFile(mResolver, after);
final ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_TITLE, displayName);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 4f121aa..c6a0de4 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -21,7 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 9316be7..f021f76 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -17,7 +17,7 @@
package android.app;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 26b4a11..9e887b8 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.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.Intent;
import android.content.IntentSender;
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 68daf44..904c4735 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -22,7 +22,7 @@
import android.animation.AnimatorSet;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index aa8a302..e8494c4 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -388,6 +388,7 @@
void requestFullBugReport();
void requestRemoteBugReport();
boolean launchBugReportHandlerApp();
+ List<String> getBugreportWhitelistedPackages();
@UnsupportedAppUsage
Intent getIntentForIntentSender(in IIntentSender sender);
@@ -582,4 +583,9 @@
* unlockProgressListener can be null if monitoring progress is not necessary.
*/
boolean startUserInForegroundWithListener(int userid, IProgressListener unlockProgressListener);
+
+ /**
+ * Method for the app to tell system that it's wedged and would like to trigger an ANR.
+ */
+ void appNotResponding(String reason);
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 37136a1..df5d6c7 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -164,7 +164,7 @@
boolean convertToTranslucent(in IBinder token, in Bundle options);
void notifyActivityDrawn(in IBinder token);
void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle);
- int getActivityDisplayId(in IBinder activityToken);
+ int getDisplayId(in IBinder activityToken);
boolean isImmersive(in IBinder token);
void setImmersive(in IBinder token, boolean immersive);
boolean isTopActivityImmersive();
@@ -405,6 +405,14 @@
void setDisablePreviewScreenshots(IBinder token, boolean disable);
/**
+ * It should only be called from home activity to remove its outdated snapshot. The home
+ * snapshot is used to speed up entering home from screen off. If the content of home activity
+ * is significantly different from before taking the snapshot, then the home activity can use
+ * this method to avoid inconsistent transition.
+ */
+ void invalidateHomeTaskSnapshot(IBinder homeToken);
+
+ /**
* Return the user id of last resumed activity.
*/
int getLastResumedActivityUserId();
@@ -429,6 +437,11 @@
void registerRemoteAnimations(in IBinder token, in RemoteAnimationDefinition definition);
/**
+ * Unregisters all remote animations for a specific activity.
+ */
+ void unregisterRemoteAnimations(in IBinder token);
+
+ /**
* Registers a remote animation to be run for all activity starts from a certain package during
* a short predefined amount of time.
*/
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index d3d7c68..254657e 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -84,6 +84,19 @@
long appVersionCode, in ParcelFileDescriptor newState,
int token, IBackupManager callbackBinder);
+ /**
+ * Restore an entire data snapshot to the application and pass the list of excluded keys to the
+ * backup agent.
+ *
+ * @param excludedKeys List of keys to be excluded from the restore. It will be passed to the
+ * backup agent to make it aware of what data has been removed (in case it has any
+ * application-level implications) as well as the data that should be removed by the
+ * agent itself.
+ */
+ void doRestoreWithExcludedKeys(in ParcelFileDescriptor data,
+ long appVersionCode, in ParcelFileDescriptor newState,
+ int token, IBackupManager callbackBinder, in List<String> excludedKeys);
+
/**
* Perform a "full" backup to the given file descriptor. The output file is presumed
* to be a socket or other non-seekable, write-only data sink. When this method is
diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl
index 7318762..8618fbb 100644
--- a/core/java/android/app/IInstantAppResolver.aidl
+++ b/core/java/android/app/IInstantAppResolver.aidl
@@ -16,15 +16,13 @@
package android.app;
-import android.content.Intent;
+import android.content.pm.InstantAppRequestInfo;
import android.os.IRemoteCallback;
/** @hide */
oneway interface IInstantAppResolver {
- void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
- int userId, String token, int sequence, IRemoteCallback callback);
+ void getInstantAppResolveInfoList(in InstantAppRequestInfo request, int sequence,
+ IRemoteCallback callback);
- void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
- int userId, String token, IRemoteCallback callback);
-
+ void getInstantAppIntentFilterList(in InstantAppRequestInfo request, IRemoteCallback callback);
}
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index a7be542..a413c60 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -21,6 +21,7 @@
import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.InstantAppRequestInfo;
import android.content.pm.InstantAppResolveInfo;
import android.os.Build;
import android.os.Bundle;
@@ -59,8 +60,9 @@
* Called to retrieve resolve info for instant applications immediately.
*
* @param digestPrefix The hash prefix of the instant app's domain.
- * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
- * String, InstantAppResolutionCallback)}.
+ *
+ * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
@Deprecated
public void onGetInstantAppResolveInfo(@Nullable int[] digestPrefix, @NonNull String token,
@@ -73,8 +75,9 @@
* sources.
*
* @param digestPrefix The hash prefix of the instant app's domain.
- * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
- * String, InstantAppResolutionCallback)}.
+ *
+ * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
@Deprecated
public void onGetInstantAppIntentFilter(@Nullable int[] digestPrefix, @NonNull String token,
@@ -103,8 +106,8 @@
*
* @see InstantAppResolveInfo
*
- * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
- * String, InstantAppResolutionCallback)}.
+ * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
@Deprecated
public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
@@ -134,8 +137,8 @@
* {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
* @param callback The {@link InstantAppResolutionCallback} to provide results to.
*
- * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
- * String, InstantAppResolutionCallback)}.
+ * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
@Deprecated
public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
@@ -170,7 +173,11 @@
* @param callback The {@link InstantAppResolutionCallback} to provide results to.
*
* @see InstantAppResolveInfo
+ *
+ * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+ * InstantAppResolutionCallback
*/
+ @Deprecated
public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
@Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
@NonNull String token, @NonNull InstantAppResolutionCallback callback) {
@@ -193,7 +200,11 @@
* Intent, int[], UserHandle, String, InstantAppResolutionCallback)} and provided
* to the currently visible installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
* @param callback The {@link InstantAppResolutionCallback} to provide results to.
+ *
+ * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
*/
+ @Deprecated
public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
@Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
@NonNull String token, @NonNull InstantAppResolutionCallback callback) {
@@ -202,6 +213,41 @@
}
/**
+ * Called to retrieve resolve info for instant applications immediately. The response will be
+ * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided
+ * in response to this method may be partial to request a second phase of resolution which will
+ * result in a subsequent call to {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+ * InstantAppResolutionCallback)}
+ *
+ * @param request The parameters for this resolution request
+ * @param callback The {@link InstantAppResolutionCallback} to provide results to.
+ *
+ * @see InstantAppResolveInfo
+ */
+ public void onGetInstantAppResolveInfo(@NonNull InstantAppRequestInfo request,
+ @NonNull InstantAppResolutionCallback callback) {
+ // If not overridden, forward to the old method.
+ onGetInstantAppResolveInfo(request.intent, request.hostDigestPrefix, request.userHandle,
+ request.token, callback);
+ }
+
+ /**
+ * Called to retrieve intent filters for potentially matching instant applications. Unlike
+ * {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo, InstantAppResolutionCallback)},
+ * the response may take as long as necessary to respond. All {@link InstantAppResolveInfo}s
+ * provided in response to this method must be completely populated.
+ *
+ * @param request The parameters for this resolution request
+ * @param callback The {@link InstantAppResolutionCallback} to provide results to.
+ */
+ public void onGetInstantAppIntentFilter(@NonNull InstantAppRequestInfo request,
+ @NonNull InstantAppResolutionCallback callback) {
+ // If not overridden, forward to the old method.
+ onGetInstantAppIntentFilter(request.intent, request.hostDigestPrefix, request.userHandle,
+ request.token, callback);
+ }
+
+ /**
* Returns a {@link Looper} to perform service operations on.
*/
Looper getLooper() {
@@ -218,35 +264,29 @@
public final IBinder onBind(Intent intent) {
return new IInstantAppResolver.Stub() {
@Override
- public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix,
- int userId, String token, int sequence, IRemoteCallback callback) {
+ public void getInstantAppResolveInfoList(InstantAppRequestInfo request, int sequence,
+ IRemoteCallback callback) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "[" + token + "] Phase1 called; posting");
+ Slog.v(TAG, "[" + request.token + "] Phase1 called; posting");
}
final SomeArgs args = SomeArgs.obtain();
- args.arg1 = callback;
- args.arg2 = digestPrefix;
- args.arg3 = userId;
- args.arg4 = token;
- args.arg5 = sanitizedIntent;
- mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO,
- sequence, 0, args).sendToTarget();
+ args.arg1 = request;
+ args.arg2 = callback;
+ mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence,
+ 0, args).sendToTarget();
}
@Override
- public void getInstantAppIntentFilterList(Intent sanitizedIntent,
- int[] digestPrefix, int userId, String token, IRemoteCallback callback) {
+ public void getInstantAppIntentFilterList(InstantAppRequestInfo request,
+ IRemoteCallback callback) {
if (DEBUG_INSTANT) {
- Slog.v(TAG, "[" + token + "] Phase2 called; posting");
+ Slog.v(TAG, "[" + request.token + "] Phase2 called; posting");
}
final SomeArgs args = SomeArgs.obtain();
- args.arg1 = callback;
- args.arg2 = digestPrefix;
- args.arg3 = userId;
- args.arg4 = token;
- args.arg5 = sanitizedIntent;
- mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
- args).sendToTarget();
+ args.arg1 = request;
+ args.arg2 = callback;
+ mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, args)
+ .sendToTarget();
}
};
}
@@ -287,36 +327,33 @@
switch (action) {
case MSG_GET_INSTANT_APP_RESOLVE_INFO: {
final SomeArgs args = (SomeArgs) message.obj;
- final IRemoteCallback callback = (IRemoteCallback) args.arg1;
- final int[] digestPrefix = (int[]) args.arg2;
- final int userId = (int) args.arg3;
- final String token = (String) args.arg4;
- final Intent intent = (Intent) args.arg5;
+ final InstantAppRequestInfo request = (InstantAppRequestInfo) args.arg1;
+ final IRemoteCallback callback = (IRemoteCallback) args.arg2;
+ args.recycle();
final int sequence = message.arg1;
if (DEBUG_INSTANT) {
- Slog.d(TAG, "[" + token + "] Phase1 request;"
- + " prefix: " + Arrays.toString(digestPrefix)
- + ", userId: " + userId);
+ Slog.d(TAG, "[" + request.token + "] Phase1 request;"
+ + " prefix: " + Arrays.toString(request.hostDigestPrefix)
+ + ", userId: " + request.userHandle.getIdentifier());
}
- onGetInstantAppResolveInfo(intent, digestPrefix, UserHandle.of(userId), token,
+ onGetInstantAppResolveInfo(request,
new InstantAppResolutionCallback(sequence, callback));
} break;
case MSG_GET_INSTANT_APP_INTENT_FILTER: {
final SomeArgs args = (SomeArgs) message.obj;
- final IRemoteCallback callback = (IRemoteCallback) args.arg1;
- final int[] digestPrefix = (int[]) args.arg2;
- final int userId = (int) args.arg3;
- final String token = (String) args.arg4;
- final Intent intent = (Intent) args.arg5;
+ final InstantAppRequestInfo request = (InstantAppRequestInfo) args.arg1;
+ final IRemoteCallback callback = (IRemoteCallback) args.arg2;
+ args.recycle();
if (DEBUG_INSTANT) {
- Slog.d(TAG, "[" + token + "] Phase2 request;"
- + " prefix: " + Arrays.toString(digestPrefix)
- + ", userId: " + userId);
+ Slog.d(TAG, "[" + request.token + "] Phase2 request;"
+ + " prefix: " + Arrays.toString(request.hostDigestPrefix)
+ + ", userId: " + request.userHandle.getIdentifier());
}
- onGetInstantAppIntentFilter(intent, digestPrefix, UserHandle.of(userId), token,
+ onGetInstantAppIntentFilter(request,
new InstantAppResolutionCallback(-1 /*sequence*/, callback));
- } break;
+ }
+ break;
default: {
throw new IllegalArgumentException("Unknown message: " + action);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 588acee..9e552e6 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.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.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -493,6 +493,7 @@
public Activity startActivitySync(@NonNull Intent intent, @Nullable Bundle options) {
validateNotAppThread();
+ final Activity activity;
synchronized (mSync) {
intent = new Intent(intent);
@@ -527,16 +528,18 @@
} catch (InterruptedException e) {
}
} while (mWaitingActivities.contains(aw));
-
- waitForEnterAnimationComplete(aw.activity);
-
- // Apply an empty transaction to ensure SF has a chance to update before
- // the Activity is ready (b/138263890).
- try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
- t.apply(true);
- }
- return aw.activity;
+ activity = aw.activity;
}
+
+ // Do not call this method within mSync, lest it could block the main thread.
+ waitForEnterAnimationComplete(activity);
+
+ // Apply an empty transaction to ensure SF has a chance to update before
+ // the Activity is ready (b/138263890).
+ try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
+ t.apply(true);
+ }
+ return activity;
}
/**
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 74fb99a..71b28fb 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -16,9 +16,9 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
-import android.annotation.WorkerThread;
import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index b1565ab..6518652 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -23,8 +23,8 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.trust.ITrustManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -118,6 +118,16 @@
public static final int RESULT_ALTERNATE = 1;
/**
+ *
+ * If this is set, check device policy for allowed biometrics when the user is authenticating.
+ * This should only be used in the context of managed profiles.
+ *
+ * @hide
+ */
+ public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm";
+
+
+ /**
* Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics
* if enrolled) for the current user of the device. The caller is expected to launch this
* activity using {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
@@ -166,6 +176,28 @@
/**
* Get an intent to prompt the user to confirm credentials (pin, pattern or password)
+ * for the given user. The caller is expected to launch this activity using
+ * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+ * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
+ *
+ * @param disallowBiometricsIfPolicyExists If true check if the Device Policy Manager has
+ * disabled biometrics on the device. If biometrics are disabled, fall back to PIN/pattern/pass.
+ *
+ * @return the intent for launching the activity or null if no password is required.
+ *
+ * @hide
+ */
+ public Intent createConfirmDeviceCredentialIntent(
+ CharSequence title, CharSequence description, int userId,
+ boolean disallowBiometricsIfPolicyExists) {
+ Intent intent = this.createConfirmDeviceCredentialIntent(title, description, userId);
+ intent.putExtra(EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS,
+ disallowBiometricsIfPolicyExists);
+ return intent;
+ }
+
+ /**
+ * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
* for the previous owner of the device. The caller is expected to launch this activity using
* {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
* {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index e858e6a..453c600 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.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.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 87b064d..4033aea 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -16,9 +16,9 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.servertransaction.PendingTransactionActions;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Binder;
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 25eb958..74bc9e2 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e307142..6f63eea 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -32,7 +32,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1fc8a2b..3eee1ae 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -18,8 +18,8 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.NotificationManager.Importance;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index c4c1e4f..403fb3e 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -17,7 +17,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 850645d..fdbb8bb 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -23,8 +23,8 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Notification.Builder;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/app/PackageDeleteObserver.java b/core/java/android/app/PackageDeleteObserver.java
index b7b0b19..d8803aa 100644
--- a/core/java/android/app/PackageDeleteObserver.java
+++ b/core/java/android/app/PackageDeleteObserver.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.IPackageDeleteObserver2;
diff --git a/core/java/android/app/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index 50031e0..0820367 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.IPackageInstallObserver2;
import android.os.Bundle;
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 0407a8a..b8348c7 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.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.IIntentReceiver;
import android.content.IIntentSender;
diff --git a/core/java/android/app/Presentation.java b/core/java/android/app/Presentation.java
index cb72d4d..f864fb5 100644
--- a/core/java/android/app/Presentation.java
+++ b/core/java/android/app/Presentation.java
@@ -20,24 +20,24 @@
import static android.content.Context.WINDOW_SERVICE;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
-import android.os.Handler;
-import android.os.Message;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
/**
* Base class for presentations.
diff --git a/core/java/android/app/ProgressDialog.java b/core/java/android/app/ProgressDialog.java
index 3193eb8..432fae5e 100644
--- a/core/java/android/app/ProgressDialog.java
+++ b/core/java/android/app/ProgressDialog.java
@@ -16,9 +16,7 @@
package android.app;
-import com.android.internal.R;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -34,6 +32,8 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.R;
+
import java.text.NumberFormat;
/**
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index 7626539..82cc2c4 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -16,7 +16,7 @@
package android.app;
-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/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 08a28f5..aa11598 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -21,7 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.ApkAssets;
diff --git a/core/java/android/app/ResultInfo.java b/core/java/android/app/ResultInfo.java
index 9ee0f31..979d3db 100644
--- a/core/java/android/app/ResultInfo.java
+++ b/core/java/android/app/ResultInfo.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Build;
import android.os.Parcel;
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 8493fb2..9fe894b 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -17,7 +17,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index b10c3e2..95f55ab 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -19,7 +19,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index a01cec7..83eb2ee 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -16,17 +16,14 @@
package android.app;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Parcel;
@@ -38,6 +35,9 @@
import android.util.Xml;
import android.view.inputmethod.EditorInfo;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.HashMap;
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 9b62e3b..dc8269f 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.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.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 9f865b4..abb0cfc 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -17,10 +17,10 @@
package android.app;
import android.annotation.Nullable;
-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.content.SharedPreferences;
import android.os.Build;
import android.os.FileUtils;
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 90cd51f..2d851e0 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -27,8 +27,9 @@
import android.os.IPullAtomCallback;
import android.os.IPullAtomResultReceiver;
import android.os.IStatsCompanionService;
-import android.os.IStatsManager;
+import android.os.IStatsManagerService;
import android.os.IStatsPullerCallback;
+import android.os.IStatsd;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.AndroidException;
@@ -56,11 +57,14 @@
private final Context mContext;
@GuardedBy("sLock")
- private IStatsManager mService;
+ private IStatsd mService;
@GuardedBy("sLock")
private IStatsCompanionService mStatsCompanion;
+ @GuardedBy("sLock")
+ private IStatsManagerService mStatsManagerService;
+
/**
* Long extra of uid that added the relevant stats config.
*/
@@ -103,6 +107,20 @@
*/
public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
+ // Pull atom callback return codes.
+ /**
+ * Value indicating that this pull was successful and that the result should be used.
+ *
+ * @hide
+ **/
+ public static final int PULL_SUCCESS = 0;
+
+ /**
+ * Value indicating that this pull was unsuccessful and that the result should not be used.
+ * @hide
+ **/
+ public static final int PULL_SKIP = 1;
+
private static final long DEFAULT_COOL_DOWN_NS = 1_000_000_000L; // 1 second.
private static final long DEFAULT_TIMEOUT_NS = 10_000_000_000L; // 10 seconds.
@@ -129,7 +147,7 @@
public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
// can throw IllegalArgumentException
service.addConfiguration(configKey, config, mContext.getOpPackageName());
} catch (RemoteException e) {
@@ -166,7 +184,7 @@
public void removeConfig(long configKey) throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
service.removeConfiguration(configKey, mContext.getOpPackageName());
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect to statsd when removing configuration");
@@ -227,7 +245,7 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (pendingIntent != null) {
// Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
IBinder intentSender = pendingIntent.getTarget().asBinder();
@@ -281,7 +299,7 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (pendingIntent == null) {
service.removeDataFetchOperation(configKey, mContext.getOpPackageName());
} else {
@@ -319,7 +337,7 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (pendingIntent == null) {
service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
return new long[0];
@@ -367,7 +385,7 @@
public byte[] getReports(long configKey) throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
return service.getData(configKey, mContext.getOpPackageName());
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect to statsd when getting data");
@@ -404,7 +422,7 @@
public byte[] getStatsMetadata() throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
return service.getMetadata(mContext.getOpPackageName());
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect to statsd when getting metadata");
@@ -439,7 +457,7 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
Slog.d(TAG, "Failed to find statsd when getting experiment IDs");
@@ -476,7 +494,7 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (callback == null) {
service.unregisterPullerCallback(atomTag, mContext.getOpPackageName());
} else {
@@ -504,13 +522,11 @@
* additive fields for mapping isolated to host uids.
* @param callback The callback to be invoked when the stats service pulls the atom.
* @param executor The executor in which to run the callback
- * @throws RemoteException if unsuccessful due to failing to connect to system server.
*
* @hide
*/
public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
- @NonNull StatsPullAtomCallback callback, @NonNull Executor executor)
- throws RemoteException, SecurityException {
+ @NonNull StatsPullAtomCallback callback, @NonNull Executor executor) {
long coolDownNs = metadata == null ? DEFAULT_COOL_DOWN_NS : metadata.mCoolDownNs;
long timeoutNs = metadata == null ? DEFAULT_TIMEOUT_NS : metadata.mTimeoutNs;
int[] additiveFields = metadata == null ? new int[0] : metadata.mAdditiveFields;
@@ -518,10 +534,34 @@
additiveFields = new int[0];
}
synchronized (sLock) {
- IStatsCompanionService service = getIStatsCompanionServiceLocked();
- PullAtomCallbackInternal rec =
+ try {
+ IStatsCompanionService service = getIStatsCompanionServiceLocked();
+ PullAtomCallbackInternal rec =
new PullAtomCallbackInternal(atomTag, callback, executor);
- service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields, rec);
+ service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields,
+ rec);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Unable to register pull callback", e);
+ }
+ }
+ }
+
+ /**
+ * Unregisters a callback for an atom when that atom is to be pulled. Note that any ongoing
+ * pulls will still occur.
+ *
+ * @param atomTag The tag of the atom of which to unregister
+ *
+ * @hide
+ */
+ public void unregisterPullAtomCallback(int atomTag) {
+ synchronized (sLock) {
+ try {
+ IStatsCompanionService service = getIStatsCompanionServiceLocked();
+ service.unregisterPullAtomCallback(atomTag);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Unable to unregister pull atom callback");
+ }
}
}
@@ -540,9 +580,11 @@
public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) {
mExecutor.execute(() -> {
List<StatsEvent> data = new ArrayList<>();
- boolean success = mCallback.onPullAtom(atomTag, data);
+ int successInt = mCallback.onPullAtom(atomTag, data);
+ boolean success = successInt == PULL_SUCCESS;
StatsEventParcel[] parcels = new StatsEventParcel[data.size()];
for (int i = 0; i < data.size(); i++) {
+ parcels[i] = new StatsEventParcel();
parcels[i].buffer = data.get(i).getBytes();
}
try {
@@ -645,9 +687,9 @@
public interface StatsPullAtomCallback {
/**
* Pull data for the specified atom tag, filling in the provided list of StatsEvent data.
- * @return if the pull was successful
+ * @return {@link #PULL_SUCCESS} if the pull was successful, or {@link #PULL_SKIP} if not.
*/
- boolean onPullAtom(int atomTag, List<StatsEvent> data);
+ int onPullAtom(int atomTag, List<StatsEvent> data);
}
private class StatsdDeathRecipient implements IBinder.DeathRecipient {
@@ -660,11 +702,11 @@
}
@GuardedBy("sLock")
- private IStatsManager getIStatsManagerLocked() throws StatsUnavailableException {
+ private IStatsd getIStatsdLocked() throws StatsUnavailableException {
if (mService != null) {
return mService;
}
- mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+ mService = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
if (mService == null) {
throw new StatsUnavailableException("could not be found");
}
@@ -686,6 +728,16 @@
return mStatsCompanion;
}
+ @GuardedBy("sLock")
+ private IStatsManagerService getIStatsManagerServiceLocked() {
+ if (mStatsManagerService != null) {
+ return mStatsManagerService;
+ }
+ mStatsManagerService = IStatsManagerService.Stub.asInterface(
+ ServiceManager.getService(Context.STATS_MANAGER_SERVICE));
+ return mStatsManagerService;
+ }
+
/**
* Exception thrown when communication with the stats service fails (eg if it is not available).
* This might be thrown early during boot before the stats service has started or if it crashed.
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 28413be..a1765c8 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -23,7 +23,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.Context;
import android.os.Binder;
import android.os.IBinder;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 601b658..31c73b9 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -50,10 +50,14 @@
import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
+import android.content.integrity.AppIntegrityManager;
+import android.content.integrity.IAppIntegrityManager;
import android.content.om.IOverlayManager;
import android.content.om.OverlayManager;
import android.content.pm.CrossProfileApps;
+import android.content.pm.DataLoaderManager;
import android.content.pm.ICrossProfileApps;
+import android.content.pm.IDataLoaderManager;
import android.content.pm.IPackageManager;
import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
@@ -114,11 +118,13 @@
import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
import android.net.TestNetworkManager;
+import android.net.TetheringManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.net.wifi.WifiFrameworkInitializer;
+import android.net.wifi.wificond.WifiCondManager;
import android.nfc.NfcManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -149,11 +155,15 @@
import android.os.health.SystemHealthManager;
import android.os.image.DynamicSystemManager;
import android.os.image.IDynamicSystemService;
+import android.os.incremental.IIncrementalManagerNative;
+import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.print.IPrintManager;
import android.print.PrintManager;
+import android.security.FileIntegrityManager;
+import android.security.IFileIntegrityService;
import android.service.oemlock.IOemLockService;
import android.service.oemlock.OemLockManager;
import android.service.persistentdata.IPersistentDataBlockService;
@@ -334,6 +344,17 @@
}
});
+ registerService(Context.TETHERING_SERVICE, TetheringManager.class,
+ new CachedServiceFetcher<TetheringManager>() {
+ @Override
+ public TetheringManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.TETHERING_SERVICE);
+ if (b == null) return null;
+
+ return new TetheringManager(ctx, b);
+ }});
+
+
registerService(Context.IPSEC_SERVICE, IpSecManager.class,
new CachedServiceFetcher<IpSecManager>() {
@Override
@@ -694,6 +715,14 @@
return new EthernetManager(ctx.getOuterContext(), service);
}});
+ registerService(Context.WIFI_COND_SERVICE, WifiCondManager.class,
+ new CachedServiceFetcher<WifiCondManager>() {
+ @Override
+ public WifiCondManager createService(ContextImpl ctx) {
+ return new WifiCondManager(ctx.getOuterContext());
+ }
+ });
+
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
@@ -1187,6 +1216,7 @@
return new DynamicSystemManager(
IDynamicSystemService.Stub.asInterface(b));
}});
+
registerService(Context.BATTERY_STATS_SERVICE, BatteryStatsManager.class,
new CachedServiceFetcher<BatteryStatsManager>() {
@Override
@@ -1197,7 +1227,49 @@
return new BatteryStatsManager(
IBatteryStats.Stub.asInterface(b));
}});
+ registerService(Context.DATA_LOADER_MANAGER_SERVICE, DataLoaderManager.class,
+ new CachedServiceFetcher<DataLoaderManager>() {
+ @Override
+ public DataLoaderManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.DATA_LOADER_MANAGER_SERVICE);
+ return new DataLoaderManager(IDataLoaderManager.Stub.asInterface(b));
+ }});
+ //TODO(b/136132412): refactor this: 1) merge IIncrementalManager.aidl and
+ //IIncrementalManagerNative.aidl, 2) implement the binder interface in
+ //IncrementalManagerService.java, 3) use JNI to call native functions
+ registerService(Context.INCREMENTAL_SERVICE, IncrementalManager.class,
+ new CachedServiceFetcher<IncrementalManager>() {
+ @Override
+ public IncrementalManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.INCREMENTAL_SERVICE);
+ if (b == null) {
+ return null;
+ }
+ return new IncrementalManager(
+ IIncrementalManagerNative.Stub.asInterface(b));
+ }});
+
+ registerService(Context.FILE_INTEGRITY_SERVICE, FileIntegrityManager.class,
+ new CachedServiceFetcher<FileIntegrityManager>() {
+ @Override
+ public FileIntegrityManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.FILE_INTEGRITY_SERVICE);
+ return new FileIntegrityManager(
+ IFileIntegrityService.Stub.asInterface(b));
+ }});
//CHECKSTYLE:ON IndentationCheck
+ registerService(Context.APP_INTEGRITY_SERVICE, AppIntegrityManager.class,
+ new CachedServiceFetcher<AppIntegrityManager>() {
+ @Override
+ public AppIntegrityManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.APP_INTEGRITY_SERVICE);
+ return new AppIntegrityManager(IAppIntegrityManager.Stub.asInterface(b));
+ }});
sInitializing = true;
try {
diff --git a/core/java/android/app/TaskEmbedder.java b/core/java/android/app/TaskEmbedder.java
index 79d88fd..e5707bb 100644
--- a/core/java/android/app/TaskEmbedder.java
+++ b/core/java/android/app/TaskEmbedder.java
@@ -184,7 +184,7 @@
return false;
}
- // Create a container surface to which the ActivityDisplay will be reparented
+ // Create a container surface to which the DisplayContent will be reparented
final String name = "TaskEmbedder - " + Integer.toHexString(System.identityHashCode(this));
mSurfaceControl = new SurfaceControl.Builder()
.setContainerLayer()
@@ -276,7 +276,7 @@
* @see ActivityView.StateCallback
* @see #startActivity(Intent)
*/
- void setListener(TaskEmbedder.Listener listener) {
+ public void setListener(TaskEmbedder.Listener listener) {
mListener = listener;
if (mListener != null && isInitialized()) {
mListener.onInitialized();
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index de64db9..fe9c640 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.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.content.Intent;
import android.content.res.Configuration;
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 40d10b7..343b386 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -16,8 +16,8 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager.TaskSnapshot;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Binder;
import android.os.IBinder;
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 1b281d5..c529297 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -17,7 +17,7 @@
package android.app;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 13d566c..18a3e6e 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -25,7 +25,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 1e9bbae..82e9881 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -19,6 +19,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
@@ -40,8 +41,6 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import libcore.io.IoUtils;
import java.io.FileInputStream;
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index d8c030d..3633064 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -23,7 +23,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.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
diff --git a/core/java/android/app/UserSwitchObserver.java b/core/java/android/app/UserSwitchObserver.java
index 2f8ee744b..6abc4f0 100644
--- a/core/java/android/app/UserSwitchObserver.java
+++ b/core/java/android/app/UserSwitchObserver.java
@@ -16,7 +16,7 @@
package android.app;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.IRemoteCallback;
import android.os.RemoteException;
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index c74f8c3..08a210b 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -6,7 +6,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.os.RemoteException;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 41604ec..2507991 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -26,13 +26,14 @@
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.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
@@ -40,6 +41,8 @@
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
+import android.graphics.ColorSpace;
+import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -63,11 +66,15 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import android.view.Display;
import android.view.WindowManagerGlobal;
+import com.android.internal.R;
+
import libcore.io.IoUtils;
import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -76,7 +83,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -193,7 +203,13 @@
*/
public static final int FLAG_LOCK = 1 << 1;
+ private static final Object sSync = new Object[0];
+ @UnsupportedAppUsage
+ private static Globals sGlobals;
+
private final Context mContext;
+ private final boolean mWcgEnabled;
+ private final ColorManagementProxy mCmProxy;
/**
* Special drawable that draws a wallpaper as fast as possible. Assumes
@@ -388,13 +404,14 @@
}
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
- @SetWallpaperFlags int which) {
+ @SetWallpaperFlags int which, ColorManagementProxy cmProxy) {
return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(),
- false /* hardware */);
+ false /* hardware */, cmProxy);
}
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
- @SetWallpaperFlags int which, int userId, boolean hardware) {
+ @SetWallpaperFlags int which, int userId, boolean hardware,
+ ColorManagementProxy cmProxy) {
if (mService != null) {
try {
if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -412,7 +429,8 @@
mCachedWallpaper = null;
mCachedWallpaperUserId = 0;
try {
- mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware);
+ mCachedWallpaper = getCurrentWallpaperLocked(
+ context, userId, hardware, cmProxy);
mCachedWallpaperUserId = userId;
} catch (OutOfMemoryError e) {
Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
@@ -450,7 +468,8 @@
}
}
- private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) {
+ private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware,
+ ColorManagementProxy cmProxy) {
if (mService == null) {
Log.w(TAG, "WallpaperService not running");
return null;
@@ -458,21 +477,29 @@
try {
Bundle params = new Bundle();
- ParcelFileDescriptor fd = mService.getWallpaperWithFeature(
+ ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
context.getOpPackageName(), context.getFeatureId(), this, FLAG_SYSTEM,
params, userId);
- if (fd != null) {
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- if (hardware) {
- options.inPreferredConfig = Bitmap.Config.HARDWARE;
+
+ if (pfd != null) {
+ try (BufferedInputStream bis = new BufferedInputStream(
+ new ParcelFileDescriptor.AutoCloseInputStream(pfd))) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int data;
+ while ((data = bis.read()) != -1) {
+ baos.write(data);
}
- return BitmapFactory.decodeFileDescriptor(
- fd.getFileDescriptor(), null, options);
- } catch (OutOfMemoryError e) {
+ ImageDecoder.Source src = ImageDecoder.createSource(baos.toByteArray());
+ return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> {
+ // Mutable and hardware config can't be set at the same time.
+ decoder.setMutableRequired(!hardware);
+ // Let's do color management
+ if (cmProxy != null) {
+ cmProxy.doColorManagement(decoder, info);
+ }
+ }));
+ } catch (OutOfMemoryError | IOException e) {
Log.w(TAG, "Can't decode file", e);
- } finally {
- IoUtils.closeQuietly(fd);
}
}
} catch (RemoteException e) {
@@ -497,10 +524,6 @@
}
}
- private static final Object sSync = new Object[0];
- @UnsupportedAppUsage
- private static Globals sGlobals;
-
static void initGlobals(IWallpaperManager service, Looper looper) {
synchronized (sSync) {
if (sGlobals == null) {
@@ -514,6 +537,10 @@
if (service != null) {
initGlobals(service, context.getMainLooper());
}
+ // Check if supports mixed color spaces composition in hardware.
+ mWcgEnabled = context.getResources().getConfiguration().isScreenWideColorGamut()
+ && context.getResources().getBoolean(R.bool.config_enableWcgMode);
+ mCmProxy = new ColorManagementProxy(context);
}
/**
@@ -531,6 +558,22 @@
}
/**
+ * Indicate whether wcg (Wide Color Gamut) should be enabled.
+ * <p>
+ * Some devices lack of capability of mixed color spaces composition,
+ * enable wcg on such devices might cause memory or battery concern.
+ * <p>
+ * Therefore, in addition to {@link Configuration#isScreenWideColorGamut()},
+ * we also take mixed color spaces composition (config_enableWcgMode) into account.
+ *
+ * @see Configuration#isScreenWideColorGamut()
+ * @return True if wcg should be enabled for this device.
+ */
+ private boolean shouldEnableWideColorGamut() {
+ return mWcgEnabled;
+ }
+
+ /**
* Retrieve the current system wallpaper; if
* no wallpaper is set, the system built-in static wallpaper is returned.
* This is returned as an
@@ -546,7 +589,7 @@
* is not able to access the wallpaper.
*/
public Drawable getDrawable() {
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy);
if (bm != null) {
Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
dr.setDither(false);
@@ -777,7 +820,7 @@
* null pointer if these is none.
*/
public Drawable peekDrawable() {
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy);
if (bm != null) {
Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
dr.setDither(false);
@@ -801,7 +844,7 @@
*/
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
public Drawable getFastDrawable() {
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy);
if (bm != null) {
return new FastBitmapDrawable(bm);
}
@@ -817,7 +860,7 @@
*/
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
public Drawable peekFastDrawable() {
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy);
if (bm != null) {
return new FastBitmapDrawable(bm);
}
@@ -825,6 +868,27 @@
}
/**
+ * Whether the wallpaper supports Wide Color Gamut or not.
+ * @param which The wallpaper whose image file is to be retrieved. Must be a single
+ * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+ * @return true when supported.
+ *
+ * @see #FLAG_LOCK
+ * @see #FLAG_SYSTEM
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ public boolean wallpaperSupportsWcg(int which) {
+ if (!shouldEnableWideColorGamut()) {
+ return false;
+ }
+ Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, mCmProxy);
+ return bitmap != null && bitmap.getColorSpace() != null
+ && bitmap.getColorSpace() != ColorSpace.get(ColorSpace.Named.SRGB)
+ && mCmProxy.isSupportedColorSpace(bitmap.getColorSpace());
+ }
+
+ /**
* Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}.
*
* @hide
@@ -852,7 +916,8 @@
* @hide
*/
public Bitmap getBitmapAsUser(int userId, boolean hardware) {
- return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
+ return sGlobals.peekWallpaperBitmap(
+ mContext, true, FLAG_SYSTEM, userId, hardware, mCmProxy);
}
/**
@@ -1975,6 +2040,33 @@
return false;
}
+ /**
+ * A private class to help Globals#getCurrentWallpaperLocked handle color management.
+ */
+ private static class ColorManagementProxy {
+ private final Set<ColorSpace> mSupportedColorSpaces = new HashSet<>();
+
+ ColorManagementProxy(Context context) {
+ // Get a list of supported wide gamut color spaces.
+ Display display = context.getDisplay();
+ if (display != null) {
+ mSupportedColorSpaces.addAll(Arrays.asList(display.getSupportedWideColorGamut()));
+ }
+ }
+
+ boolean isSupportedColorSpace(ColorSpace colorSpace) {
+ return colorSpace != null && (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)
+ || mSupportedColorSpaces.contains(colorSpace));
+ }
+
+ void doColorManagement(ImageDecoder decoder, ImageDecoder.ImageInfo info) {
+ if (!isSupportedColorSpace(info.getColorSpace())) {
+ decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));
+ Log.w(TAG, "Not supported color space: " + info.getColorSpace());
+ }
+ }
+ }
+
// Private completion callback for setWallpaper() synchronization
private class WallpaperSetCompletion extends IWallpaperManagerCallback.Stub {
final CountDownLatch mLatch;
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index aa6492e..1e4f8f3 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -26,6 +26,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Parcel;
@@ -35,8 +36,6 @@
import android.util.proto.WireTypeMismatchException;
import android.view.DisplayInfo;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.IOException;
/**
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 63bc40b..a2f4414 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -17,7 +17,7 @@
package android.app.admin;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index bacb969..acdf919 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -33,13 +33,13 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.Activity;
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1349,6 +1349,9 @@
* Broadcast action: send when any policy admin changes a policy.
* This is generally used to find out when a new policy is in effect.
*
+ * If the profile owner of an organization-owned managed profile changes some user
+ * restriction explicitly on the parent user, this broadcast will <em>not</em> be
+ * sent to the parent user.
* @hide
*/
@UnsupportedAppUsage
@@ -2324,6 +2327,11 @@
/**
* Activity action: Starts the administrator to show policy compliance for the provisioning.
+ * This action is used any time that the administrator has an opportunity to show policy
+ * compliance before the end of setup wizard. This could happen as part of the admin-integrated
+ * provisioning flow (in which case this gets sent after {@link #ACTION_GET_PROVISIONING_MODE}),
+ * or it could happen during provisioning finalization if the administrator supports
+ * finalization during setup wizard.
*/
public static final String ACTION_ADMIN_POLICY_COMPLIANCE =
"android.app.action.ADMIN_POLICY_COMPLIANCE";
@@ -5053,12 +5061,17 @@
* owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL},
* {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner
* or the Certificate Installer delegate.
- * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, if the
- * algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec}
- * or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the
- * {@code keySpec} does not contain an attestation challenge.
- * @throws UnsupportedOperationException if Device ID attestation was requested but the
- * underlying hardware does not support it.
+ * @throws IllegalArgumentException in the following cases:
+ * <p>
+ * <ul>
+ * <li>The alias in {@code keySpec} is empty.</li>
+ * <li>The algorithm specification in {@code keySpec} is not
+ * {@code RSAKeyGenParameterSpec} or {@code ECGenParameterSpec}.</li>
+ * <li>Device ID attestation was requested but the {@code keySpec} does not contain an
+ * attestation challenge.</li>
+ * </ul>
+ * @throws UnsupportedOperationException if Device ID attestation or individual attestation
+ * was requested but the underlying hardware does not support it.
* @throws StrongBoxUnavailableException if the use of StrongBox for key generation was
* specified in {@code keySpec} but the device does not have one.
* @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])
@@ -5805,6 +5818,49 @@
}
/**
+ * Called by a device owner, a profile owner for the primary user or a profile
+ * owner of an organization-owned managed profile to turn auto time zone on and off.
+ * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
+ * to prevent the user from changing this setting.
+ * <p>
+ * If user restriction {@link UserManager#DISALLOW_CONFIG_DATE_TIME} is used,
+ * no user will be able set the date and time zone. Instead, the network date
+ * and time zone will be used.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param enabled Whether time zone should be obtained automatically from the network or not.
+ * @throws SecurityException if caller is not a device owner, a profile owner for the
+ * primary user, or a profile owner of an organization-owned managed profile.
+ */
+ public void setAutoTimeZone(@NonNull ComponentName admin, boolean enabled) {
+ throwIfParentInstance("setAutoTimeZone");
+ if (mService != null) {
+ try {
+ mService.setAutoTimeZone(admin, enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * @return true if auto time zone is enabled on the device.
+ * @throws SecurityException if caller is not a device owner, a profile owner for the
+ * primary user, or a profile owner of an organization-owned managed profile.
+ */
+ public boolean getAutoTimeZone(@NonNull ComponentName admin) {
+ throwIfParentInstance("getAutoTimeZone");
+ if (mService != null) {
+ try {
+ return mService.getAutoTimeZone(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Called by a device owner to set whether all users created on the device should be ephemeral.
* <p>
* The system user is exempt from this policy - it is never ephemeral.
@@ -7911,18 +7967,23 @@
* <p>
* The calling device admin must be a profile or device owner; if it is not, a security
* exception will be thrown.
+ * <p>
+ * The profile owner of an organization-owned managed profile may invoke this method on
+ * the {@link DevicePolicyManager} instance it obtained from
+ * {@link #getParentProfileInstance(ComponentName)}, for enforcing device-wide restrictions.
+ * <p>
+ * See the constants in {@link android.os.UserManager} for the list of restrictions that can
+ * be enforced device-wide.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param key The key of the restriction. See the constants in {@link android.os.UserManager}
- * for the list of keys.
+ * @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public void addUserRestriction(@NonNull ComponentName admin,
@UserManager.UserRestrictionKey String key) {
- throwIfParentInstance("addUserRestriction");
if (mService != null) {
try {
- mService.setUserRestriction(admin, key, true);
+ mService.setUserRestriction(admin, key, true, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -7934,18 +7995,22 @@
* <p>
* The calling device admin must be a profile or device owner; if it is not, a security
* exception will be thrown.
+ * <p>
+ * The profile owner of an organization-owned managed profile may invoke this method on
+ * the {@link DevicePolicyManager} instance it obtained from
+ * {@link #getParentProfileInstance(ComponentName)}, for clearing device-wide restrictions.
+ * <p>
+ * See the constants in {@link android.os.UserManager} for the list of restrictions.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param key The key of the restriction. See the constants in {@link android.os.UserManager}
- * for the list of keys.
+ * @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public void clearUserRestriction(@NonNull ComponentName admin,
@UserManager.UserRestrictionKey String key) {
- throwIfParentInstance("clearUserRestriction");
if (mService != null) {
try {
- mService.setUserRestriction(admin, key, false);
+ mService.setUserRestriction(admin, key, false, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -7959,16 +8024,20 @@
* The target user may have more restrictions set by the system or other device owner / profile
* owner. To get all the user restrictions currently set, use
* {@link UserManager#getUserRestrictions()}.
+ * <p>
+ * The profile owner of an organization-owned managed profile may invoke this method on
+ * the {@link DevicePolicyManager} instance it obtained from
+ * {@link #getParentProfileInstance(ComponentName)}, for retrieving device-wide restrictions
+ * it previously set with {@link #addUserRestriction(ComponentName, String)}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
public @NonNull Bundle getUserRestrictions(@NonNull ComponentName admin) {
- throwIfParentInstance("getUserRestrictions");
Bundle ret = null;
if (mService != null) {
try {
- ret = mService.getUserRestrictions(admin);
+ ret = mService.getUserRestrictions(admin, mParentInstance);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -11086,6 +11155,53 @@
}
/**
+ * Sets the set of package names that are allowed to request user consent for cross-profile
+ * communication.
+ *
+ * <p>Assumes that the caller is a profile owner and is the given {@code admin}.
+ *
+ * <p>Previous calls are overridden by each subsequent call to this method.
+ *
+ * @param admin the {@link DeviceAdminReceiver} this request is associated with
+ * @param packageNames the new cross-profile package names
+ */
+ public void setCrossProfilePackages(
+ @NonNull ComponentName admin, @NonNull Set<String> packageNames) {
+ throwIfParentInstance("setCrossProfilePackages");
+ if (mService != null) {
+ try {
+ mService.setCrossProfilePackages(admin, new ArrayList<>(packageNames));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the set of package names that the admin has previously set as allowed to request user
+ * consent for cross-profile communication, via {@link
+ * #setCrossProfilePackages(ComponentName, Set)}.
+ *
+ * <p>Assumes that the caller is a profile owner and is the given {@code admin}.
+ *
+ * @param admin the {@link DeviceAdminReceiver} this request is associated with
+ * @return the set of package names the admin has previously set as allowed to request user
+ * consent for cross-profile communication, via {@link
+ * #setCrossProfilePackages(ComponentName, Set)}
+ */
+ public @NonNull Set<String> getCrossProfilePackages(@NonNull ComponentName admin) {
+ throwIfParentInstance("getCrossProfilePackages");
+ if (mService != null) {
+ try {
+ return new ArraySet<>(mService.getCrossProfilePackages(admin));
+ } 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>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 34246fa..9c82ff6 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -208,8 +208,8 @@
void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
ComponentName getRestrictionsProvider(int userHandle);
- void setUserRestriction(in ComponentName who, in String key, boolean enable);
- Bundle getUserRestrictions(in ComponentName who);
+ void setUserRestriction(in ComponentName who, in String key, boolean enable, boolean parent);
+ Bundle getUserRestrictions(in ComponentName who, boolean parent);
void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
void clearCrossProfileIntentFilters(in ComponentName admin);
@@ -298,6 +298,9 @@
void setAutoTime(in ComponentName who, boolean enabled);
boolean getAutoTime(in ComponentName who);
+ void setAutoTimeZone(in ComponentName who, boolean enabled);
+ boolean getAutoTimeZone(in ComponentName who);
+
void setForceEphemeralUsers(in ComponentName who, boolean forceEpehemeralUsers);
boolean getForceEphemeralUsers(in ComponentName who);
@@ -438,6 +441,9 @@
boolean isPackageAllowedToAccessCalendarForUser(String packageName, int userHandle);
List<String> getCrossProfileCalendarPackagesForUser(int userHandle);
+ void setCrossProfilePackages(in ComponentName admin, in List<String> packageNames);
+ List<String> getCrossProfilePackages(in ComponentName admin);
+
boolean isManagedKiosk();
boolean isUnattendedManagedKiosk();
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 9727621..f0b87a8 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index db6ae4f..e5316bc0 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -1,6 +1,6 @@
package android.app.assist;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 24580b4..20aa064 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -45,7 +45,10 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -327,6 +330,28 @@
}
/**
+ * New version of {@link #onRestore(BackupDataInput, long, android.os.ParcelFileDescriptor)}
+ * that has a list of keys to be excluded from the restore. Key/value pairs for which the key
+ * is present in {@code excludedKeys} have already been excluded from the restore data by the
+ * system. The list is passed to the agent to make it aware of what data has been removed (in
+ * case it has any application-level consequences) as well as the data that should be removed
+ * by the agent itself.
+ *
+ * The default implementation calls {@link #onRestore(BackupDataInput, long,
+ * android.os.ParcelFileDescriptor)}.
+ *
+ * @param excludedKeys A list of keys to be excluded from restore.
+ *
+ * @hide
+ */
+ public void onRestore(BackupDataInput data, long appVersionCode,
+ ParcelFileDescriptor newState,
+ Set<String> excludedKeys)
+ throws IOException {
+ onRestore(data, appVersionCode, newState);
+ }
+
+ /**
* The application is having its entire file system contents backed up. {@code data}
* points to the backup destination, and the app has the opportunity to choose which
* files are to be stored. To commit a file as part of the backup, call the
@@ -1016,8 +1041,22 @@
@Override
public void doRestore(ParcelFileDescriptor data, long appVersionCode,
- ParcelFileDescriptor newState,
- int token, IBackupManager callbackBinder) throws RemoteException {
+ ParcelFileDescriptor newState, int token, IBackupManager callbackBinder)
+ throws RemoteException {
+ doRestoreInternal(data, appVersionCode, newState, token, callbackBinder,
+ /* excludedKeys */ null);
+ }
+
+ @Override
+ public void doRestoreWithExcludedKeys(ParcelFileDescriptor data, long appVersionCode,
+ ParcelFileDescriptor newState, int token, IBackupManager callbackBinder,
+ List<String> excludedKeys) throws RemoteException {
+ doRestoreInternal(data, appVersionCode, newState, token, callbackBinder, excludedKeys);
+ }
+
+ private void doRestoreInternal(ParcelFileDescriptor data, long appVersionCode,
+ ParcelFileDescriptor newState, int token, IBackupManager callbackBinder,
+ List<String> excludedKeys) throws RemoteException {
// Ensure that we're running with the app's normal permission level
long ident = Binder.clearCallingIdentity();
@@ -1029,7 +1068,9 @@
BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
try {
- BackupAgent.this.onRestore(input, appVersionCode, newState);
+ BackupAgent.this.onRestore(input, appVersionCode, newState,
+ excludedKeys != null ? new HashSet<>(excludedKeys)
+ : Collections.emptySet());
} catch (IOException ex) {
Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw new RuntimeException(ex);
diff --git a/core/java/android/app/backup/BackupDataInput.java b/core/java/android/app/backup/BackupDataInput.java
index 2a98ca7..d1383c8 100644
--- a/core/java/android/app/backup/BackupDataInput.java
+++ b/core/java/android/app/backup/BackupDataInput.java
@@ -17,7 +17,7 @@
package android.app.backup;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/core/java/android/app/backup/BackupDataInputStream.java b/core/java/android/app/backup/BackupDataInputStream.java
index 0888066..11a3d0c 100644
--- a/core/java/android/app/backup/BackupDataInputStream.java
+++ b/core/java/android/app/backup/BackupDataInputStream.java
@@ -16,9 +16,10 @@
package android.app.backup;
-import android.annotation.UnsupportedAppUsage;
-import java.io.InputStream;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.IOException;
+import java.io.InputStream;
/**
* Provides an {@link java.io.InputStream}-like interface for accessing an
diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java
index 01961e7..fb161d4 100644
--- a/core/java/android/app/backup/BackupDataOutput.java
+++ b/core/java/android/app/backup/BackupDataOutput.java
@@ -17,7 +17,7 @@
package android.app.backup;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.ParcelFileDescriptor;
import java.io.FileDescriptor;
diff --git a/core/java/android/app/backup/BackupHelperDispatcher.java b/core/java/android/app/backup/BackupHelperDispatcher.java
index e9acdbf..6faa887 100644
--- a/core/java/android/app/backup/BackupHelperDispatcher.java
+++ b/core/java/android/app/backup/BackupHelperDispatcher.java
@@ -16,7 +16,7 @@
package android.app.backup;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.ParcelFileDescriptor;
import android.util.Log;
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 93d1e71..beb4449 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.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.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/app/backup/FileBackupHelperBase.java b/core/java/android/app/backup/FileBackupHelperBase.java
index 0caab98..5ad5d08 100644
--- a/core/java/android/app/backup/FileBackupHelperBase.java
+++ b/core/java/android/app/backup/FileBackupHelperBase.java
@@ -16,7 +16,7 @@
package android.app.backup;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 9a595b2..587e883 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,7 +16,7 @@
package android.app.backup;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java
index 0ce8653..d8fa0f5 100644
--- a/core/java/android/app/backup/FullBackupDataOutput.java
+++ b/core/java/android/app/backup/FullBackupDataOutput.java
@@ -1,6 +1,6 @@
package android.app.backup;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.ParcelFileDescriptor;
/**
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 099272d..4940976 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -597,6 +597,12 @@
boolean isBackupServiceActive(int whichUser);
/**
+ * Checks if the user is ready for backup or not.
+ * @param userId User id for which this operation should be performed.
+ */
+ boolean isUserReadyForBackup(int userId);
+
+ /**
* Ask the framework which dataset, if any, the given package's data would be
* restored from if we were to install it right now.
*
diff --git a/core/java/android/app/role/RoleControllerService.java b/core/java/android/app/role/RoleControllerService.java
index 06623f9..d92c956 100644
--- a/core/java/android/app/role/RoleControllerService.java
+++ b/core/java/android/app/role/RoleControllerService.java
@@ -35,6 +35,7 @@
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -82,7 +83,7 @@
public void grantDefaultRoles(RemoteCallback callback) {
enforceCallerSystemUid("grantDefaultRoles");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::grantDefaultRoles, RoleControllerService.this,
@@ -97,7 +98,7 @@
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::onAddRoleHolder, RoleControllerService.this,
@@ -112,7 +113,7 @@
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::onRemoveRoleHolder, RoleControllerService.this,
@@ -124,7 +125,7 @@
enforceCallerSystemUid("onClearRoleHolders");
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
RoleControllerService::onClearRoleHolders, RoleControllerService.this,
@@ -146,7 +147,7 @@
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName);
callback.sendResult(qualified ? Bundle.EMPTY : null);
@@ -160,7 +161,7 @@
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName,
"packageName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
boolean visible = onIsApplicationVisibleForRole(roleName, packageName);
callback.sendResult(visible ? Bundle.EMPTY : null);
@@ -171,7 +172,7 @@
enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
boolean visible = onIsRoleVisible(roleName);
callback.sendResult(visible ? Bundle.EMPTY : null);
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index bb04a2e..61eeacc 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -42,6 +42,7 @@
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -284,7 +285,7 @@
@TestApi
public List<String> getRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- Preconditions.checkNotNull(user, "user cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
try {
return mService.getRoleHoldersAsUser(roleName, user.getIdentifier());
} catch (RemoteException e) {
@@ -321,9 +322,9 @@
@CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
- Preconditions.checkNotNull(user, "user cannot be null");
- Preconditions.checkNotNull(executor, "executor cannot be null");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.addRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),
createRemoteCallback(executor, callback));
@@ -360,9 +361,9 @@
@CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
- Preconditions.checkNotNull(user, "user cannot be null");
- Preconditions.checkNotNull(executor, "executor cannot be null");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.removeRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),
createRemoteCallback(executor, callback));
@@ -397,9 +398,9 @@
@NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor,
@NonNull Consumer<Boolean> callback) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- Preconditions.checkNotNull(user, "user cannot be null");
- Preconditions.checkNotNull(executor, "executor cannot be null");
- Preconditions.checkNotNull(callback, "callback cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.clearRoleHoldersAsUser(roleName, flags, user.getIdentifier(),
createRemoteCallback(executor, callback));
@@ -442,9 +443,9 @@
@TestApi
public void addOnRoleHoldersChangedListenerAsUser(@CallbackExecutor @NonNull Executor executor,
@NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
- Preconditions.checkNotNull(executor, "executor cannot be null");
- Preconditions.checkNotNull(listener, "listener cannot be null");
- Preconditions.checkNotNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
int userId = user.getIdentifier();
synchronized (mListenersLock) {
ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
@@ -488,8 +489,8 @@
@TestApi
public void removeOnRoleHoldersChangedListenerAsUser(
@NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
- Preconditions.checkNotNull(listener, "listener cannot be null");
- Preconditions.checkNotNull(user, "user cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
int userId = user.getIdentifier();
synchronized (mListenersLock) {
ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
@@ -529,7 +530,7 @@
@SystemApi
@TestApi
public void setRoleNamesFromController(@NonNull List<String> roleNames) {
- Preconditions.checkNotNull(roleNames, "roleNames cannot be null");
+ Objects.requireNonNull(roleNames, "roleNames cannot be null");
try {
mService.setRoleNamesFromController(roleNames);
} catch (RemoteException e) {
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 52ec3e6..4e743ca 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -18,9 +18,9 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import android.annotation.UnsupportedAppUsage;
import android.app.ClientTransactionHandler;
import android.app.ResultInfo;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index b08e5973..4d2e9a5 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -17,9 +17,9 @@
package android.app.servertransaction;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.ClientTransactionHandler;
import android.app.IApplicationThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 1236e0a..6d674ae 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -18,11 +18,11 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index bb775fc..6a4996d 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -19,8 +19,8 @@
import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
-import android.annotation.UnsupportedAppUsage;
import android.app.ClientTransactionHandler;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 5e530ee..bd1eea5 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -515,8 +515,8 @@
public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
String callingPackage) {
Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
- intent.setComponent(new ComponentName("com.android.systemui",
- "com.android.systemui.SlicePermissionActivity"));
+ intent.setComponent(ComponentName.unflattenFromString(context.getResources().getString(
+ com.android.internal.R.string.config_slicePermissionComponent)));
intent.putExtra(EXTRA_BIND_URI, sliceUri);
intent.putExtra(EXTRA_PKG, callingPackage);
intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 471606da..55f92be 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -56,6 +56,7 @@
public ManualTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
mUtcTime = Objects.requireNonNull(utcTime);
+ Objects.requireNonNull(utcTime.getValue());
}
private static ManualTimeSuggestion createFromParcel(Parcel in) {
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index dd02af7..4a89a12 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -166,7 +166,12 @@
}
/** Returns the builder for call chaining. */
- public Builder setUtcTime(TimestampedValue<Long> utcTime) {
+ public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) {
+ if (utcTime != null) {
+ // utcTime can be null, but the value it holds cannot.
+ Objects.requireNonNull(utcTime.getValue());
+ }
+
mUtcTime = utcTime;
return this;
}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 27abdcf..65b2775 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -19,7 +19,7 @@
import android.Manifest;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
diff --git a/core/java/android/app/usage/ConfigurationStats.java b/core/java/android/app/usage/ConfigurationStats.java
index da3b769..8a7107d 100644
--- a/core/java/android/app/usage/ConfigurationStats.java
+++ b/core/java/android/app/usage/ConfigurationStats.java
@@ -15,7 +15,7 @@
*/
package android.app.usage;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Parcel;
diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl
index 7eacc89..b5036da 100644
--- a/core/java/android/app/usage/IStorageStatsManager.aidl
+++ b/core/java/android/app/usage/IStorageStatsManager.aidl
@@ -18,6 +18,8 @@
import android.app.usage.StorageStats;
import android.app.usage.ExternalStorageStats;
+import android.content.pm.ParceledListSlice;
+import android.os.storage.CrateInfo;
/** {@hide} */
interface IStorageStatsManager {
@@ -31,4 +33,10 @@
StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage);
StorageStats queryStatsForUser(String volumeUuid, int userId, String callingPackage);
ExternalStorageStats queryExternalStatsForUser(String volumeUuid, int userId, String callingPackage);
+ ParceledListSlice /* CrateInfo */ queryCratesForPackage(String volumeUuid, String packageName,
+ int userId, String callingPackage);
+ ParceledListSlice /* CrateInfo */ queryCratesForUid(String volumeUuid, int uid,
+ String callingPackage);
+ ParceledListSlice /* CrateInfo */ queryCratesForUser(String volumeUuid, int userId,
+ String callingPackage);
}
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 6bade90..7412970 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -21,8 +21,8 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.usage.NetworkStats.Bucket;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.DataUsageRequest;
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index a86c27a..eecf092 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -20,6 +20,7 @@
import android.annotation.BytesLong;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.WorkerThread;
@@ -27,15 +28,19 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.storage.CrateInfo;
import android.os.storage.StorageManager;
import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.IOException;
+import java.util.Collection;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -347,4 +352,100 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Return all of crate information for the specified storageUuid, packageName, and
+ * userHandle.
+ *
+ * @param storageUuid the UUID of the storage volume you're interested in,
+ * such as {@link StorageManager#UUID_DEFAULT}.
+ * @param uid the uid you're interested in.
+ * @return the collection of crate information.
+ * @throws PackageManager.NameNotFoundException when the package name is not found.
+ * @throws IOException cause by IO, not support, or the other reasons.
+ * @hide
+ */
+ @TestApi
+ @WorkerThread
+ @NonNull
+ public Collection<CrateInfo> queryCratesForUid(@NonNull UUID storageUuid,
+ int uid) throws IOException, PackageManager.NameNotFoundException {
+ try {
+ ParceledListSlice<CrateInfo> crateInfoList =
+ mService.queryCratesForUid(convert(storageUuid), uid,
+ mContext.getOpPackageName());
+ return Objects.requireNonNull(crateInfoList).getList();
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ e.maybeRethrow(IOException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return all of crates information for the specified storageUuid, packageName, and
+ * userHandle.
+ *
+ * @param storageUuid the UUID of the storage volume you're interested in,
+ * such as {@link StorageManager#UUID_DEFAULT}.
+ * @param packageName the package name you're interested in.
+ * @param user the user you're interested in.
+ * @return the collection of crate information.
+ * @throws PackageManager.NameNotFoundException when the package name is not found.
+ * @throws IOException cause by IO, not support, or the other reasons.
+ * @hide
+ */
+ @WorkerThread
+ @TestApi
+ @NonNull
+ public Collection<CrateInfo> queryCratesForPackage(@NonNull UUID storageUuid,
+ @NonNull String packageName, @NonNull UserHandle user)
+ throws PackageManager.NameNotFoundException, IOException {
+ try {
+ ParceledListSlice<CrateInfo> crateInfoList =
+ mService.queryCratesForPackage(convert(storageUuid), packageName,
+ user.getIdentifier(), mContext.getOpPackageName());
+ return Objects.requireNonNull(crateInfoList).getList();
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ e.maybeRethrow(IOException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return all of crate information for the specified storageUuid, packageName, and
+ * userHandle.
+ *
+ * @param storageUuid the UUID of the storage volume you're interested in,
+ * such as {@link StorageManager#UUID_DEFAULT}.
+ * @param user the user you're interested in.
+ * @return the collection of crate information.
+ * @throws PackageManager.NameNotFoundException when the package name is not found.
+ * @throws IOException cause by IO, not support, or the other reasons.
+ * @hide
+ */
+ @WorkerThread
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_CRATES)
+ @NonNull
+ public Collection<CrateInfo> queryCratesForUser(@NonNull UUID storageUuid,
+ @NonNull UserHandle user) throws PackageManager.NameNotFoundException, IOException {
+ try {
+ ParceledListSlice<CrateInfo> crateInfoList =
+ mService.queryCratesForUser(convert(storageUuid), user.getIdentifier(),
+ mContext.getOpPackageName());
+ return Objects.requireNonNull(crateInfoList).getList();
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ e.maybeRethrow(IOException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 4bf9c04..d840c1c 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Parcel;
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 9d43dd3..d06baed 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -29,7 +29,7 @@
import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index fb5645a..176a181 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -23,9 +23,9 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Build;
@@ -1125,8 +1125,11 @@
/**
* Inform usage stats that the carrier privileged apps access rules have changed.
+ * <p> The caller must have {@link android.Manifest.permission#BIND_CARRIER_SERVICES} </p>
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES)
public void onCarrierPrivilegedAppsChanged() {
try {
mService.onCarrierPrivilegedAppsChanged();
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index c20cb25..467b2fb 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.IntentSender;
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 85f0e23..09d56ec 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -16,9 +16,9 @@
package android.appwidget;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.ActivityOptions;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index dbc1c19..6dea1c6 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -23,9 +23,9 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.IServiceConnection;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 2faa900..130a20d 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -18,8 +18,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-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.ActivityInfo;
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 64df0e8..d8c653c6 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -24,7 +24,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
import android.os.Build;
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index cf33676..8993de0 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -17,9 +17,11 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -39,6 +41,7 @@
*
* @hide
*/
+@SystemApi
public final class BluetoothA2dpSink implements BluetoothProfile {
private static final String TAG = "BluetoothA2dpSink";
private static final boolean DBG = true;
@@ -59,71 +62,14 @@
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
+ * @hide
*/
+ @SystemApi
+ @SuppressLint("ActionValue")
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
- /**
- * Intent used to broadcast the change in the Playing state of the A2DP Sink
- * profile.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
- * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * </ul>
- *
- * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
- * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- */
- public static final String ACTION_PLAYING_STATE_CHANGED =
- "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED";
-
- /**
- * A2DP sink device is streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
- */
- public static final int STATE_PLAYING = 10;
-
- /**
- * A2DP sink device is NOT streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
- */
- public static final int STATE_NOT_PLAYING = 11;
-
- /**
- * Intent used to broadcast the change in the Playing state of the A2DP Sink
- * profile.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link #EXTRA_AUDIO_CONFIG} - The audio configuration for the remote device. </li>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * </ul>
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- */
- public static final String ACTION_AUDIO_CONFIG_CHANGED =
- "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED";
-
- /**
- * Extra for the {@link #ACTION_AUDIO_CONFIG_CHANGED} intent.
- *
- * This extra represents the current audio configuration of the A2DP source device.
- * {@see BluetoothAudioConfig}
- */
- public static final String EXTRA_AUDIO_CONFIG =
- "android.bluetooth.a2dp-sink.profile.extra.AUDIO_CONFIG";
-
private BluetoothAdapter mAdapter;
private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector =
new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK,
@@ -170,13 +116,11 @@
* the state. Users can get the connection state of the profile
* from this intent.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothA2dpSink service = getService();
@@ -210,14 +154,12 @@
* {@link #STATE_DISCONNECTING} can be used to distinguish between the
* two scenarios.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothA2dpSink service = getService();
@@ -235,6 +177,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getConnectedDevices() {
@@ -254,6 +198,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -273,6 +219,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public int getConnectionState(BluetoothDevice device) {
@@ -300,6 +248,8 @@
* @return audio configuration for the device, or null
*
* {@see BluetoothAudioConfig}
+ *
+ * @hide
*/
public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
if (VDBG) log("getAudioConfig(" + device + ")");
@@ -347,7 +297,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 IBluetoothA2dpSink service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -395,7 +345,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public int getConnectionPolicy(BluetoothDevice device) {
+ public int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothA2dpSink service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -411,13 +361,16 @@
}
/**
- * Check if A2DP profile is streaming music.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ * Check if audio is playing on the bluetooth device (A2DP profile is streaming music).
*
* @param device BluetoothDevice device
+ * @return true if audio is playing (A2dp is streaming music), false otherwise
+ *
+ * @hide
*/
- public boolean isA2dpPlaying(BluetoothDevice device) {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ public boolean isAudioPlaying(@Nullable BluetoothDevice device) {
final IBluetoothA2dpSink service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
try {
@@ -448,9 +401,9 @@
return "connected";
case STATE_DISCONNECTING:
return "disconnecting";
- case STATE_PLAYING:
+ case BluetoothA2dp.STATE_PLAYING:
return "playing";
- case STATE_NOT_PLAYING:
+ case BluetoothA2dp.STATE_NOT_PLAYING:
return "not playing";
default:
return "<unknown state " + state + ">";
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 3f8cb62..b1b6f0d 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -25,7 +25,6 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.bluetooth.BluetoothProfile.ConnectionPolicy;
import android.bluetooth.le.BluetoothLeAdvertiser;
@@ -36,6 +35,7 @@
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.BatteryStats;
import android.os.Binder;
@@ -847,7 +847,8 @@
}
synchronized (mLock) {
if (sBluetoothLeScanner == null) {
- sBluetoothLeScanner = new BluetoothLeScanner(mManagerService);
+ sBluetoothLeScanner = new BluetoothLeScanner(mManagerService, getOpPackageName(),
+ getFeatureId());
}
}
return sBluetoothLeScanner;
@@ -1637,6 +1638,15 @@
return ActivityThread.currentOpPackageName();
}
+ private String getFeatureId() {
+ // Workaround for legacy API for getting a BluetoothAdapter not
+ // passing a context
+ if (mContext != null) {
+ return mContext.getFeatureId();
+ }
+ return null;
+ }
+
/**
* Start the remote device discovery process.
* <p>The discovery process usually involves an inquiry scan of about 12
@@ -1674,7 +1684,7 @@
try {
mServiceLock.readLock().lock();
if (mService != null) {
- return mService.startDiscovery(getOpPackageName());
+ return mService.startDiscovery(getOpPackageName(), getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "", e);
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
index 260e2fb..905b0cee 100755
--- a/core/java/android/bluetooth/BluetoothClass.java
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -17,7 +17,7 @@
package android.bluetooth;
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/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 08d0797..93e76fa 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
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;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 49187dc..9fe4dd6 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -17,13 +17,14 @@
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.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Handler;
import android.os.Parcel;
@@ -33,8 +34,12 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.UUID;
/**
@@ -771,6 +776,13 @@
@UnsupportedAppUsage
public static final String EXTRA_SDP_SEARCH_STATUS =
"android.bluetooth.device.extra.SDP_SEARCH_STATUS";
+
+ /** @hide */
+ @IntDef(prefix = "ACCESS_", value = {ACCESS_UNKNOWN,
+ ACCESS_ALLOWED, ACCESS_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AccessPermission{}
+
/**
* For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
* {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
@@ -1096,15 +1108,14 @@
/**
* Get the most recent identified battery level of this Bluetooth device
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @return Battery level in percents from 0 to 100, or {@link #BATTERY_LEVEL_UNKNOWN} if
* Bluetooth is disabled, or device is disconnected, or does not have any battery reporting
* service, or return value is invalid
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- @UnsupportedAppUsage
public int getBatteryLevel() {
final IBluetooth service = sService;
if (service == null) {
@@ -1187,8 +1198,15 @@
return false;
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * Gets whether bonding was initiated locally
+ *
+ * @return true if bonding is initiated locally, false otherwise
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
public boolean isBondingInitiatedLocally() {
final IBluetooth service = sService;
if (service == null) {
@@ -1480,15 +1498,20 @@
return false;
}
- /** @hide */
- @UnsupportedAppUsage
- public boolean setPasskey(int passkey) {
- //TODO(BT)
- /*
- try {
- return sService.setPasskey(this, true, 4, passkey);
- } catch (RemoteException e) {Log.e(TAG, "", e);}*/
- return false;
+ /**
+ * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
+ *
+ * @return true pin has been set false for error
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean setPin(@Nullable String pin) {
+ byte[] pinBytes = convertPinToBytes(pin);
+ if (pinBytes == null) {
+ return false;
+ }
+ return setPin(pinBytes);
}
/**
@@ -1511,22 +1534,18 @@
return false;
}
- /** @hide */
- public boolean setRemoteOutOfBandData() {
- // TODO(BT)
- /*
- try {
- return sService.setRemoteOutOfBandData(this);
- } catch (RemoteException e) {Log.e(TAG, "", e);}*/
- return false;
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public boolean cancelPairingUserInput() {
+ /**
+ * Cancels pairing to this device
+ *
+ * @return true if pairing cancelled successfully, false otherwise
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean cancelPairing() {
final IBluetooth service = sService;
if (service == null) {
- Log.e(TAG, "BT not enabled. Cannot create pairing user input");
+ Log.e(TAG, "BT not enabled. Cannot cancel pairing");
return false;
}
try {
@@ -1537,17 +1556,6 @@
return false;
}
- /** @hide */
- @UnsupportedAppUsage
- public boolean isBluetoothDock() {
- // TODO(BT)
- /*
- try {
- return sService.isBluetoothDock(this);
- } catch (RemoteException e) {Log.e(TAG, "", e);}*/
- return false;
- }
-
boolean isBluetoothEnabled() {
boolean ret = false;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -1558,13 +1566,14 @@
}
/**
- * Requires {@link android.Manifest.permission#BLUETOOTH}.
+ * Gets whether the phonebook access is allowed for this bluetooth device
*
* @return Whether the phonebook access is allowed to this device. Can be {@link
* #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
public int getPhonebookAccessPermission() {
final IBluetooth service = sService;
if (service == null) {
@@ -1667,14 +1676,14 @@
}
/**
- * Requires {@link android.Manifest.permission#BLUETOOTH}.
+ * Gets whether message access is allowed to this bluetooth device
*
- * @return Whether the message access is allowed to this device. Can be {@link #ACCESS_UNKNOWN},
- * {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}.
+ * @return Whether the message access is allowed to this device.
* @hide
*/
- @UnsupportedAppUsage
- public int getMessageAccessPermission() {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ public @AccessPermission int getMessageAccessPermission() {
final IBluetooth service = sService;
if (service == null) {
return ACCESS_UNKNOWN;
@@ -1689,15 +1698,18 @@
/**
* Sets whether the message access is allowed to this device.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
*
- * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link
- * #ACCESS_REJECTED}.
+ * @param value is the value we are setting the message access permission to
* @return Whether the value has been successfully set.
* @hide
*/
- @UnsupportedAppUsage
- public boolean setMessageAccessPermission(int value) {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean setMessageAccessPermission(@AccessPermission int value) {
+ // Validates param value is one of the accepted constants
+ if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) {
+ throw new IllegalArgumentException(value + "is not a valid AccessPermission value");
+ }
final IBluetooth service = sService;
if (service == null) {
return false;
@@ -1711,13 +1723,14 @@
}
/**
- * Requires {@link android.Manifest.permission#BLUETOOTH}.
+ * Gets whether sim access is allowed for this bluetooth device
*
- * @return Whether the Sim access is allowed to this device. Can be {@link #ACCESS_UNKNOWN},
- * {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}.
+ * @return Whether the Sim access is allowed to this device.
* @hide
*/
- public int getSimAccessPermission() {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ public @AccessPermission int getSimAccessPermission() {
final IBluetooth service = sService;
if (service == null) {
return ACCESS_UNKNOWN;
@@ -1732,14 +1745,14 @@
/**
* Sets whether the Sim access is allowed to this device.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
*
* @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link
* #ACCESS_REJECTED}.
* @return Whether the value has been successfully set.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setSimAccessPermission(int value) {
final IBluetooth service = sService;
if (service == null) {
@@ -1970,7 +1983,7 @@
* @return the pin code as a UTF-8 byte array, or null if it is an invalid Bluetooth pin.
* @hide
*/
- @UnsupportedAppUsage
+ @VisibleForTesting
public static byte[] convertPinToBytes(String pin) {
if (pin == null) {
return null;
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index d616b8f..f877f04 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -16,7 +16,7 @@
package android.bluetooth;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Handler;
import android.os.ParcelUuid;
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index edacf3e..7066f47 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -15,7 +15,7 @@
*/
package android.bluetooth;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
index 0783cd2..7cc2d6b 100644
--- a/core/java/android/bluetooth/BluetoothGattDescriptor.java
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -16,7 +16,7 @@
package android.bluetooth;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
index c20faf9..13d6d70 100644
--- a/core/java/android/bluetooth/BluetoothGattService.java
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -15,7 +15,7 @@
*/
package android.bluetooth;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 0955b10..1ba2bb5 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -23,7 +23,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index 7ee29ff..6de1ffb 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -19,7 +19,7 @@
import android.Manifest;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
index 7165dd5..d1a096e 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -16,7 +16,7 @@
package android.bluetooth;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index ead8429..83eaa72 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -23,7 +23,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -335,9 +335,9 @@
* is not active, it will be null on that position. Returns empty list on error.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public List<BluetoothDevice> getActiveDevices() {
+ public @NonNull List<BluetoothDevice> getActiveDevices() {
if (VDBG) log("getActiveDevices()");
final IBluetoothHearingAid service = getService();
try {
@@ -559,8 +559,9 @@
* @return the CustomerId of the device
* @hide
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public long getHiSyncId(BluetoothDevice device) {
+ public long getHiSyncId(@Nullable BluetoothDevice device) {
if (VDBG) {
log("getCustomerId(" + device + ")");
}
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index adedff3..7ff6466 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -22,7 +22,9 @@
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
import java.util.ArrayList;
@@ -60,22 +62,34 @@
* @hide
*/
public BluetoothManager(Context context) {
- context = context.getApplicationContext();
- if (context == null) {
- throw new IllegalArgumentException(
- "context not associated with any application (using a mock context?)");
+ if (context.getFeatureId() == null) {
+ context = context.getApplicationContext();
+ if (context == null) {
+ throw new IllegalArgumentException(
+ "context not associated with any application (using a mock context?)");
+ }
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ } else {
+ IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+ if (b != null) {
+ mAdapter = new BluetoothAdapter(IBluetoothManager.Stub.asInterface(b));
+ } else {
+ Log.e(TAG, "Bluetooth binder is null");
+ mAdapter = null;
+ }
}
- // Legacy api - getDefaultAdapter does not take in the context
- mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ // Context is not initialized in constructor
if (mAdapter != null) {
mAdapter.setContext(context);
}
}
/**
- * Get the default BLUETOOTH Adapter for this device.
+ * Get the BLUETOOTH Adapter for this device.
*
- * @return the default BLUETOOTH Adapter
+ * @return the BLUETOOTH Adapter
*/
public BluetoothAdapter getAdapter() {
return mAdapter;
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 979dfd4..917e7fa 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -19,7 +19,7 @@
import android.Manifest;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 0ec473c..0aa5aac 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -19,8 +19,8 @@
import android.Manifest;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 4e97627..42f27f2 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -23,7 +23,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index d94c657..c579fdf 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -16,8 +16,11 @@
package android.bluetooth;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -54,6 +57,7 @@
*
* @hide
*/
+@SystemApi
public class BluetoothPbap implements BluetoothProfile {
private static final String TAG = "BluetoothPbap";
@@ -75,7 +79,11 @@
* {@link BluetoothProfile#STATE_DISCONNECTING}.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
* receive.
+ *
+ * @hide
*/
+ @SuppressLint("ActionValue")
+ @SystemApi
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
@@ -85,33 +93,16 @@
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
+ /** @hide */
public static final int RESULT_FAILURE = 0;
+ /** @hide */
public static final int RESULT_SUCCESS = 1;
- /** Connection canceled before completion. */
- public static final int RESULT_CANCELED = 2;
-
/**
- * An interface for notifying Bluetooth PCE IPC clients when they have
- * been connected to the BluetoothPbap service.
+ * Connection canceled before completion.
+ *
+ * @hide
*/
- public interface ServiceListener {
- /**
- * Called to notify the client when this proxy object has been
- * connected to the BluetoothPbap service. Clients must wait for
- * this callback before making IPC calls on the BluetoothPbap
- * service.
- */
- public void onServiceConnected(BluetoothPbap proxy);
-
- /**
- * Called to notify the client that this proxy object has been
- * disconnected from the BluetoothPbap service. Clients must not
- * make IPC calls on the BluetoothPbap service after this callback.
- * This callback will currently only occur if the application hosting
- * the BluetoothPbap service, but may be called more often in future.
- */
- public void onServiceDisconnected();
- }
+ public static final int RESULT_CANCELED = 2;
private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
new IBluetoothStateChangeCallback.Stub() {
@@ -127,6 +118,8 @@
/**
* Create a BluetoothPbap proxy object.
+ *
+ * @hide
*/
public BluetoothPbap(Context context, ServiceListener l) {
mContext = context;
@@ -181,6 +174,7 @@
}
}
+ /** @hide */
protected void finalize() throws Throwable {
try {
close();
@@ -194,6 +188,8 @@
* Other public functions of BluetoothPbap will return default error
* results once close() has been called. Multiple invocations of close()
* are ok.
+ *
+ * @hide
*/
public synchronized void close() {
IBluetoothManager mgr = mAdapter.getBluetoothManager();
@@ -210,6 +206,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getConnectedDevices() {
@@ -229,17 +227,22 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
+ @SystemApi
@Override
- public int getConnectionState(BluetoothDevice device) {
+ public int getConnectionState(@Nullable BluetoothDevice device) {
log("getConnectionState: device=" + device);
- final IBluetoothPbap service = mService;
- if (service == null) {
- Log.w(TAG, "Proxy not attached to service");
- return BluetoothProfile.STATE_DISCONNECTED;
- }
try {
- return service.getConnectionState(device);
+ final IBluetoothPbap service = mService;
+ if (service != null && isEnabled() && isValidDevice(device)) {
+ return service.getConnectionState(device);
+ }
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
@@ -248,6 +251,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -266,22 +271,12 @@
}
/**
- * Returns true if the specified Bluetooth device is connected (does not
- * include connecting). Returns false if not connected, or if this proxy
- * object is not currently connected to the Pbap service.
- */
- // TODO: This is currently being used by SettingsLib and internal app.
- public boolean isConnected(BluetoothDevice device) {
- return getConnectionState(device) == BluetoothAdapter.STATE_CONNECTED;
- }
-
- /**
* 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.
+ *
+ * @hide
*/
- // TODO: This is currently being used by SettingsLib and will be used in the future.
- // TODO: Must specify target device. Implement this in the service.
@UnsupportedAppUsage
public boolean disconnect(BluetoothDevice device) {
log("disconnect()");
@@ -304,7 +299,7 @@
log("Proxy object connected");
mService = IBluetoothPbap.Stub.asInterface(service);
if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothPbap.this);
+ mServiceListener.onServiceConnected(BluetoothProfile.PBAP, BluetoothPbap.this);
}
}
@@ -312,11 +307,23 @@
log("Proxy object disconnected");
doUnbind();
if (mServiceListener != null) {
- mServiceListener.onServiceDisconnected();
+ mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP);
}
}
};
+ 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/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 097a367..638e6de 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -22,7 +22,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index 9b4dabc..8bf1b58 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -19,7 +19,7 @@
import android.Manifest;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index 3a23808..88c186c 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -16,7 +16,7 @@
package android.bluetooth;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.ParcelUuid;
import android.util.Log;
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 760166b..f774369 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -16,7 +16,7 @@
package android.bluetooth;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.LocalSocket;
import android.os.ParcelFileDescriptor;
import android.os.ParcelUuid;
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 7e96c23..e274af1 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.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.ParcelUuid;
import java.nio.ByteBuffer;
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index ac126ae..9a17346 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -21,7 +21,6 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.app.ActivityThread;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
@@ -84,17 +83,25 @@
private BluetoothAdapter mBluetoothAdapter;
private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
+ private final String mOpPackageName;
+ private final String mFeatureId;
+
/**
* Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
*
* @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
+ * @param opPackageName The opPackageName of the context this object was created from
+ * @param featureId The featureId of the context this object was created from
* @hide
*/
- public BluetoothLeScanner(IBluetoothManager bluetoothManager) {
+ public BluetoothLeScanner(IBluetoothManager bluetoothManager,
+ @NonNull String opPackageName, @Nullable String featureId) {
mBluetoothManager = bluetoothManager;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mHandler = new Handler(Looper.getMainLooper());
mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
+ mOpPackageName = opPackageName;
+ mFeatureId = featureId;
}
/**
@@ -246,8 +253,8 @@
wrapper.startRegistration();
} else {
try {
- gatt.startScanForIntent(callbackIntent, settings, filters,
- ActivityThread.currentOpPackageName());
+ gatt.startScanForIntent(callbackIntent, settings, filters, mOpPackageName,
+ mFeatureId);
} catch (RemoteException e) {
return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
}
@@ -288,7 +295,7 @@
IBluetoothGatt gatt;
try {
gatt = mBluetoothManager.getBluetoothGatt();
- gatt.stopScanForIntent(callbackIntent, ActivityThread.currentOpPackageName());
+ gatt.stopScanForIntent(callbackIntent, mOpPackageName);
} catch (RemoteException e) {
}
}
@@ -448,8 +455,7 @@
} else {
mScannerId = scannerId;
mBluetoothGatt.startScan(mScannerId, mSettings, mFilters,
- mResultStorages,
- ActivityThread.currentOpPackageName());
+ mResultStorages, mOpPackageName, mFeatureId);
}
} catch (RemoteException e) {
Log.e(TAG, "fail to start le scan: " + e);
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
index 97e3f52..c0c1aa1 100644
--- a/core/java/android/bluetooth/le/ScanRecord.java
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.bluetooth.BluetoothUuid;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.ParcelUuid;
import android.util.ArrayMap;
import android.util.Log;
diff --git a/tests/utils/testutils/java/test/package-info.java b/core/java/android/companion/Association.aidl
similarity index 76%
copy from tests/utils/testutils/java/test/package-info.java
copy to core/java/android/companion/Association.aidl
index c34d7b2..2a28f1f 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/core/java/android/companion/Association.aidl
@@ -13,9 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.companion;
-/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+parcelable Association;
diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java
new file mode 100644
index 0000000..3fa6a3e
--- /dev/null
+++ b/core/java/android/companion/Association.java
@@ -0,0 +1,179 @@
+/*
+ * 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.companion;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+
+/**
+ * A record indicating that a device with a given address was confirmed by the user to be
+ * associated to a given companion app
+ *
+ * @hide
+ */
+@DataClass(genEqualsHashCode = true, genToString = true)
+public class Association implements Parcelable {
+
+ public final int userId;
+ public final @NonNull String deviceAddress;
+ public final @NonNull String companionAppPackage;
+
+
+
+
+ // Code below generated by codegen v1.0.13.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/companion/Association.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 Association(
+ int userId,
+ @NonNull String deviceAddress,
+ @NonNull String companionAppPackage) {
+ this.userId = userId;
+ this.deviceAddress = deviceAddress;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, deviceAddress);
+ this.companionAppPackage = companionAppPackage;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, companionAppPackage);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "Association { " +
+ "userId = " + userId + ", " +
+ "deviceAddress = " + deviceAddress + ", " +
+ "companionAppPackage = " + companionAppPackage +
+ " }";
+ }
+
+ @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(Association other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ Association that = (Association) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && userId == that.userId
+ && Objects.equals(deviceAddress, that.deviceAddress)
+ && Objects.equals(companionAppPackage, that.companionAppPackage);
+ }
+
+ @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 + userId;
+ _hash = 31 * _hash + Objects.hashCode(deviceAddress);
+ _hash = 31 * _hash + Objects.hashCode(companionAppPackage);
+ 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.writeInt(userId);
+ dest.writeString(deviceAddress);
+ dest.writeString(companionAppPackage);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected Association(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int _userId = in.readInt();
+ String _deviceAddress = in.readString();
+ String _companionAppPackage = in.readString();
+
+ this.userId = _userId;
+ this.deviceAddress = _deviceAddress;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, deviceAddress);
+ this.companionAppPackage = _companionAppPackage;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, companionAppPackage);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<Association> CREATOR
+ = new Parcelable.Creator<Association>() {
+ @Override
+ public Association[] newArray(int size) {
+ return new Association[size];
+ }
+
+ @Override
+ public Association createFromParcel(@NonNull Parcel in) {
+ return new Association(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1573767103332L,
+ codegenVersion = "1.0.13",
+ sourceFile = "frameworks/base/core/java/android/companion/Association.java",
+ inputSignatures = "public final int userId\npublic final @android.annotation.NonNull java.lang.String deviceAddress\npublic final @android.annotation.NonNull java.lang.String companionAppPackage\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index ac40150..1f57c7d 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.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.provider.OneTimeUseBuilder;
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index fe0123c..2649fbe 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -25,8 +25,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.provider.OneTimeUseBuilder;
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
index 0f67f6b..24be45c 100644
--- a/core/java/android/companion/BluetoothDeviceFilterUtils.java
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -21,9 +21,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanFilter;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.wifi.ScanResult;
import android.os.ParcelUuid;
import android.os.Parcelable;
diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java
index 2701619..730bc60 100644
--- a/core/java/android/companion/BluetoothLeDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLeDeviceFilter.java
@@ -25,11 +25,11 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.provider.OneTimeUseBuilder;
import android.text.TextUtils;
diff --git a/core/java/android/companion/DeviceFilter.java b/core/java/android/companion/DeviceFilter.java
index dc7cf82..c9cb072 100644
--- a/core/java/android/companion/DeviceFilter.java
+++ b/core/java/android/companion/DeviceFilter.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcelable;
import java.lang.annotation.Retention;
diff --git a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
index 5398c3c..5e3d46c 100644
--- a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
+++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl
@@ -16,9 +16,10 @@
package android.companion;
+import android.companion.Association;
import android.companion.AssociationRequest;
-import android.companion.ICompanionDeviceDiscoveryServiceCallback;
import android.companion.IFindDeviceCallback;
+import com.android.internal.infra.AndroidFuture;
/** @hide */
@@ -27,5 +28,5 @@
in AssociationRequest request,
in String callingPackage,
in IFindDeviceCallback findCallback,
- in ICompanionDeviceDiscoveryServiceCallback serviceCallback);
+ in AndroidFuture<Association> serviceCallback);
}
diff --git a/core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl
deleted file mode 100644
index c9dc019..0000000
--- a/core/java/android/companion/ICompanionDeviceDiscoveryServiceCallback.aidl
+++ /dev/null
@@ -1,25 +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 per missions and
- * limitations under the License.
- */
-
-package android.companion;
-
-/** @hide */
-interface ICompanionDeviceDiscoveryServiceCallback {
- @UnsupportedAppUsage
- oneway void onDeviceSelected(String packageName, int userId, String deviceAddress);
- @UnsupportedAppUsage
- oneway void onDeviceSelectionCancel();
-}
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index bb7d5e4..14c3387 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.OperationCanceledException;
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index f73a376..1d4d30d 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -18,11 +18,11 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.IActivityManager;
import android.app.QueuedWork;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 999ec37..9c806fa 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,7 +21,7 @@
import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.ContentResolver.SCHEME_FILE;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index dc1c700..dec9589 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 33216d7..27960b0 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.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/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 2240823..393d488 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -27,8 +27,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.PackageManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index bb65aa0..4008f2b 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -22,7 +22,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.database.CrossProcessCursorWrapper;
import android.database.Cursor;
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index dfa71f8..45ace40 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -17,7 +17,7 @@
package android.content;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.database.BulkCursorDescriptor;
import android.database.BulkCursorToCursorAdaptor;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index d4280f8..ede668a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -25,12 +25,12 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.UriGrantsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index bdd1f4c..f9f4c5d 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -16,9 +16,8 @@
package android.content;
-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.ArrayMap;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d370a38..4815d78 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -32,12 +32,12 @@
import android.annotation.StyleableRes;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.VrManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -1060,6 +1060,31 @@
public abstract File getFilesDir();
/**
+ * Returns the absolute path to the directory that is related to the crate on the filesystem.
+ * <p>
+ * The crateId require a validated file name. It can't contain any "..", ".",
+ * {@link File#separatorChar} etc..
+ * </p>
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * </p>
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *</p>
+ *
+ * @param crateId the relative validated file name under {@link Context#getDataDir()}/crates
+ * @return the crate directory file.
+ * @hide
+ */
+ @NonNull
+ @TestApi
+ public File getCrateDir(@NonNull String crateId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Returns the absolute path to the directory on the filesystem similar to
* {@link #getFilesDir()}. The difference is that files placed under this
* directory will be excluded from automatic backup to remote storage. See
@@ -3907,6 +3932,15 @@
public static final String NETWORK_STACK_SERVICE = "network_stack";
/**
+ * Use with {@link android.os.ServiceManager.getService()} to retrieve a
+ * {@link ITetheringConnector} IBinder for communicating with the tethering service
+ * @hide
+ * @see TetheringClient
+ */
+ @SystemApi
+ public static final String TETHERING_SERVICE = "tethering";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.net.IpSecManager} for encrypting Sockets or Networks with
* IPSec.
@@ -3973,6 +4007,17 @@
public static final String WIFI_SERVICE = "wifi";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.wifi.WifiCondManager} for handling management of the Wi-Fi control
+ * daemon.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.WifiCondManager
+ * @hide
+ */
+ public static final String WIFI_COND_SERVICE = "wificond";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.net.wifi.p2p.WifiP2pManager} for handling management of
* Wi-Fi peer-to-peer connections.
@@ -4325,6 +4370,15 @@
public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
/**
+ * Use with {@link #getSystemService(String)} to access the
+ * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
+
+ /**
* Official published name of the (internal) permission service.
*
* @see #getSystemService(String)
@@ -4783,6 +4837,12 @@
public static final String INCIDENT_COMPANION_SERVICE = "incidentcompanion";
/**
+ * Service to assist {@link android.app.StatsManager} that lives in system server.
+ * @hide
+ */
+ public static final String STATS_MANAGER_SERVICE = "statsmanager";
+
+ /**
* Service to assist statsd in obtaining general stats.
* @hide
*/
@@ -4905,6 +4965,8 @@
* {@link android.telephony.ims.ImsManager}.
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
/**
@@ -4973,14 +5035,22 @@
* {@link android.content.pm.DataLoaderManager}.
* @hide
*/
- public static final String DATA_LOADER_MANAGER_SERVICE = "dataloadermanager";
+ public static final String DATA_LOADER_MANAGER_SERVICE = "dataloader_manager";
/**
* Use with {@link #getSystemService(String)} to retrieve an
* {@link android.os.incremental.IncrementalManager}.
* @hide
*/
- public static final String INCREMENTAL_SERVICE = "incremental";
+ public static final String INCREMENTAL_SERVICE = "incremental_service";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.security.FileIntegrityManager}.
+ * @see #getSystemService(String)
+ * @see android.security.FileIntegrityManager
+ */
+ public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
/**
* Determine whether the given permission is allowed for a particular
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index d6442e2..6fe1187 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -21,9 +21,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -252,6 +252,16 @@
return mBase.getFilesDir();
}
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @NonNull
+ @Override
+ public File getCrateDir(@NonNull String cratedId) {
+ return mBase.getCrateDir(cratedId);
+ }
+
@Override
public File getNoBackupFilesDir() {
return mBase.getNoBackupFilesDir();
diff --git a/core/java/android/content/CursorEntityIterator.java b/core/java/android/content/CursorEntityIterator.java
index 2c630d2..952366d 100644
--- a/core/java/android/content/CursorEntityIterator.java
+++ b/core/java/android/content/CursorEntityIterator.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.database.Cursor;
import android.os.RemoteException;
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 4ccafab..4ff5cca 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
diff --git a/core/java/android/content/Entity.java b/core/java/android/content/Entity.java
index ff4f150..13137c4 100644
--- a/core/java/android/content/Entity.java
+++ b/core/java/android/content/Entity.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 1fb2958..6f477ff 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -17,7 +17,7 @@
package android.content;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7815a33..7967708 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -28,8 +28,8 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
@@ -1728,6 +1728,7 @@
* @hide
*/
@SystemApi
+ @TestApi
public static final String EXTRA_ORIGINATING_UID
= "android.intent.extra.ORIGINATING_UID";
@@ -4000,9 +4001,15 @@
* Broadcast Action: The sim card state has changed.
* For more details see TelephonyIntents.ACTION_SIM_STATE_CHANGED. This is here
* because TelephonyIntents is an internal class.
- * @hide
+ * The intent will have following extras.</p>
+ * <p>
+ * @see #EXTRA_SIM_STATE
+ * @see #EXTRA_SIM_LOCKED_REASON
+ *
* @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED} or
* {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ *
+ * @hide
*/
@Deprecated
@SystemApi
@@ -4010,6 +4017,170 @@
public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
/**
+ * The extra used with {@link #ACTION_SIM_STATE_CHANGED} for broadcasting SIM STATE.
+ * This will have one of the following intent values.
+ * @see #SIM_STATE_UNKNOWN
+ * @see #SIM_STATE_NOT_READY
+ * @see #SIM_STATE_ABSENT
+ * @see #SIM_STATE_PRESENT
+ * @see #SIM_STATE_CARD_IO_ERROR
+ * @see #SIM_STATE_CARD_RESTRICTED
+ * @see #SIM_STATE_LOCKED
+ * @see #SIM_STATE_READY
+ * @see #SIM_STATE_IMSI
+ * @see #SIM_STATE_LOADED
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_SIM_STATE = "ss";
+
+ /**
+ * The intent value UNKNOWN represents the SIM state unknown
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_STATE_UNKNOWN = "UNKNOWN";
+
+ /**
+ * The intent value NOT_READY means that the SIM is not ready eg. radio is off or powering on
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_STATE_NOT_READY = "NOT_READY";
+
+ /**
+ * The intent value ABSENT means the SIM card is missing
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_STATE_ABSENT = "ABSENT";
+
+ /**
+ * The intent value PRESENT means the device has a SIM card inserted
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_STATE_PRESENT = "PRESENT";
+
+ /**
+ * The intent value CARD_IO_ERROR means for three consecutive times there was SIM IO error
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ static public final String SIM_STATE_CARD_IO_ERROR = "CARD_IO_ERROR";
+
+ /**
+ * The intent value CARD_RESTRICTED means card is present but not usable due to carrier
+ * restrictions
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ static public final String SIM_STATE_CARD_RESTRICTED = "CARD_RESTRICTED";
+
+ /**
+ * The intent value LOCKED means the SIM is locked by PIN or by network
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_STATE_LOCKED = "LOCKED";
+
+ /**
+ * The intent value READY means the SIM is ready to be accessed
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_STATE_READY = "READY";
+
+ /**
+ * The intent value IMSI means the SIM IMSI is ready in property
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_STATE_IMSI = "IMSI";
+
+ /**
+ * The intent value LOADED means all SIM records, including IMSI, are loaded
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_CARD_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_STATE_LOADED = "LOADED";
+
+ /**
+ * The extra used with {@link #ACTION_SIM_STATE_CHANGED} for broadcasting SIM STATE.
+ * This extra will have one of the following intent values.
+ * <p>
+ * @see #SIM_LOCKED_ON_PIN
+ * @see #SIM_LOCKED_ON_PUK
+ * @see #SIM_LOCKED_NETWORK
+ * @see #SIM_ABSENT_ON_PERM_DISABLED
+ *
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String EXTRA_SIM_LOCKED_REASON = "reason";
+
+ /**
+ * The intent value PIN means the SIM is locked on PIN1
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_LOCKED_ON_PIN = "PIN";
+
+ /**
+ * The intent value PUK means the SIM is locked on PUK1
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ /* PUK means ICC is locked on PUK1 */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_LOCKED_ON_PUK = "PUK";
+
+ /**
+ * The intent value NETWORK means the SIM is locked on NETWORK PERSONALIZATION
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_LOCKED_NETWORK = "NETWORK";
+
+ /**
+ * The intent value PERM_DISABLED means SIM is permanently disabled due to puk fails
+ * @hide
+ * @deprecated Use {@link #ACTION_SIM_APPLICATION_STATE_CHANGED}
+ */
+ @Deprecated
+ @SystemApi
+ public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
+
+ /**
* Broadcast Action: indicate that the phone service state has changed.
* The intent will have the following extra values:</p>
* <p>
@@ -10879,6 +11050,7 @@
case ACTION_MEDIA_SCANNER_FINISHED:
case ACTION_MEDIA_SCANNER_SCAN_FILE:
case ACTION_PACKAGE_NEEDS_VERIFICATION:
+ case ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION:
case ACTION_PACKAGE_VERIFIED:
case ACTION_PACKAGE_ENABLE_ROLLBACK:
// Ignore legacy actions
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 93390bd..73c1e2f 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
import android.os.Parcel;
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index ec0bac4..f40dc29 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -16,14 +16,14 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.AndroidException;
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index bbdab04..885eb70 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -17,9 +17,9 @@
package android.content;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
diff --git a/core/java/android/content/SearchRecentSuggestionsProvider.java b/core/java/android/content/SearchRecentSuggestionsProvider.java
index 8ee7b9e..fc3ddf6 100644
--- a/core/java/android/content/SearchRecentSuggestionsProvider.java
+++ b/core/java/android/content/SearchRecentSuggestionsProvider.java
@@ -16,8 +16,8 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
import android.app.SearchManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
diff --git a/core/java/android/content/SyncAdapterType.java b/core/java/android/content/SyncAdapterType.java
index 8c8fe5a..7bcdbfd 100644
--- a/core/java/android/content/SyncAdapterType.java
+++ b/core/java/android/content/SyncAdapterType.java
@@ -17,11 +17,11 @@
package android.content;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
-import android.text.TextUtils;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
/**
* Value type that represents a SyncAdapterType. This object overrides {@link #equals} and
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
index d4e5217..58445a7 100644
--- a/core/java/android/content/SyncAdaptersCache.java
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.RegisteredServicesCache;
import android.content.pm.XmlSerializerAndParser;
import android.content.res.Resources;
@@ -29,8 +29,8 @@
import com.android.internal.annotations.GuardedBy;
import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.ArrayList;
diff --git a/core/java/android/content/SyncContext.java b/core/java/android/content/SyncContext.java
index 50d1dc9..4a9f66c 100644
--- a/core/java/android/content/SyncContext.java
+++ b/core/java/android/content/SyncContext.java
@@ -16,10 +16,10 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.IBinder;
public class SyncContext {
private ISyncContext mSyncContext;
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index d3f2eed..017a92b 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -17,7 +17,7 @@
package android.content;
import android.accounts.Account;
-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/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index 5f1f180..9e568a4 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -17,7 +17,7 @@
package android.content;
import android.accounts.Account;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index 0eea47a..b72eb04 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java
index f9c58d6..ed9cd86 100644
--- a/core/java/android/content/UndoManager.java
+++ b/core/java/android/content/UndoManager.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.text.TextUtils;
import android.util.ArrayMap;
diff --git a/core/java/android/content/UndoOperation.java b/core/java/android/content/UndoOperation.java
index a425486..235d721 100644
--- a/core/java/android/content/UndoOperation.java
+++ b/core/java/android/content/UndoOperation.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/content/UriMatcher.java b/core/java/android/content/UriMatcher.java
index 208bc01..7fa48f0 100644
--- a/core/java/android/content/UriMatcher.java
+++ b/core/java/android/content/UriMatcher.java
@@ -16,7 +16,7 @@
package android.content;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import java.util.ArrayList;
diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java
index c963475..70776c7 100644
--- a/core/java/android/content/integrity/AppInstallMetadata.java
+++ b/core/java/android/content/integrity/AppInstallMetadata.java
@@ -86,6 +86,19 @@
return mIsPreInstalled;
}
+ @Override
+ public String toString() {
+ return String.format(
+ "AppInstallMetadata { PackageName = %s, AppCert = %s, InstallerName = %s,"
+ + " InstallerCert = %s, VersionCode = %d, PreInstalled = %b }",
+ mPackageName,
+ mAppCertificate,
+ mInstallerName == null ? "null" : mInstallerName,
+ mInstallerCertificate == null ? "null" : mInstallerCertificate,
+ mVersionCode,
+ mIsPreInstalled);
+ }
+
/** Builder class for constructing {@link AppInstallMetadata} objects. */
public static final class Builder {
private String mPackageName;
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index c8e164f..574a93f 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -44,6 +44,7 @@
private static final String TAG = "AtomicFormula";
+ /** @hide */
@IntDef(
value = {
PACKAGE_NAME,
@@ -56,6 +57,7 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Key {}
+ /** @hide */
@IntDef(value = {EQ, LT, LE, GT, GE})
@Retention(RetentionPolicy.SOURCE)
public @interface Operator {}
diff --git a/core/java/android/content/integrity/CompoundFormula.java b/core/java/android/content/integrity/CompoundFormula.java
index 53a9953..2a651d9 100644
--- a/core/java/android/content/integrity/CompoundFormula.java
+++ b/core/java/android/content/integrity/CompoundFormula.java
@@ -47,6 +47,7 @@
public final class CompoundFormula implements Formula, Parcelable {
private static final String TAG = "OpenFormula";
+ /** @hide */
@IntDef(
value = {
AND, OR, NOT,
diff --git a/core/java/android/content/integrity/Formula.java b/core/java/android/content/integrity/Formula.java
index 030ff6b..b092a22 100644
--- a/core/java/android/content/integrity/Formula.java
+++ b/core/java/android/content/integrity/Formula.java
@@ -38,6 +38,7 @@
@SystemApi
@VisibleForTesting
public interface Formula {
+ /** @hide */
@IntDef(
value = {
COMPOUND_FORMULA_TAG,
diff --git a/core/java/android/content/integrity/Rule.java b/core/java/android/content/integrity/Rule.java
index 914f147..39a0909 100644
--- a/core/java/android/content/integrity/Rule.java
+++ b/core/java/android/content/integrity/Rule.java
@@ -42,6 +42,7 @@
@VisibleForTesting
public final class Rule implements Parcelable {
+ /** @hide */
@IntDef(
value = {
DENY,
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 1a78f79..a3487be 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -20,8 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 26193f6..5b1f926 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 552c8ac..1aed977 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -22,7 +22,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.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
diff --git a/core/java/android/content/pm/AuxiliaryResolveInfo.java b/core/java/android/content/pm/AuxiliaryResolveInfo.java
index 202df50..7d07e1d 100644
--- a/core/java/android/content/pm/AuxiliaryResolveInfo.java
+++ b/core/java/android/content/pm/AuxiliaryResolveInfo.java
@@ -46,17 +46,21 @@
public final Intent failureIntent;
/** The matching filters for this resolve info. */
public final List<AuxiliaryFilter> filters;
+ /** Stored {@link InstantAppRequest#hostDigestPrefixSecure} to prevent re-generation */
+ public final int[] hostDigestPrefixSecure;
/** Create a response for installing an instant application. */
public AuxiliaryResolveInfo(@NonNull String token,
boolean needsPhase2,
@Nullable Intent failureIntent,
- @Nullable List<AuxiliaryFilter> filters) {
+ @Nullable List<AuxiliaryFilter> filters,
+ @Nullable int[] hostDigestPrefix) {
this.token = token;
this.needsPhaseTwo = needsPhase2;
this.failureIntent = failureIntent;
this.filters = filters;
this.installFailureActivity = null;
+ this.hostDigestPrefixSecure = hostDigestPrefix;
}
/** Create a response for installing a split on demand. */
@@ -69,6 +73,7 @@
this.token = null;
this.needsPhaseTwo = false;
this.failureIntent = failureIntent;
+ this.hostDigestPrefixSecure = null;
}
/** Create a response for installing a split on demand. */
@@ -126,4 +131,4 @@
+ ", splitName='" + splitName + '\'' + '}';
}
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index 4178309..bd847bf 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -16,7 +16,7 @@
package android.content.pm;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
@@ -47,7 +47,7 @@
* TODO get this number from somewhere else. For now set it to a quarter of
* the 1MB limit.
*/
- private static final int MAX_IPC_SIZE = IBinder.MAX_IPC_SIZE;
+ private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
private final List<T> mList;
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 10f0df7..8b41c04 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -16,7 +16,7 @@
package android.content.pm;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
diff --git a/core/java/android/content/pm/DataLoaderParams.java b/core/java/android/content/pm/DataLoaderParams.java
new file mode 100644
index 0000000..60d7bb3
--- /dev/null
+++ b/core/java/android/content/pm/DataLoaderParams.java
@@ -0,0 +1,126 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.os.ParcelFileDescriptor;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * This class represents the parameters used to configure a Data Loader.
+ *
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
+ * @hide
+ */
+@SystemApi
+public class DataLoaderParams {
+ @NonNull
+ private final DataLoaderParamsParcel mData;
+
+ /**
+ * Creates and populates set of Data Loader parameters for Streaming installation.
+ *
+ * @param componentName Data Loader component supporting Streaming installation.
+ * @param arguments free form installation arguments
+ */
+ public static final @NonNull DataLoaderParams forStreaming(@NonNull ComponentName componentName,
+ @NonNull String arguments) {
+ return new DataLoaderParams(DataLoaderType.STREAMING, componentName, arguments, null);
+ }
+
+ /**
+ * Creates and populates set of Data Loader parameters for Incremental installation.
+ *
+ * @param componentName Data Loader component supporting Incremental installation.
+ * @param arguments free form installation arguments
+ * @param namedFds TODO(b/146080380) remove
+ */
+ public static final @NonNull DataLoaderParams forIncremental(
+ @NonNull ComponentName componentName, @NonNull String arguments,
+ @Nullable Map<String, ParcelFileDescriptor> namedFds) {
+ return new DataLoaderParams(DataLoaderType.INCREMENTAL, componentName, arguments, namedFds);
+ }
+
+ /** @hide */
+ public DataLoaderParams(@NonNull @DataLoaderType int type, @NonNull ComponentName componentName,
+ @NonNull String arguments, @Nullable Map<String, ParcelFileDescriptor> namedFds) {
+ DataLoaderParamsParcel data = new DataLoaderParamsParcel();
+ data.type = type;
+ data.packageName = componentName.getPackageName();
+ data.className = componentName.getClassName();
+ data.arguments = arguments;
+ if (namedFds == null || namedFds.isEmpty()) {
+ data.dynamicArgs = new NamedParcelFileDescriptor[0];
+ } else {
+ data.dynamicArgs = new NamedParcelFileDescriptor[namedFds.size()];
+ int i = 0;
+ for (Map.Entry<String, ParcelFileDescriptor> namedFd : namedFds.entrySet()) {
+ data.dynamicArgs[i] = new NamedParcelFileDescriptor();
+ data.dynamicArgs[i].name = namedFd.getKey();
+ data.dynamicArgs[i].fd = namedFd.getValue();
+ i += 1;
+ }
+ }
+ mData = data;
+ }
+
+ /** @hide */
+ DataLoaderParams(@NonNull DataLoaderParamsParcel data) {
+ mData = data;
+ }
+
+ /** @hide */
+ public final @NonNull DataLoaderParamsParcel getData() {
+ return mData;
+ }
+
+ /**
+ * @return data loader type
+ */
+ public final @NonNull @DataLoaderType int getType() {
+ return mData.type;
+ }
+
+ /**
+ * @return data loader's component name
+ */
+ public final @NonNull ComponentName getComponentName() {
+ return new ComponentName(mData.packageName, mData.className);
+ }
+
+ /**
+ * @return data loader's arguments
+ */
+ public final @NonNull String getArguments() {
+ return mData.arguments;
+ }
+
+ /**
+ * @return data loader's dynamic arguments such as file descriptors TODO: remove
+ */
+ public final @NonNull Map<String, ParcelFileDescriptor> getDynamicArgs() {
+ return Arrays.stream(mData.dynamicArgs).collect(
+ Collectors.toMap(p -> p.name, p -> p.fd));
+ }
+}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
similarity index 75%
rename from core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
rename to core/java/android/content/pm/DataLoaderParamsParcel.aidl
index cd988dc..e05843b 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/core/java/android/content/pm/DataLoaderParamsParcel.aidl
@@ -14,16 +14,19 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
-import android.os.incremental.NamedParcelFileDescriptor;
+import android.content.pm.DataLoaderType;
+import android.content.pm.NamedParcelFileDescriptor;
/**
* Class for holding data loader configuration parameters.
* @hide
*/
-parcelable IncrementalDataLoaderParamsParcel {
+parcelable DataLoaderParamsParcel {
+ DataLoaderType type;
@utf8InCpp String packageName;
- @utf8InCpp String staticArgs;
+ @utf8InCpp String className;
+ @utf8InCpp String arguments;
NamedParcelFileDescriptor[] dynamicArgs;
}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/core/java/android/content/pm/DataLoaderType.aidl
similarity index 62%
copy from core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
copy to core/java/android/content/pm/DataLoaderType.aidl
index cd988dc..7d726f5 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/core/java/android/content/pm/DataLoaderType.aidl
@@ -14,16 +14,24 @@
* limitations under the License.
*/
-package android.os.incremental;
-
-import android.os.incremental.NamedParcelFileDescriptor;
+package android.content.pm;
/**
- * Class for holding data loader configuration parameters.
+ * Types of Data Loader for an installation session.
* @hide
*/
-parcelable IncrementalDataLoaderParamsParcel {
- @utf8InCpp String packageName;
- @utf8InCpp String staticArgs;
- NamedParcelFileDescriptor[] dynamicArgs;
+@Backing(type="int")
+enum DataLoaderType {
+ /**
+ * Default value, legacy installation.
+ */
+ NONE = 0,
+ /**
+ * Streaming installation using data loader.
+ */
+ STREAMING = 1,
+ /**
+ * Streaming installation using Incremental FileSystem.
+ */
+ INCREMENTAL = 2,
}
diff --git a/core/java/android/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl
new file mode 100644
index 0000000..f00feae
--- /dev/null
+++ b/core/java/android/content/pm/FileSystemControlParcel.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.content.pm;
+
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.os.incremental.IncrementalFileSystemControlParcel;
+
+/**
+ * Wraps info needed for DataLoader to provide data.
+ * @hide
+ */
+parcelable FileSystemControlParcel {
+ // Incremental FS control descriptors.
+ @nullable IncrementalFileSystemControlParcel incremental;
+ // Callback-based installation connector.
+ @nullable IPackageInstallerSessionFileSystemConnector callback;
+}
diff --git a/core/java/android/content/pm/IDataLoader.aidl b/core/java/android/content/pm/IDataLoader.aidl
index 60cc9ba9..b5baa93 100644
--- a/core/java/android/content/pm/IDataLoader.aidl
+++ b/core/java/android/content/pm/IDataLoader.aidl
@@ -27,8 +27,9 @@
*/
oneway interface IDataLoader {
void create(int id, in Bundle params, IDataLoaderStatusListener listener);
- void start(in List<InstallationFile> fileInfos);
+ void start();
void stop();
void destroy();
- void onFileCreated(long inode, in byte[] metadata);
+
+ void prepareImage(in List<InstallationFile> addedFiles, in List<String> removedFiles);
}
diff --git a/core/java/android/content/pm/IDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
index a60d6ee..5011faa 100644
--- a/core/java/android/content/pm/IDataLoaderStatusListener.aidl
+++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
@@ -22,13 +22,18 @@
*/
oneway interface IDataLoaderStatusListener {
/** Data loader status */
- const int DATA_LOADER_READY = 0;
- const int DATA_LOADER_NOT_READY = 1;
- const int DATA_LOADER_RUNNING = 2;
+ const int DATA_LOADER_CREATED = 0;
+ const int DATA_LOADER_DESTROYED = 1;
+
+ const int DATA_LOADER_STARTED = 2;
const int DATA_LOADER_STOPPED = 3;
- const int DATA_LOADER_SLOW_CONNECTION = 4;
- const int DATA_LOADER_NO_CONNECTION = 5;
- const int DATA_LOADER_CONNECTION_OK = 6;
+
+ const int DATA_LOADER_IMAGE_READY = 4;
+ const int DATA_LOADER_IMAGE_NOT_READY = 5;
+
+ const int DATA_LOADER_SLOW_CONNECTION = 6;
+ const int DATA_LOADER_NO_CONNECTION = 7;
+ const int DATA_LOADER_CONNECTION_OK = 8;
/** Data loader status callback */
void onStatusChanged(in int dataLoaderId, in int status);
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 0b3c765..e954635 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -39,6 +39,9 @@
void transfer(in String packageName, in IntentSender statusReceiver);
void abandon();
+ void addFile(String name, long lengthBytes, in byte[] metadata);
+ void removeFile(String name);
+
boolean isMultiPackage();
int[] getChildSessionIds();
void addChildSessionId(in int sessionId);
@@ -46,5 +49,4 @@
int getParentSessionId();
boolean isStaged();
- void addFile(in String name, long size, in byte[] metadata);
}
diff --git a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl b/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
similarity index 76%
copy from core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
copy to core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
index 038ced1..4b2f29e 100644
--- a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl
@@ -14,15 +14,11 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
import android.os.ParcelFileDescriptor;
-/**
- * A named ParcelFileDescriptor.
- * @hide
- */
-parcelable NamedParcelFileDescriptor {
- @utf8InCpp String name;
- ParcelFileDescriptor fd;
+/** {@hide} */
+interface IPackageInstallerSessionFileSystemConnector {
+ void writeData(String name, long offsetBytes, long lengthBytes, in ParcelFileDescriptor fd);
}
diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java
index 361d4e4..f692db1 100644
--- a/core/java/android/content/pm/InstantAppRequest.java
+++ b/core/java/android/content/pm/InstantAppRequest.java
@@ -16,9 +16,10 @@
package android.content.pm;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Intent;
import android.os.Bundle;
-import android.text.TextUtils;
/**
* Information needed to make an instant application resolution request.
@@ -34,32 +35,40 @@
public final String resolvedType;
/** The name of the package requesting the instant application */
public final String callingPackage;
+ /** Whether or not the requesting package was an instant app */
+ public final boolean isRequesterInstantApp;
/** ID of the user requesting the instant application */
public final int userId;
/**
* Optional extra bundle provided by the source application to the installer for additional
- * verification. */
+ * verification.
+ */
public final Bundle verificationBundle;
/** Whether resolution occurs because an application is starting */
public final boolean resolveForStart;
- /** The instant app digest for this request */
- public final InstantAppResolveInfo.InstantAppDigest digest;
+ /**
+ * The hash prefix of an instant app's domain or null if no host is defined.
+ * Secure version that should be carried through for external use.
+ */
+ @Nullable
+ public final int[] hostDigestPrefixSecure;
+ /** A unique identifier */
+ @NonNull
+ public final String token;
public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
- String resolvedType, String callingPackage, int userId, Bundle verificationBundle,
- boolean resolveForStart) {
+ String resolvedType, String callingPackage, boolean isRequesterInstantApp,
+ int userId, Bundle verificationBundle, boolean resolveForStart,
+ @Nullable int[] hostDigestPrefixSecure, @NonNull String token) {
this.responseObj = responseObj;
this.origIntent = origIntent;
this.resolvedType = resolvedType;
this.callingPackage = callingPackage;
+ this.isRequesterInstantApp = isRequesterInstantApp;
this.userId = userId;
this.verificationBundle = verificationBundle;
this.resolveForStart = resolveForStart;
- if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) {
- digest = new InstantAppResolveInfo.InstantAppDigest(
- origIntent.getData().getHost(), 5 /*maxDigests*/);
- } else {
- digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED;
- }
+ this.hostDigestPrefixSecure = hostDigestPrefixSecure;
+ this.token = token;
}
}
diff --git a/tests/utils/testutils/java/test/package-info.java b/core/java/android/content/pm/InstantAppRequestInfo.aidl
similarity index 76%
copy from tests/utils/testutils/java/test/package-info.java
copy to core/java/android/content/pm/InstantAppRequestInfo.aidl
index c34d7b2..0f94220 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/core/java/android/content/pm/InstantAppRequestInfo.aidl
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+package android.content.pm;
+
+parcelable InstantAppRequestInfo;
diff --git a/core/java/android/content/pm/InstantAppRequestInfo.java b/core/java/android/content/pm/InstantAppRequestInfo.java
new file mode 100644
index 0000000..83d5536
--- /dev/null
+++ b/core/java/android/content/pm/InstantAppRequestInfo.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information exposed to {@link android.app.InstantAppResolverService} to complete
+ * an instant application resolution request.
+ * @hide
+ */
+@SystemApi
+@DataClass(genParcelable = true, genConstructor = true, genAidl = true)
+public final class InstantAppRequestInfo implements Parcelable {
+
+ /**
+ * The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+ * potential PII removed from the original intent. Fields removed include extras and the
+ * host + path of the data, if defined.
+ */
+ @NonNull
+ public final Intent intent;
+
+ /** The hash prefix of the instant app's domain or null if no host is defined. */
+ @Nullable
+ public final int[] hostDigestPrefix;
+
+ /** The user requesting the instant application */
+ @NonNull
+ public final UserHandle userHandle;
+
+ /** Whether or not the requesting package was an instant app itself */
+ public final boolean isRequesterInstantApp;
+
+ /** A unique identifier */
+ @NonNull
+ public final String token;
+
+
+
+ // Code below generated by codegen v1.0.13.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/InstantAppRequestInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new InstantAppRequestInfo.
+ *
+ * @param intent
+ * The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+ * potential PII removed from the original intent. Fields removed include extras and the
+ * host + path of the data, if defined.
+ * @param hostDigestPrefix
+ * The hash prefix of the instant app's domain or null if no host is defined.
+ * @param userHandle
+ * The user requesting the instant application
+ * @param isRequesterInstantApp
+ * Whether or not the requesting package was an instant app itself
+ * @param token
+ * A unique identifier
+ */
+ @DataClass.Generated.Member
+ public InstantAppRequestInfo(
+ @NonNull Intent intent,
+ @Nullable int[] hostDigestPrefix,
+ @NonNull UserHandle userHandle,
+ boolean isRequesterInstantApp,
+ @NonNull String token) {
+ this.intent = intent;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, intent);
+ this.hostDigestPrefix = hostDigestPrefix;
+ this.userHandle = userHandle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, userHandle);
+ this.isRequesterInstantApp = isRequesterInstantApp;
+ this.token = token;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, token);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (isRequesterInstantApp) flg |= 0x8;
+ if (hostDigestPrefix != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeTypedObject(intent, flags);
+ if (hostDigestPrefix != null) dest.writeIntArray(hostDigestPrefix);
+ dest.writeTypedObject(userHandle, flags);
+ dest.writeString(token);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InstantAppRequestInfo(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean _isRequesterInstantApp = (flg & 0x8) != 0;
+ Intent _intent = (Intent) in.readTypedObject(Intent.CREATOR);
+ int[] _hostDigestPrefix = (flg & 0x2) == 0 ? null : in.createIntArray();
+ UserHandle _userHandle = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
+ String _token = in.readString();
+
+ this.intent = _intent;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, intent);
+ this.hostDigestPrefix = _hostDigestPrefix;
+ this.userHandle = _userHandle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, userHandle);
+ this.isRequesterInstantApp = _isRequesterInstantApp;
+ this.token = _token;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, token);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InstantAppRequestInfo> CREATOR
+ = new Parcelable.Creator<InstantAppRequestInfo>() {
+ @Override
+ public InstantAppRequestInfo[] newArray(int size) {
+ return new InstantAppRequestInfo[size];
+ }
+
+ @Override
+ public InstantAppRequestInfo createFromParcel(@NonNull android.os.Parcel in) {
+ return new InstantAppRequestInfo(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1574373347443L,
+ codegenVersion = "1.0.13",
+ sourceFile = "frameworks/base/core/java/android/content/pm/InstantAppRequestInfo.java",
+ inputSignatures = "public final @android.annotation.NonNull android.content.Intent intent\npublic final @android.annotation.Nullable int[] hostDigestPrefix\npublic final @android.annotation.NonNull android.os.UserHandle userHandle\npublic final boolean isRequesterInstantApp\npublic final @android.annotation.NonNull java.lang.String token\nclass InstantAppRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=true, genAidl=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 1451431..67deb82 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -16,7 +16,7 @@
package android.content.pm;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index d2a4030..94a5375 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -25,10 +25,10 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
diff --git a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl b/core/java/android/content/pm/NamedParcelFileDescriptor.aidl
similarity index 95%
rename from core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
rename to core/java/android/content/pm/NamedParcelFileDescriptor.aidl
index 038ced1..68dd5f5 100644
--- a/core/java/android/os/incremental/NamedParcelFileDescriptor.aidl
+++ b/core/java/android/content/pm/NamedParcelFileDescriptor.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.os.incremental;
+package android.content.pm;
import android.os.ParcelFileDescriptor;
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index aa0002d..36fa572 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -17,7 +17,7 @@
package android.content.pm;
import android.annotation.Nullable;
-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/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index f5442ec..6743a3f 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -16,7 +16,7 @@
package android.content.pm;
-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/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f017aad..e4a0bc0 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -26,9 +26,9 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
@@ -50,8 +50,6 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
-import android.os.incremental.IncrementalDataLoaderParams;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
@@ -231,6 +229,15 @@
public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
/**
+ * Streaming installation pending.
+ * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
+ *
+ * @see #EXTRA_SESSION_ID
+ * {@hide}
+ */
+ public static final int STATUS_PENDING_STREAMING = -2;
+
+ /**
* User action is currently required to proceed. You can launch the intent
* activity described by {@link Intent#EXTRA_INTENT} to involve the user and
* continue.
@@ -1059,13 +1066,60 @@
}
}
+
+ /**
+ * Adds a file to session. On commit this file will be pulled from dataLoader.
+ *
+ * @param name arbitrary, unique name of your choosing to identify the
+ * APK being written. You can open a file again for
+ * additional writes (such as after a reboot) by using the
+ * same name. This name is only meaningful within the context
+ * of a single install session.
+ * @param lengthBytes total size of the file being written.
+ * The system may clear various caches as needed to allocate
+ * this space.
+ * @param metadata additional info use by dataLoader to pull data for the file.
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ * @throws IllegalStateException if called for non-callback session
+ *
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
+ * {@hide}
+ */
+ @SystemApi
+ public void addFile(@NonNull String name, long lengthBytes, @NonNull byte[] metadata) {
+ try {
+ mSession.addFile(name, lengthBytes, metadata);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a file.
+ *
+ * @param name name of a file, e.g. split.
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ * @throws IllegalStateException if called for non-callback session
+ * {@hide}
+ */
+ public void removeFile(@NonNull String name) {
+ try {
+ mSession.removeFile(name);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Attempt to commit everything staged in this session. This may require
* user intervention, and so it may not happen immediately. The final
* result of the commit will be reported through the given callback.
* <p>
- * Once this method is called, the session is sealed and no additional
- * mutations may be performed on the session. If the device reboots
+ * Once this method is called, the session is sealed and no additional mutations may be
+ * performed on the session. In case of device reboot or data loader transient failure
* before the session has been finalized, you may commit the session again.
* <p>
* If the installer is the device owner or the affiliated profile owner, there will be no
@@ -1220,27 +1274,6 @@
}
/**
- * Configure files for an installation session.
- *
- * Currently only for Incremental installation session. Once this method is called,
- * the files and their paths, as specified in the parameters, will be created and properly
- * configured in the Incremental File System.
- *
- * TODO(b/136132412): update this and InstallationFile class with latest API design.
- *
- * @throws IllegalStateException if {@link SessionParams#incrementalParams} is null.
- *
- * @hide
- */
- public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
- try {
- mSession.addFile(name, size, metadata);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Release this session object. You can open the session again if it
* hasn't been finalized.
*/
@@ -1428,7 +1461,9 @@
/** {@hide} */
public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
/** {@hide} */
- public IncrementalDataLoaderParams incrementalParams;
+ public DataLoaderParams dataLoaderParams;
+ /** {@hide} */
+ public int rollbackDataPolicy = PackageManager.RollbackDataPolicy.RESTORE;
/**
* Construct parameters for a new package install session.
@@ -1462,12 +1497,12 @@
isMultiPackage = source.readBoolean();
isStaged = source.readBoolean();
requiredInstalledVersionCode = source.readLong();
- IncrementalDataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
- IncrementalDataLoaderParamsParcel.class.getClassLoader());
+ DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
+ DataLoaderParamsParcel.class.getClassLoader());
if (dataLoaderParamsParcel != null) {
- incrementalParams = new IncrementalDataLoaderParams(
- dataLoaderParamsParcel);
+ dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
}
+ rollbackDataPolicy = source.readInt();
}
/** {@hide} */
@@ -1491,7 +1526,8 @@
ret.isMultiPackage = isMultiPackage;
ret.isStaged = isStaged;
ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
- ret.incrementalParams = incrementalParams;
+ ret.dataLoaderParams = dataLoaderParams;
+ ret.rollbackDataPolicy = rollbackDataPolicy;
return ret;
}
@@ -1648,12 +1684,14 @@
}
/**
- * Request that rollbacks be enabled or disabled for the given upgrade.
+ * Request that rollbacks be enabled or disabled for the given upgrade with rollback data
+ * policy set to RESTORE.
*
* <p>If the parent session is staged or has rollback enabled, all children sessions
* must have the same properties.
*
* @param enable set to {@code true} to enable, {@code false} to disable
+ * @see SessionParams#setEnableRollback(boolean, int)
* @hide
*/
@SystemApi @TestApi
@@ -1663,9 +1701,36 @@
} else {
installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
}
+ rollbackDataPolicy = PackageManager.RollbackDataPolicy.RESTORE;
}
/**
+ * Request that rollbacks be enabled or disabled for the given upgrade.
+ *
+ * <p>If the parent session is staged or has rollback enabled, all children sessions
+ * must have the same properties.
+ *
+ * <p> For a multi-package install, this method must be called on each child session to
+ * specify rollback data policies explicitly. Note each child session is allowed to have
+ * different policies.
+ *
+ * @param enable set to {@code true} to enable, {@code false} to disable
+ * @param dataPolicy the rollback data policy for this session
+ * @hide
+ */
+ @SystemApi @TestApi
+ public void setEnableRollback(boolean enable,
+ @PackageManager.RollbackDataPolicy int dataPolicy) {
+ if (enable) {
+ installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
+ } else {
+ installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
+ }
+ rollbackDataPolicy = dataPolicy;
+ }
+
+
+ /**
* @deprecated use {@link #setRequestDowngrade(boolean)}.
* {@hide}
*/
@@ -1770,7 +1835,8 @@
* @param installerPackageName name of the installer package
* {@hide}
*/
- public void setInstallerPackageName(String installerPackageName) {
+ @TestApi
+ public void setInstallerPackageName(@Nullable String installerPackageName) {
this.installerPackageName = installerPackageName;
}
@@ -1821,13 +1887,18 @@
}
/**
- * Set Incremental data loader params.
+ * Set the data loader params for the session.
+ * This also switches installation into data provider mode and disallow direct writes into
+ * staging folder.
*
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
* {@hide}
*/
+ @SystemApi
@RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
- public void setIncrementalParams(@NonNull IncrementalDataLoaderParams incrementalParams) {
- this.incrementalParams = incrementalParams;
+ public void setDataLoaderParams(@NonNull DataLoaderParams dataLoaderParams) {
+ this.dataLoaderParams = dataLoaderParams;
}
/** {@hide} */
@@ -1850,6 +1921,8 @@
pw.printPair("isMultiPackage", isMultiPackage);
pw.printPair("isStaged", isStaged);
pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
+ pw.printPair("dataLoaderParams", dataLoaderParams);
+ pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
pw.println();
}
@@ -1879,11 +1952,12 @@
dest.writeBoolean(isMultiPackage);
dest.writeBoolean(isStaged);
dest.writeLong(requiredInstalledVersionCode);
- if (incrementalParams != null) {
- dest.writeParcelable(incrementalParams.getData(), flags);
+ if (dataLoaderParams != null) {
+ dest.writeParcelable(dataLoaderParams.getData(), flags);
} else {
dest.writeParcelable(null, flags);
}
+ dest.writeInt(rollbackDataPolicy);
}
public static final Parcelable.Creator<SessionParams>
@@ -2021,6 +2095,9 @@
public long updatedMillis;
/** {@hide} */
+ public int rollbackDataPolicy;
+
+ /** {@hide} */
@UnsupportedAppUsage
public SessionInfo() {
}
@@ -2063,6 +2140,7 @@
mStagedSessionErrorCode = source.readInt();
mStagedSessionErrorMessage = source.readString();
isCommitted = source.readBoolean();
+ rollbackDataPolicy = source.readInt();
}
/**
@@ -2380,6 +2458,17 @@
}
/**
+ * Return the data policy associated with the rollback for the given upgrade.
+ *
+ * @hide
+ */
+ @SystemApi @TestApi
+ @PackageManager.RollbackDataPolicy
+ public int getRollbackDataPolicy() {
+ return rollbackDataPolicy;
+ }
+
+ /**
* Returns {@code true} if this session is an active staged session.
*
* We consider a session active if it has been committed and it is either pending
@@ -2541,6 +2630,7 @@
dest.writeInt(mStagedSessionErrorCode);
dest.writeString(mStagedSessionErrorMessage);
dest.writeBoolean(isCommitted);
+ dest.writeInt(rollbackDataPolicy);
}
public static final Parcelable.Creator<SessionInfo>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6e890ba..f792127 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -29,7 +29,6 @@
import android.annotation.StringRes;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.annotation.XmlRes;
import android.app.ActivityManager;
@@ -40,6 +39,7 @@
import android.app.usage.StorageStatsManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -710,6 +710,29 @@
public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
/** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ RollbackDataPolicy.RESTORE,
+ RollbackDataPolicy.WIPE,
+ RollbackDataPolicy.RETAIN
+ })
+ public @interface RollbackDataPolicy {
+ /**
+ * User data will be backed up during install and restored during rollback.
+ */
+ int RESTORE = 0;
+ /**
+ * User data won't be backed up during install but will be wiped out during rollback.
+ */
+ int WIPE = 1;
+ /**
+ * User data won't be backed up during install and won't be restored during rollback.
+ * TODO: Not implemented yet.
+ */
+ int RETAIN = 2;
+ }
+
+ /** @hide */
@IntDef(flag = true, prefix = { "INSTALL_" }, value = {
INSTALL_REPLACE_EXISTING,
INSTALL_ALLOW_TEST,
@@ -946,7 +969,8 @@
INSTALL_REASON_POLICY,
INSTALL_REASON_DEVICE_RESTORE,
INSTALL_REASON_DEVICE_SETUP,
- INSTALL_REASON_USER
+ INSTALL_REASON_USER,
+ INSTALL_REASON_ROLLBACK
})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallReason {}
@@ -977,6 +1001,13 @@
public static final int INSTALL_REASON_USER = 4;
/**
+ * Code indicating that the package installation was a rollback initiated by RollbackManager.
+ *
+ * @hide
+ */
+ public static final int INSTALL_REASON_ROLLBACK = 5;
+
+ /**
* @hide
*/
public static final int INSTALL_UNKNOWN = 0;
@@ -1505,16 +1536,6 @@
public static final int DELETE_DONT_KILL_APP = 0x00000008;
/**
- * Flag parameter for {@link #deletePackage} to indicate that any
- * contributed media should also be deleted during this uninstall. The
- * meaning of "contributed" means it won't automatically be deleted when the
- * app is uninstalled.
- *
- * @hide
- */
- public static final int DELETE_CONTRIBUTED_MEDIA = 0x00000010;
-
- /**
* Flag parameter for {@link #deletePackage} to indicate that package deletion
* should be chatty.
*
@@ -3245,6 +3266,15 @@
public static final int FLAG_PERMISSION_REVOKED_COMPAT = FLAG_PERMISSION_REVOKE_ON_UPGRADE;
/**
+ * Permission flag: The permission is one-time and should be revoked automatically on app
+ * inactivity
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_PERMISSION_ONE_TIME = 1 << 16;
+
+ /**
* Permission flags: Bitwise or of all permission flags allowing an
* exemption for a restricted permission.
* @hide
@@ -3285,7 +3315,8 @@
| FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
| FLAG_PERMISSION_APPLY_RESTRICTION
| FLAG_PERMISSION_GRANTED_BY_ROLE
- | FLAG_PERMISSION_REVOKED_COMPAT;
+ | FLAG_PERMISSION_REVOKED_COMPAT
+ | FLAG_PERMISSION_ONE_TIME;
/**
* Injected activity in app that forwards user to setting activity of that app.
@@ -4065,7 +4096,8 @@
FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT,
FLAG_PERMISSION_APPLY_RESTRICTION,
FLAG_PERMISSION_GRANTED_BY_ROLE,
- FLAG_PERMISSION_REVOKED_COMPAT
+ FLAG_PERMISSION_REVOKED_COMPAT,
+ FLAG_PERMISSION_ONE_TIME
})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionFlags {}
@@ -7166,6 +7198,7 @@
case FLAG_PERMISSION_APPLY_RESTRICTION: return "APPLY_RESTRICTION";
case FLAG_PERMISSION_GRANTED_BY_ROLE: return "GRANTED_BY_ROLE";
case FLAG_PERMISSION_REVOKED_COMPAT: return "REVOKED_COMPAT";
+ case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME";
default: return Integer.toString(flag);
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 65ee1e5..47edf2e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -47,11 +47,11 @@
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.apex.ApexInfo;
import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.ResourcesManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index b0fecfa..7c12527 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -16,8 +16,8 @@
package android.content.pm;
-import android.annotation.UnsupportedAppUsage;
import android.app.usage.StorageStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 55574c3..f0f6753 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -27,7 +27,7 @@
import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.parsing.ComponentParseUtils;
import android.os.BaseBundle;
import android.os.Debug;
diff --git a/core/java/android/content/pm/ParceledListSlice.aidl b/core/java/android/content/pm/ParceledListSlice.aidl
index c02cc6a..5031fba 100644
--- a/core/java/android/content/pm/ParceledListSlice.aidl
+++ b/core/java/android/content/pm/ParceledListSlice.aidl
@@ -16,4 +16,4 @@
package android.content.pm;
-parcelable ParceledListSlice;
+parcelable ParceledListSlice<T>;
diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java
index dccc524..73119e0 100644
--- a/core/java/android/content/pm/ParceledListSlice.java
+++ b/core/java/android/content/pm/ParceledListSlice.java
@@ -16,7 +16,7 @@
package android.content.pm;
-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/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index c77c53f..a0f089b 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -22,7 +22,7 @@
import android.annotation.StringRes;
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;
@@ -248,6 +248,17 @@
@TestApi
public static final int PROTECTION_FLAG_TELEPHONY = 0x400000;
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>companion</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int PROTECTION_FLAG_COMPANION = 0x800000;
+
/** @hide */
@IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
PROTECTION_FLAG_PRIVILEGED,
@@ -270,6 +281,7 @@
PROTECTION_FLAG_INCIDENT_REPORT_APPROVER,
PROTECTION_FLAG_APP_PREDICTOR,
PROTECTION_FLAG_TELEPHONY,
+ PROTECTION_FLAG_COMPANION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 23fbefb..bd909c7 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -17,7 +17,7 @@
package android.content.pm;
import android.Manifest;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -43,11 +43,11 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
+import libcore.io.IoUtils;
+
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
-import libcore.io.IoUtils;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 4d3e1f7..55846ad 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -17,7 +17,7 @@
package android.content.pm;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 5a80079..2c4ccbf 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -20,11 +20,11 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.app.Notification;
import android.app.Person;
import android.app.TaskStackBuilder;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index f851799..dde8865 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -22,9 +22,9 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.app.usage.UsageStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 25a4dca..3b84ae7 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -16,7 +16,7 @@
package android.content.pm;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index fff9cf3..008cfa5 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,8 +18,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
diff --git a/core/java/android/content/pm/VerifierInfo.java b/core/java/android/content/pm/VerifierInfo.java
index 224ca62..5f88555 100644
--- a/core/java/android/content/pm/VerifierInfo.java
+++ b/core/java/android/content/pm/VerifierInfo.java
@@ -16,7 +16,7 @@
package android.content.pm;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/content/pm/XmlSerializerAndParser.java b/core/java/android/content/pm/XmlSerializerAndParser.java
index 6d24800..5dce839 100644
--- a/core/java/android/content/pm/XmlSerializerAndParser.java
+++ b/core/java/android/content/pm/XmlSerializerAndParser.java
@@ -16,11 +16,12 @@
package android.content.pm;
-import org.xmlpull.v1.XmlSerializer;
+import android.compat.annotation.UnsupportedAppUsage;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
-import android.annotation.UnsupportedAppUsage;
import java.io.IOException;
/** @hide */
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index ffddbb6..7732316 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -2338,7 +2338,7 @@
parsingPackage.setProfileableByShell(true);
}
XmlUtils.skipCurrentTag(parser);
-
+ break;
default:
if (!PackageParser.RIGID_PARSER) {
Slog.w(TAG, "Unknown element under <application>: " + tagName
diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java
index 5364313..fc210b2 100644
--- a/core/java/android/content/pm/parsing/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java
@@ -768,6 +768,10 @@
protected PatternMatcher[] uriPermissionPatterns;
protected PathPermission[] pathPermissions;
+ public ParsedProvider(ParsedProvider other) {
+ this.setFrom(other);
+ }
+
protected void setFrom(ParsedProvider other) {
super.setFrom(other);
this.exported = other.exported;
@@ -823,6 +827,10 @@
return authority;
}
+ public void setSyncable(boolean isSyncable) {
+ this.isSyncable = isSyncable;
+ }
+
public boolean isSyncable() {
return isSyncable;
}
diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java
index 28f0bc4..f2cf9a4 100644
--- a/core/java/android/content/pm/parsing/PackageInfoUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java
@@ -436,7 +436,7 @@
ii.functionalTest = i.functionalTest;
ii.sourceDir = pkg.getBaseCodePath();
- ii.publicSourceDir = pkg.getCodePath();
+ ii.publicSourceDir = pkg.getBaseCodePath();
ii.splitNames = pkg.getSplitNames();
ii.splitSourceDirs = pkg.getSplitCodePaths();
ii.splitPublicSourceDirs = pkg.getSplitCodePaths();
@@ -485,6 +485,7 @@
pg.backgroundRequestDetailResourceId
);
assignSharedFieldsForPackageItemInfo(pgi, pg);
+ pgi.descriptionRes = pg.descriptionRes;
pgi.priority = pg.priority;
pgi.requestRes = pg.requestRes;
pgi.flags = pg.flags;
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index ad37555..0e4a8e6 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.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.om.OverlayableInfo;
import android.content.res.loader.ResourcesProvider;
import android.text.TextUtils;
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 2ba5579..e93ec00 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,7 +16,7 @@
package android.content.res;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 168eae6..d20dca1 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -24,7 +24,7 @@
import android.annotation.StringRes;
import android.annotation.StyleRes;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
import android.content.res.loader.ResourceLoader;
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index b5b097b..f23c802 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -19,9 +19,18 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources.Theme;
import android.graphics.Color;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.SparseArray;
+import android.util.StateSet;
+import android.util.Xml;
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
@@ -30,16 +39,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.annotation.UnsupportedAppUsage;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.MathUtils;
-import android.util.SparseArray;
-import android.util.StateSet;
-import android.util.Xml;
-import android.os.Parcel;
-import android.os.Parcelable;
-
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Arrays;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index f3b7553..c66c70a2 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -16,7 +16,7 @@
package android.content.res;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.graphics.Canvas;
import android.graphics.PointF;
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 090629f..8c358cc 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -45,8 +45,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.WindowConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.LocaleProto;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -452,6 +452,9 @@
if ((diff & ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
list.add("CONFIG_SMALLEST_SCREEN_SIZE");
}
+ if ((diff & ActivityInfo.CONFIG_DENSITY) != 0) {
+ list.add("CONFIG_DENSITY");
+ }
if ((diff & ActivityInfo.CONFIG_LAYOUT_DIRECTION) != 0) {
list.add("CONFIG_LAYOUT_DIRECTION");
}
@@ -461,6 +464,9 @@
if ((diff & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
list.add("CONFIG_ASSETS_PATHS");
}
+ if ((diff & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) {
+ list.add("CONFIG_WINDOW_CONFIGURATION");
+ }
StringBuilder builder = new StringBuilder("{");
for (int i = 0, n = list.size(); i < n; i++) {
builder.append(list.get(i));
diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java
index 848790f..5e10a57 100644
--- a/core/java/android/content/res/ConfigurationBoundResourceCache.java
+++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java
@@ -16,7 +16,7 @@
package android.content.res;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
/**
diff --git a/core/java/android/content/res/DrawableCache.java b/core/java/android/content/res/DrawableCache.java
index 90604b8..5497e61 100644
--- a/core/java/android/content/res/DrawableCache.java
+++ b/core/java/android/content/res/DrawableCache.java
@@ -16,7 +16,7 @@
package android.content.res;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Drawable;
/**
diff --git a/core/java/android/content/res/ObbInfo.java b/core/java/android/content/res/ObbInfo.java
index 8af27ca..c477abc 100644
--- a/core/java/android/content/res/ObbInfo.java
+++ b/core/java/android/content/res/ObbInfo.java
@@ -16,7 +16,7 @@
package android.content.res;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 2698c2d..4725e0a 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -40,9 +40,9 @@
import android.annotation.StringRes;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
import android.app.ResourcesManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.content.res.loader.ResourceLoader;
@@ -1859,6 +1859,7 @@
mResId = other.mResId == null ? null : other.mResId.clone();
mForce = other.mForce == null ? null : other.mForce.clone();
mCount = other.mCount;
+ mHashCode = other.mHashCode;
}
@Override
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 84489cf..acdd47a 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,7 +27,7 @@
import android.annotation.RawRes;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.content.res.AssetManager.AssetInputStream;
@@ -344,7 +344,7 @@
try {
return mAssets.openNonAssetFd(tempValue.assetCookie, tempValue.string.toString());
} catch (Exception e) {
- throw new NotFoundException("File " + tempValue.string.toString() + " from drawable "
+ throw new NotFoundException("File " + tempValue.string.toString() + " from "
+ "resource ID #0x" + Integer.toHexString(id), e);
}
}
@@ -359,7 +359,7 @@
// Note: value.string might be null
NotFoundException rnf = new NotFoundException("File "
+ (value.string == null ? "(null)" : value.string.toString())
- + " from drawable resource ID #0x" + Integer.toHexString(id));
+ + " from resource ID #0x" + Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index 1db2dd8..a29fea0 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.TextUtils;
import java.util.Arrays;
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index d43bd36..8f3f77b 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -16,7 +16,7 @@
package android.content.res;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index 968ab40..3270944 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.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.pm.ActivityInfo.Config;
import android.content.res.Resources.Theme;
import android.content.res.Resources.ThemeKey;
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 38df3175..29c5c93 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -20,7 +20,7 @@
import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.annotation.StyleableRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.graphics.Typeface;
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 626bf77..cb93cbf 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -20,7 +20,7 @@
import android.annotation.AnyRes;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 8c2a65f..cda0e98 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -44,9 +44,10 @@
// Used by the staging manager to notify the RollbackManager that a session is
// being staged. In the case of multi-package sessions, the specified sessionId
// is that of the parent session.
+ // Returns the rollback id if rollback was enabled successfully, or -1 if not.
//
// NOTE: This call is synchronous.
- boolean notifyStagedSession(int sessionId);
+ int notifyStagedSession(int sessionId);
// Used by the staging manager to notify the RollbackManager of the apk
// session for a staged session.
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 48d8867..f7137705 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -782,58 +782,42 @@
private void enforceStrictGrammar(@Nullable String selection, @Nullable String groupBy,
@Nullable String having, @Nullable String sortOrder, @Nullable String limit) {
SQLiteTokenizer.tokenize(selection, SQLiteTokenizer.OPTION_NONE,
- this::enforceStrictGrammarWhereHaving);
+ this::enforceStrictToken);
SQLiteTokenizer.tokenize(groupBy, SQLiteTokenizer.OPTION_NONE,
- this::enforceStrictGrammarGroupBy);
+ this::enforceStrictToken);
SQLiteTokenizer.tokenize(having, SQLiteTokenizer.OPTION_NONE,
- this::enforceStrictGrammarWhereHaving);
+ this::enforceStrictToken);
SQLiteTokenizer.tokenize(sortOrder, SQLiteTokenizer.OPTION_NONE,
- this::enforceStrictGrammarOrderBy);
+ this::enforceStrictToken);
SQLiteTokenizer.tokenize(limit, SQLiteTokenizer.OPTION_NONE,
- this::enforceStrictGrammarLimit);
+ this::enforceStrictToken);
}
- private void enforceStrictGrammarWhereHaving(@NonNull String token) {
+ private void enforceStrictToken(@NonNull String token) {
if (isTableOrColumn(token)) return;
if (SQLiteTokenizer.isFunction(token)) return;
if (SQLiteTokenizer.isType(token)) return;
- // NOTE: we explicitly don't allow SELECT subqueries, since they could
- // leak data that should have been filtered by the trusted where clause
+ // Carefully block any tokens that are attempting to jump across query
+ // clauses or create subqueries, since they could leak data that should
+ // have been filtered by the trusted where clause
+ boolean isAllowedKeyword = SQLiteTokenizer.isKeyword(token);
switch (token.toUpperCase(Locale.US)) {
- case "AND": case "AS": case "BETWEEN": case "BINARY":
- case "CASE": case "CAST": case "COLLATE": case "DISTINCT":
- case "ELSE": case "END": case "ESCAPE": case "EXISTS":
- case "GLOB": case "IN": case "IS": case "ISNULL":
- case "LIKE": case "MATCH": case "NOCASE": case "NOT":
- case "NOTNULL": case "NULL": case "OR": case "REGEXP":
- case "RTRIM": case "THEN": case "WHEN":
- return;
+ case "SELECT":
+ case "FROM":
+ case "WHERE":
+ case "GROUP":
+ case "HAVING":
+ case "WINDOW":
+ case "VALUES":
+ case "ORDER":
+ case "LIMIT":
+ isAllowedKeyword = false;
+ break;
}
- throw new IllegalArgumentException("Invalid token " + token);
- }
-
- private void enforceStrictGrammarGroupBy(@NonNull String token) {
- if (isTableOrColumn(token)) return;
- throw new IllegalArgumentException("Invalid token " + token);
- }
-
- private void enforceStrictGrammarOrderBy(@NonNull String token) {
- if (isTableOrColumn(token)) return;
- switch (token.toUpperCase(Locale.US)) {
- case "COLLATE": case "ASC": case "DESC":
- case "BINARY": case "RTRIM": case "NOCASE":
- return;
+ if (!isAllowedKeyword) {
+ throw new IllegalArgumentException("Invalid token " + token);
}
- throw new IllegalArgumentException("Invalid token " + token);
- }
-
- private void enforceStrictGrammarLimit(@NonNull String token) {
- switch (token.toUpperCase(Locale.US)) {
- case "OFFSET":
- return;
- }
- throw new IllegalArgumentException("Invalid token " + token);
}
/**
diff --git a/core/java/android/hardware/biometrics/Authenticator.java b/core/java/android/hardware/biometrics/Authenticator.java
deleted file mode 100644
index 6d7e748..0000000
--- a/core/java/android/hardware/biometrics/Authenticator.java
+++ /dev/null
@@ -1,35 +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 android.hardware.biometrics;
-
-/**
- * Type of authenticators defined on a granularity that the BiometricManager / BiometricPrompt
- * supports.
- * @hide
- */
-public class Authenticator {
-
- /**
- * Device credential, e.g. Pin/Pattern/Password.
- */
- public static final int TYPE_CREDENTIAL = 1 << 0;
- /**
- * Encompasses all biometrics on the device, e.g. Fingerprint/Iris/Face.
- */
- public static final int TYPE_BIOMETRIC = 1 << 1;
-
-}
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 191516b..d28b7c5 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -16,8 +16,9 @@
package android.hardware.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import android.annotation.UnsupportedAppUsage;
-import android.app.KeyguardManager;
/**
@@ -126,8 +127,8 @@
/**
* The device does not have pin, pattern, or password set up. See
- * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and
- * {@link KeyguardManager#isDeviceSecure()}
+ * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)},
+ * {@link Authenticators#DEVICE_CREDENTIAL}, and {@link BiometricManager#canAuthenticate(int)}.
*/
int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index f17b3ae..7e1506f 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -21,6 +21,7 @@
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -65,6 +66,77 @@
BIOMETRIC_ERROR_NO_HARDWARE})
@interface BiometricError {}
+ /**
+ * Types of authenticators, defined at a level of granularity supported by
+ * {@link BiometricManager} and {@link BiometricPrompt}.
+ *
+ * <p>Types may combined via bitwise OR into a single integer representing multiple
+ * authenticators (e.g. <code>DEVICE_CREDENTIAL | BIOMETRIC_WEAK</code>).
+ */
+ public interface Authenticators {
+ /**
+ * An {@link IntDef} representing valid combinations of authenticator types.
+ * @hide
+ */
+ @IntDef(flag = true, value = {
+ BIOMETRIC_STRONG,
+ BIOMETRIC_WEAK,
+ DEVICE_CREDENTIAL,
+ })
+ @interface Types {}
+
+ /**
+ * Empty set with no authenticators specified.
+ * @hide
+ */
+ @SystemApi
+ int EMPTY_SET = 0x0;
+
+ /**
+ * Placeholder for the theoretical strongest biometric security tier.
+ * @hide
+ */
+ int BIOMETRIC_MAX_STRENGTH = 0x001;
+
+ /**
+ * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+ * requirements for <strong>Strong</strong>, as defined by the Android CDD.
+ */
+ int BIOMETRIC_STRONG = 0x00F;
+
+ /**
+ * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+ * requirements for <strong>Weak</strong>, as defined by the Android CDD.
+ *
+ * <p>Note that this is a superset of {@link #BIOMETRIC_STRONG} and is defined such that
+ * <code>BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK</code>.
+ */
+ int BIOMETRIC_WEAK = 0x0FF;
+
+ /**
+ * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+ * requirements for <strong>Convenience</strong>, as defined by the Android CDD. This
+ * is not a valid parameter to any of the {@link android.hardware.biometrics} APIs, since
+ * the CDD allows only {@link #BIOMETRIC_WEAK} and stronger authenticators to participate.
+ * @hide
+ */
+ @SystemApi
+ int BIOMETRIC_CONVENIENCE = 0xFFF;
+
+ /**
+ * Placeholder for the theoretical weakest biometric security tier.
+ * @hide
+ */
+ int BIOMETRIC_MIN_STRENGTH = 0x7FFF;
+
+ /**
+ * The non-biometric credential used to secure the device (i.e., PIN, pattern, or password).
+ * This should typically only be used in combination with a biometric auth type, such as
+ * {@link #BIOMETRIC_WEAK}.
+ */
+ int DEVICE_CREDENTIAL = 1 << 15;
+ }
+
private final Context mContext;
private final IAuthService mService;
private final boolean mHasHardware;
@@ -94,27 +166,64 @@
}
/**
- * Determine if biometrics can be used. In other words, determine if {@link BiometricPrompt}
- * can be expected to be shown (hardware available, templates enrolled, user-enabled).
+ * Determine if biometrics can be used. In other words, determine if
+ * {@link BiometricPrompt} can be expected to be shown (hardware available, templates enrolled,
+ * user-enabled). This is the equivalent of {@link #canAuthenticate(int)} with
+ * {@link Authenticators#BIOMETRIC_WEAK}
*
- * @return Returns {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any
- * enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are currently
- * supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if a biometric can currently be
- * used (enrolled and available).
+ * @return {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any strong
+ * biometrics enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are currently
+ * supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if a strong biometric can currently
+ * be used (enrolled and available).
+ *
+ * @deprecated See {@link #canAuthenticate(int)}.
*/
+ @Deprecated
@RequiresPermission(USE_BIOMETRIC)
public @BiometricError int canAuthenticate() {
- return canAuthenticate(mContext.getUserId());
+ return canAuthenticate(Authenticators.BIOMETRIC_WEAK);
+ }
+
+ /**
+ * Determine if any of the provided authenticators can be used. In other words, determine if
+ * {@link BiometricPrompt} can be expected to be shown (hardware available, templates enrolled,
+ * user-enabled).
+ *
+ * For biometric authenticators, determine if the device can currently authenticate with at
+ * least the requested strength. For example, invoking this API with
+ * {@link Authenticators#BIOMETRIC_WEAK} on a device that currently only has
+ * {@link Authenticators#BIOMETRIC_STRONG} enrolled will return {@link #BIOMETRIC_SUCCESS}.
+ *
+ * Invoking this API with {@link Authenticators#DEVICE_CREDENTIAL} can be used to determine
+ * if the user has a PIN/Pattern/Password set up.
+ *
+ * @param authenticators bit field consisting of constants defined in {@link Authenticators}.
+ * If multiple authenticators are queried, a logical OR will be applied.
+ * For example, if {@link Authenticators#DEVICE_CREDENTIAL} |
+ * {@link Authenticators#BIOMETRIC_STRONG} is queried and only
+ * {@link Authenticators#DEVICE_CREDENTIAL} is set up, this API will
+ * return {@link #BIOMETRIC_SUCCESS}
+ *
+ * @return {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any of the
+ * requested authenticators enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are
+ * currently supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if one of the requested
+ * authenticators can currently be used (enrolled and available).
+ */
+ @RequiresPermission(USE_BIOMETRIC)
+ public @BiometricError int canAuthenticate(@Authenticators.Types int authenticators) {
+ return canAuthenticate(mContext.getUserId(), authenticators);
}
/**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public @BiometricError int canAuthenticate(int userId) {
+ public @BiometricError int canAuthenticate(int userId,
+ @Authenticators.Types int authenticators) {
if (mService != null) {
try {
- return mService.canAuthenticate(mContext.getOpPackageName(), userId);
+ final String opPackageName = mContext.getOpPackageName();
+ return mService.canAuthenticate(opPackageName, userId, authenticators);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 9c51b52..cb8fc8b 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -86,6 +87,11 @@
* @hide
*/
public static final String KEY_AUTHENTICATORS_ALLOWED = "authenticators_allowed";
+ /**
+ * If this is set, check the Device Policy Manager for allowed biometrics.
+ * @hide
+ */
+ public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm";
/**
* Error/help message will show for this amount of time.
@@ -149,7 +155,7 @@
/**
* A builder that collects arguments to be shown on the system-provided biometric dialog.
- **/
+ */
public static class Builder {
private final Bundle mBundle;
private ButtonInfo mPositiveButtonInfo;
@@ -157,8 +163,8 @@
private Context mContext;
/**
- * Creates a builder for a biometric dialog.
- * @param context
+ * Creates a builder for a {@link BiometricPrompt} dialog.
+ * @param context The {@link Context} that will be used to build the prompt.
*/
public Builder(Context context) {
mBundle = new Bundle();
@@ -166,58 +172,67 @@
}
/**
- * Required: Set the title to display.
- * @param title
- * @return
+ * Required: Sets the title that will be shown on the prompt.
+ * @param title The title to display.
+ * @return This builder.
*/
- @NonNull public Builder setTitle(@NonNull CharSequence title) {
+ @NonNull
+ public Builder setTitle(@NonNull CharSequence title) {
mBundle.putCharSequence(KEY_TITLE, title);
return this;
}
/**
- * For internal use currently. Only takes effect if title is null/empty. Shows a default
- * modality-specific title.
+ * Shows a default, modality-specific title for the prompt if the title would otherwise be
+ * null or empty. Currently for internal use only.
+ * @return This builder.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- @NonNull public Builder setUseDefaultTitle() {
+ @NonNull
+ public Builder setUseDefaultTitle() {
mBundle.putBoolean(KEY_USE_DEFAULT_TITLE, true);
return this;
}
/**
- * Optional: Set the subtitle to display.
- * @param subtitle
- * @return
+ * Optional: Sets a subtitle that will be shown on the prompt.
+ * @param subtitle The subtitle to display.
+ * @return This builder.
*/
- @NonNull public Builder setSubtitle(@NonNull CharSequence subtitle) {
+ @NonNull
+ public Builder setSubtitle(@NonNull CharSequence subtitle) {
mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
return this;
}
/**
- * Optional: Set the description to display.
- * @param description
- * @return
+ * Optional: Sets a description that will be shown on the prompt.
+ * @param description The description to display.
+ * @return This builder.
*/
- @NonNull public Builder setDescription(@NonNull CharSequence description) {
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
mBundle.putCharSequence(KEY_DESCRIPTION, description);
return this;
}
/**
- * Required: Set the text for the negative button. This would typically be used as a
- * "Cancel" button, but may be also used to show an alternative method for authentication,
- * such as screen that asks for a backup password.
+ * Required: Sets the text, executor, and click listener for the negative button on the
+ * prompt. This is typically a cancel button, but may be also used to show an alternative
+ * method for authentication, such as a screen that asks for a backup password.
*
- * Note that this should not be set if {@link #setDeviceCredentialAllowed(boolean)}(boolean)
- * is set to true.
+ * <p>Note that this setting is not required, and in fact is explicitly disallowed, if
+ * device credential authentication is enabled via {@link #setAllowedAuthenticators(int)} or
+ * {@link #setDeviceCredentialAllowed(boolean)}.
*
- * @param text
- * @return
+ * @param text Text to be shown on the negative button for the prompt.
+ * @param executor Executor that will be used to run the on click callback.
+ * @param listener Listener containing a callback to be run when the button is pressed.
+ * @return This builder.
*/
- @NonNull public Builder setNegativeButton(@NonNull CharSequence text,
+ @NonNull
+ public Builder setNegativeButton(@NonNull CharSequence text,
@NonNull @CallbackExecutor Executor executor,
@NonNull DialogInterface.OnClickListener listener) {
if (TextUtils.isEmpty(text)) {
@@ -235,70 +250,126 @@
}
/**
- * Optional: A hint to the system to require user confirmation after a biometric has been
- * authenticated. For example, implicit modalities like Face and Iris authentication are
- * passive, meaning they don't require an explicit user action to complete. When set to
- * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt
- * will require confirmation by default.
+ * Optional: Sets a hint to the system for whether to require user confirmation after
+ * authentication. For example, implicit modalities like face and iris are passive, meaning
+ * they don't require an explicit user action to complete authentication. If set to true,
+ * these modalities should require the user to take some action (e.g. press a button)
+ * before {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is
+ * called. Defaults to true.
*
- * A typical use case for not requiring confirmation would be for low-risk transactions,
+ * <p>A typical use case for not requiring confirmation would be for low-risk transactions,
* such as re-authenticating a recently authenticated application. A typical use case for
* requiring confirmation would be for authorizing a purchase.
*
- * Note that this is a hint to the system. The system may choose to ignore the flag. For
- * example, if the user disables implicit authentication in Settings, or if it does not
- * apply to a modality (e.g. Fingerprint). When ignored, the system will default to
- * requiring confirmation.
+ * <p>Note that this just passes a hint to the system, which the system may then ignore. For
+ * example, a value of false may be ignored if the user has disabled implicit authentication
+ * in Settings, or if it does not apply to a particular modality (e.g. fingerprint).
*
- * @param requireConfirmation
+ * @param requireConfirmation true if explicit user confirmation should be required, or
+ * false otherwise.
+ * @return This builder.
*/
- @NonNull public Builder setConfirmationRequired(boolean requireConfirmation) {
+ @NonNull
+ public Builder setConfirmationRequired(boolean requireConfirmation) {
mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
return this;
}
/**
- * The user will first be prompted to authenticate with biometrics, but also given the
- * option to authenticate with their device PIN, pattern, or password. Developers should
- * first check {@link KeyguardManager#isDeviceSecure()} before enabling this. If the device
- * is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL} will be
- * returned in {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}}.
+ * Optional: If enabled, the user will first be prompted to authenticate with biometrics,
+ * but also given the option to authenticate with their device PIN, pattern, or password.
+ * Developers should first check {@link KeyguardManager#isDeviceSecure()} before enabling.
+ * If the device is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL}
+ * will be given to {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
* Defaults to false.
*
- * Note that {@link #setNegativeButton(CharSequence, Executor,
- * DialogInterface.OnClickListener)} should not be set if this is set to true.
+ * <p>Note that enabling this option replaces the negative button on the prompt with one
+ * that allows the user to authenticate with their device credential, making it an error to
+ * call {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
*
- * @param allowed When true, the prompt will fall back to ask for the user's device
- * credentials (PIN, pattern, or password).
- * @return
+ * @param allowed true if the prompt should fall back to asking for the user's device
+ * credential (PIN/pattern/password), or false otherwise.
+ * @return This builder.
+ *
+ * @deprecated Replaced by {@link #setAllowedAuthenticators(int)}.
*/
- @NonNull public Builder setDeviceCredentialAllowed(boolean allowed) {
+ @Deprecated
+ @NonNull
+ public Builder setDeviceCredentialAllowed(boolean allowed) {
mBundle.putBoolean(KEY_ALLOW_DEVICE_CREDENTIAL, allowed);
return this;
}
/**
- * Creates a {@link BiometricPrompt}.
- * @return a {@link BiometricPrompt}
- * @throws IllegalArgumentException if any of the required fields are not set.
+ * Optional: Specifies the type(s) of authenticators that may be invoked by
+ * {@link BiometricPrompt} to authenticate the user. Available authenticator types are
+ * defined in {@link Authenticators} and can be combined via bitwise OR. Defaults to:
+ * <ul>
+ * <li>{@link Authenticators#BIOMETRIC_WEAK} for non-crypto authentication, or</li>
+ * <li>{@link Authenticators#BIOMETRIC_STRONG} for crypto-based authentication.</li>
+ * </ul>
+ *
+ * <p>If this method is used and no authenticator of any of the specified types is available
+ * at the time <code>BiometricPrompt#authenticate(...)</code> is called, authentication will
+ * be canceled and {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}
+ * will be invoked with an appropriate error code.
+ *
+ * <p>This method should be preferred over {@link #setDeviceCredentialAllowed(boolean)} and
+ * overrides the latter if both are used. Using this method to enable device credential
+ * authentication (with {@link Authenticators#DEVICE_CREDENTIAL}) will replace the negative
+ * button on the prompt, making it an error to also call
+ * {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
+ *
+ * @param authenticators A bit field representing all valid authenticator types that may be
+ * invoked by the prompt.
+ * @return This builder.
*/
- @NonNull public BiometricPrompt build() {
+ @NonNull
+ public Builder setAllowedAuthenticators(@Authenticators.Types int authenticators) {
+ mBundle.putInt(KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ return this;
+ }
+
+ /**
+ * If set check the Device Policy Manager for disabled biometrics.
+ *
+ * @param checkDevicePolicyManager
+ * @return This builder.
+ * @hide
+ */
+ @NonNull
+ public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
+ mBundle.putBoolean(EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS,
+ checkDevicePolicyManager);
+ return this;
+ }
+
+ /**
+ * Creates a {@link BiometricPrompt}.
+ *
+ * @return An instance of {@link BiometricPrompt}.
+ *
+ * @throws IllegalArgumentException If any required fields are unset, or if given any
+ * invalid combination of field values.
+ */
+ @NonNull
+ public BiometricPrompt build() {
final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
- final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
- final boolean allowCredential = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
- final Object authenticatorsAllowed = mBundle.get(KEY_AUTHENTICATORS_ALLOWED);
+ final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE, false);
+ final boolean deviceCredentialAllowed = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
+ final @Authenticators.Types int authenticators =
+ mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 0);
+ final boolean willShowDeviceCredentialButton = deviceCredentialAllowed
+ || (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
if (TextUtils.isEmpty(title) && !useDefaultTitle) {
throw new IllegalArgumentException("Title must be set and non-empty");
- } else if (TextUtils.isEmpty(negative) && !allowCredential) {
+ } else if (TextUtils.isEmpty(negative) && !willShowDeviceCredentialButton) {
throw new IllegalArgumentException("Negative text must be set and non-empty");
- } else if (!TextUtils.isEmpty(negative) && allowCredential) {
+ } else if (!TextUtils.isEmpty(negative) && willShowDeviceCredentialButton) {
throw new IllegalArgumentException("Can't have both negative button behavior"
+ " and device credential enabled");
- } else if (authenticatorsAllowed != null && allowCredential) {
- throw new IllegalArgumentException("setAuthenticatorsAllowed and"
- + " setDeviceCredentialAllowed should not be used simultaneously");
}
return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
}
@@ -394,6 +465,75 @@
}
/**
+ * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
+ * @return The title of the prompt, which is guaranteed to be non-null.
+ */
+ @NonNull
+ public CharSequence getTitle() {
+ return mBundle.getCharSequence(KEY_TITLE, "");
+ }
+
+ /**
+ * Whether to use a default modality-specific title. For internal use only.
+ * @return See {@link Builder#setUseDefaultTitle()}.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public boolean shouldUseDefaultTitle() {
+ return mBundle.getBoolean(KEY_USE_DEFAULT_TITLE, false);
+ }
+
+ /**
+ * Gets the subtitle for the prompt, as set by {@link Builder#setSubtitle(CharSequence)}.
+ * @return The subtitle for the prompt, or null if the prompt has no subtitle.
+ */
+ @Nullable
+ public CharSequence getSubtitle() {
+ return mBundle.getCharSequence(KEY_SUBTITLE);
+ }
+
+ /**
+ * Gets the description for the prompt, as set by {@link Builder#setDescription(CharSequence)}.
+ * @return The description for the prompt, or null if the prompt has no description.
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mBundle.getCharSequence(KEY_DESCRIPTION);
+ }
+
+ /**
+ * Gets the negative button text for the prompt, as set by
+ * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
+ * @return The negative button text for the prompt, or null if no negative button text was set.
+ */
+ @Nullable
+ public CharSequence getNegativeButtonText() {
+ return mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
+ }
+
+ /**
+ * Determines if explicit user confirmation is required by the prompt, as set by
+ * {@link Builder#setConfirmationRequired(boolean)}.
+ *
+ * @return true if explicit user confirmation is required, or false otherwise.
+ */
+ public boolean isConfirmationRequired() {
+ return mBundle.getBoolean(KEY_REQUIRE_CONFIRMATION, true);
+ }
+
+ /**
+ * Gets the type(s) of authenticators that may be invoked by the prompt to authenticate the
+ * user, as set by {@link Builder#setAllowedAuthenticators(int)}.
+ *
+ * @return A bit field representing the type(s) of authenticators that may be invoked by the
+ * prompt (as defined by {@link Authenticators}), or 0 if this field was not set.
+ */
+ @Nullable
+ public int getAllowedAuthenticators() {
+ return mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 0);
+ }
+
+ /**
* A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework
* supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
*/
@@ -509,10 +649,12 @@
/**
* Authenticates for the given user.
+ *
* @param cancel An object that can be used to cancel authentication
* @param executor An executor to handle callback events
* @param callback An object to receive authentication events
* @param userId The user to authenticate
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -542,23 +684,33 @@
* authentication errors through {@link AuthenticationCallback}, and button events through the
* corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor,
* DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object,
- * and calling {@link BiometricPrompt#authenticate( CancellationSignal, Executor,
+ * and calling {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
* AuthenticationCallback)} while an existing authentication attempt is occurring will stop the
* previous client and start a new authentication. The interrupted client will receive a
* cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int,
* CharSequence)}.
*
- * Note: Applications generally should not cancel and start authentication in quick succession.
- * For example, to properly handle authentication across configuration changes, it's recommended
- * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the
- * application will not need to cancel/restart authentication during the configuration change.
+ * <p>Note: Applications generally should not cancel and start authentication in quick
+ * succession. For example, to properly handle authentication across configuration changes, it's
+ * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so,
+ * the application will not need to cancel/restart authentication during the configuration
+ * change.
*
- * @throws IllegalArgumentException If any of the arguments are null
+ * <p>Per the Android CDD, only biometric authenticators that meet or exceed the requirements
+ * for <strong>Strong</strong> are permitted to integrate with Keystore to perform related
+ * cryptographic operations. Therefore, it is an error to call this method after explicitly
+ * calling {@link Builder#setAllowedAuthenticators(int)} with any value other than
+ * {@link Authenticators#BIOMETRIC_STRONG}.
*
- * @param crypto Object associated with the call
- * @param cancel An object that can be used to cancel authentication
- * @param executor An executor to handle callback events
- * @param callback An object to receive authentication events
+ * @throws IllegalArgumentException If any of the arguments are null, if
+ * {@link Builder#setDeviceCredentialAllowed(boolean)} was explicitly set to true, or if
+ * {@link Builder#setAllowedAuthenticators(int)} was explicitly called with any value other than
+ * {@link Authenticators#BIOMETRIC_STRONG}.
+ *
+ * @param crypto A cryptographic operation to be unlocked after successful authentication.
+ * @param cancel An object that can be used to cancel authentication.
+ * @param executor An executor to handle callback events.
+ * @param callback An object to receive authentication events.
*/
@RequiresPermission(USE_BIOMETRIC)
public void authenticate(@NonNull CryptoObject crypto,
@@ -577,9 +729,20 @@
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
- if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) {
+
+ @Authenticators.Types int authenticators = mBundle.getInt(
+ KEY_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_STRONG);
+
+ if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)
+ || (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0) {
throw new IllegalArgumentException("Device credential not supported with crypto");
}
+
+ // Disallow any non-Strong biometric authenticator types.
+ if ((authenticators & ~Authenticators.BIOMETRIC_STRONG) != 0) {
+ throw new IllegalArgumentException("Only Strong biometrics supported with crypto");
+ }
+
authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
}
@@ -598,16 +761,17 @@
* authentication. The interrupted client will receive a cancelled notification through {@link
* AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
*
- * Note: Applications generally should not cancel and start authentication in quick succession.
- * For example, to properly handle authentication across configuration changes, it's recommended
- * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the
- * application will not need to cancel/restart authentication during the configuration change.
+ * <p>Note: Applications generally should not cancel and start authentication in quick
+ * succession. For example, to properly handle authentication across configuration changes, it's
+ * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so,
+ * the application will not need to cancel/restart authentication during the configuration
+ * change.
*
- * @throws IllegalArgumentException If any of the arguments are null
+ * @throws IllegalArgumentException If any of the arguments are null.
*
- * @param cancel An object that can be used to cancel authentication
- * @param executor An executor to handle callback events
- * @param callback An object to receive authentication events
+ * @param cancel An object that can be used to cancel authentication.
+ * @param executor An executor to handle callback events.
+ * @param callback An object to receive authentication events.
*/
@RequiresPermission(USE_BIOMETRIC)
public void authenticate(@NonNull CancellationSignal cancel,
@@ -653,8 +817,22 @@
mAuthenticationCallback = callback;
final long sessionId = crypto != null ? crypto.getOpId() : 0;
if (BiometricManager.hasBiometrics(mContext)) {
+ final Bundle bundle;
+ if (crypto != null) {
+ // Allowed authenticators should default to BIOMETRIC_STRONG for crypto auth.
+ // Note that we use a new bundle here so as to not overwrite the application's
+ // preference, since it is possible that the same prompt configuration be used
+ // without a crypto object later.
+ bundle = new Bundle(mBundle);
+ bundle.putInt(KEY_AUTHENTICATORS_ALLOWED,
+ mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED,
+ Authenticators.BIOMETRIC_STRONG));
+ } else {
+ bundle = mBundle;
+ }
+
mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
- mContext.getOpPackageName(), mBundle);
+ mContext.getOpPackageName(), bundle);
} else {
mExecutor.execute(() -> {
callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT,
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 516a25d..d482198 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -35,7 +35,7 @@
// TODO(b/141025588): Make userId the first arg to be consistent with hasEnrolledBiometrics.
// Checks if biometrics can be used.
- int canAuthenticate(String opPackageName, int userId);
+ int canAuthenticate(String opPackageName, int userId, int authenticators);
// Checks if any biometrics are enrolled.
boolean hasEnrolledBiometrics(int userId, String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index ca02421..8a6be18 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -35,7 +35,7 @@
void cancelAuthentication(IBinder token, String opPackageName);
// Checks if biometrics can be used.
- int canAuthenticate(String opPackageName, int userId);
+ int canAuthenticate(String opPackageName, int userId, int authenticators);
// Checks if any biometrics are enrolled.
boolean hasEnrolledBiometrics(int userId, String opPackageName);
@@ -43,7 +43,8 @@
// Registers an authenticator (e.g. face, fingerprint, iris).
// Id must be unique, whereas strength and modality don't need to be.
// TODO(b/123321528): Turn strength and modality into enums.
- void registerAuthenticator(int id, int strength, int modality, IBiometricAuthenticator authenticator);
+ void registerAuthenticator(int id, int modality, int strength,
+ IBiometricAuthenticator authenticator);
// Register callback for when keyguard biometric eligibility changes.
void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1f29d1a..b605866 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -935,7 +935,7 @@
/**
* <p>List of the maximum number of regions that can be used for metering in
* auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF);
- * this corresponds to the the maximum number of elements in
+ * this corresponds to the maximum number of elements in
* {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}, {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions},
* and {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
* <p><b>Range of valid values:</b><br></p>
@@ -955,7 +955,7 @@
/**
* <p>The maximum number of metering regions that can be used by the auto-exposure (AE)
* routine.</p>
- * <p>This corresponds to the the maximum allowed number of elements in
+ * <p>This corresponds to the maximum allowed number of elements in
* {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}.</p>
* <p><b>Range of valid values:</b><br>
* Value will be >= 0. For FULL-capability devices, this
@@ -973,7 +973,7 @@
/**
* <p>The maximum number of metering regions that can be used by the auto-white balance (AWB)
* routine.</p>
- * <p>This corresponds to the the maximum allowed number of elements in
+ * <p>This corresponds to the maximum allowed number of elements in
* {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}.</p>
* <p><b>Range of valid values:</b><br>
* Value will be >= 0.</p>
@@ -989,7 +989,7 @@
/**
* <p>The maximum number of metering regions that can be used by the auto-focus (AF) routine.</p>
- * <p>This corresponds to the the maximum allowed number of elements in
+ * <p>This corresponds to the maximum allowed number of elements in
* {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
* <p><b>Range of valid values:</b><br>
* Value will be >= 0. For FULL-capability devices, this
@@ -1125,8 +1125,8 @@
new Key<android.util.Range<Integer>>("android.control.postRawSensitivityBoostRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
- * <p>The list of bokeh modes that are supported by this camera device, and each bokeh mode's
- * maximum streaming (non-stall) size with bokeh effect.</p>
+ * <p>The list of bokeh modes for {@link CaptureRequest#CONTROL_BOKEH_MODE android.control.bokehMode} that are supported by this camera
+ * device, and each bokeh mode's maximum streaming (non-stall) size with bokeh effect.</p>
* <p>For OFF mode, the camera behaves normally with no bokeh effect.</p>
* <p>For STILL_CAPTURE mode, the maximum streaming dimension specifies the limit under which
* bokeh is effective when capture intent is PREVIEW. Note that when capture intent is
@@ -1148,12 +1148,86 @@
* 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_BOKEH_MODE
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<int[]> CONTROL_AVAILABLE_BOKEH_MAX_SIZES =
+ new Key<int[]>("android.control.availableBokehMaxSizes", int[].class);
+
+ /**
+ * <p>The ranges of supported zoom ratio for non-OFF {@link CaptureRequest#CONTROL_BOKEH_MODE android.control.bokehMode}.</p>
+ * <p>When bokeh mode is enabled, the camera device may have limited range of zoom ratios
+ * compared to when bokeh mode is disabled. This tag lists the zoom ratio ranges for all
+ * supported non-OFF bokeh modes, in the same order as in
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_BOKEH_CAPABILITIES android.control.availableBokehCapabilities}.</p>
+ * <p>Range [1.0, 1.0] means that no zoom (optical or digital) is supported.</p>
+ * <p><b>Units</b>: (minZoom, maxZoom)</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * 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 CameraCharacteristics#CONTROL_AVAILABLE_BOKEH_CAPABILITIES
+ * @see CaptureRequest#CONTROL_BOKEH_MODE
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<float[]> CONTROL_AVAILABLE_BOKEH_ZOOM_RATIO_RANGES =
+ new Key<float[]>("android.control.availableBokehZoomRatioRanges", float[].class);
+
+ /**
+ * <p>The list of bokeh modes for {@link CaptureRequest#CONTROL_BOKEH_MODE android.control.bokehMode} that are supported by
+ * this camera device, and each bokeh mode's capabilities such as maximum streaming size
+ * with bokeh effect, and supported zoom ratio ranges.</p>
+ * <p>For OFF mode, the camera behaves normally with no bokeh effect.</p>
+ * <p>For STILL_CAPTURE mode, the maximum streaming dimension specifies the limit under which
+ * bokeh is effective when capture intent is PREVIEW. Note that when capture intent is
+ * PREVIEW, the bokeh effect may not be as high quality compared to STILL_CAPTURE intent
+ * in order to maintain reasonable frame rate. The maximum streaming dimension must be one
+ * of the YUV_420_888 or PRIVATE resolutions in availableStreamConfigurations, or (0, 0)
+ * if preview bokeh is not supported. If the application configures a stream larger than
+ * the maximum streaming dimension, bokeh effect may not be applied for this stream for
+ * PREVIEW intent.</p>
+ * <p>For CONTINUOUS mode, the maximum streaming dimension specifies the limit under which
+ * bokeh is effective. This dimension must be one of the YUV_420_888 or PRIVATE resolutions
+ * in availableStreamConfigurations, and if the sensor maximum resolution is larger than or
+ * equal to 1080p, the maximum streaming dimension must be at least 1080p. If the
+ * application configures a stream with larger dimension, the stream may not have bokeh
+ * effect applied.</p>
+ * <p>When bokeh mode is enabled, the camera device may have limited range of zoom ratios
+ * compared to when bokeh mode is disabled. availableBokehCapabilities lists the zoom
+ * ranges for all supported bokeh modes. A range of (1.0, 1.0) means that no zoom
+ * (optical or digital) is supported.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_BOKEH_MODE
+ */
+ @PublicKey
+ @NonNull
+ @SyntheticKey
+ public static final Key<android.hardware.camera2.params.Capability[]> CONTROL_AVAILABLE_BOKEH_CAPABILITIES =
+ new Key<android.hardware.camera2.params.Capability[]>("android.control.availableBokehCapabilities", android.hardware.camera2.params.Capability[].class);
+
+ /**
+ * <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
+ * 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>
+ * <p>maxZoom >= 1.0 >= minZoom</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * 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 CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
*/
@PublicKey
@NonNull
- public static final Key<android.hardware.camera2.params.CapabilityAndMaxSize[]> CONTROL_AVAILABLE_BOKEH_CAPABILITIES =
- new Key<android.hardware.camera2.params.CapabilityAndMaxSize[]>("android.control.availableBokehCapabilities", android.hardware.camera2.params.CapabilityAndMaxSize[].class);
+ public static final Key<android.util.Range<Float>> CONTROL_ZOOM_RATIO_RANGE =
+ new Key<android.util.Range<Float>>("android.control.zoomRatioRange", new TypeReference<android.util.Range<Float>>() {{ }});
/**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
@@ -1987,6 +2061,7 @@
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA SECURE_IMAGE_DATA}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA SYSTEM_CAMERA}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}</li>
* </ul></p>
* <p>This key is available on all devices.</p>
*
@@ -2006,6 +2081,7 @@
* @see #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME
* @see #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA
* @see #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING
*/
@PublicKey
@NonNull
@@ -2213,11 +2289,16 @@
* <p>Crop regions that have a width or height that is smaller
* than this ratio allows will be rounded up to the minimum
* allowed size by the camera device.</p>
+ * <p>Starting from API level 30, when using {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} to zoom in or out,
+ * the application must use {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE android.control.zoomRatioRange} to query both the minimum and
+ * maximum zoom ratio.</p>
* <p><b>Units</b>: Zoom scale factor</p>
* <p><b>Range of valid values:</b><br>
* >=1</p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE
* @see CaptureRequest#SCALER_CROP_REGION
*/
@PublicKey
@@ -2665,6 +2746,21 @@
* <p>Camera devices that support FREEFORM cropping will support any crop region that
* is inside of the active array. The camera device will apply the same crop region and
* return the final used crop region in capture result metadata {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</p>
+ * <p>Starting from API level 30,</p>
+ * <ul>
+ * <li>If the camera device supports FREEFORM cropping, in order to do FREEFORM cropping, the
+ * application must set {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} to 1.0, and use {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}
+ * for zoom.</li>
+ * <li>To do CENTER_ONLY zoom, the application has below 2 options:<ol>
+ * <li>Set {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} to 1.0; adjust zoom by {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}.</li>
+ * <li>Adjust zoom by {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}; use {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to crop
+ * the field of view vertically (letterboxing) or horizontally (pillarboxing), but not
+ * windowboxing.</li>
+ * </ol>
+ * </li>
+ * <li>Setting {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} to values different than 1.0 and
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to be windowboxing at the same time is undefined behavior.</li>
+ * </ul>
* <p>LEGACY capability devices will only support CENTER_ONLY cropping.</p>
* <p><b>Possible values:</b>
* <ul>
@@ -2673,6 +2769,7 @@
* </ul></p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see #SCALER_CROPPING_TYPE_CENTER_ONLY
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 799c716..8e0a46d 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -764,6 +764,7 @@
* <li>{@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}</li>
* <li>{@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger}</li>
* <li>{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}</li>
+ * <li>{@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}</li>
* </ul>
* <p>Outside of android.control.*, the following controls will work:</p>
* <ul>
@@ -812,6 +813,7 @@
* @see CaptureRequest#CONTROL_AWB_REGIONS
* @see CaptureRequest#CONTROL_EFFECT_MODE
* @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#FLASH_MODE
* @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
@@ -1004,6 +1006,51 @@
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14;
+ /**
+ * <p>The camera device supports the OFFLINE_PROCESSING use case.</p>
+ * <p>With OFFLINE_PROCESSING capability, the application can switch an ongoing
+ * capture session to offline mode by calling the
+ * CameraCaptureSession#switchToOffline method and specify streams to be kept in offline
+ * mode. The camera will then stop currently active repeating requests, prepare for
+ * some requests to go into offline mode, and return an offline session object. After
+ * the switchToOffline call returns, the original capture session is in closed state as
+ * if the CameraCaptureSession#close method has been called.
+ * In the offline mode, all inflight requests will continue to be processed in the
+ * background, and the application can immediately close the camera or create a new
+ * capture session without losing those requests' output images and capture results.</p>
+ * <p>While the camera device is processing offline requests, it
+ * might not be able to support all stream configurations it can support
+ * without offline requests. When that happens, the createCaptureSession
+ * method call will fail. The following stream configurations are guaranteed to work
+ * without hitting the resource busy exception:</p>
+ * <ul>
+ * <li>One ongoing offline session: target one output surface of YUV or
+ * JPEG format, any resolution.</li>
+ * <li>The active camera capture session:<ol>
+ * <li>One preview surface (SurfaceView or SurfaceTexture) up to 1920 width</li>
+ * <li>One YUV ImageReader surface up to 1920 width</li>
+ * <li>One Jpeg ImageReader, any resolution: the camera device is
+ * allowed to slow down JPEG output speed by 50% if there is any ongoing offline
+ * session.</li>
+ * <li>One ImageWriter surface of private format, any resolution if the device supports
+ * PRIVATE_REPROCESSING capability</li>
+ * </ol>
+ * </li>
+ * <li>Alternatively, the active camera session above can be replaced by an legacy
+ * {@link android.hardware.Camera Camera} with the following parameter settings:<ol>
+ * <li>Preview size up to 1920 width</li>
+ * <li>Preview callback size up to 1920 width</li>
+ * <li>Video size up to 1920 width</li>
+ * <li>Picture size, any resolution: the camera device is
+ * allowed to slow down JPEG output speed by 50% if there is any ongoing offline
+ * session.</li>
+ * </ol>
+ * </li>
+ * </ul>
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING = 15;
+
//
// Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
//
@@ -2271,6 +2318,7 @@
* <li>{@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}</li>
* <li>{@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger}</li>
* <li>{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}</li>
+ * <li>{@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}</li>
* </ul>
* <p>Outside of android.control.*, the following controls will work:</p>
* <ul>
@@ -2317,6 +2365,7 @@
* @see CaptureRequest#CONTROL_AWB_REGIONS
* @see CaptureRequest#CONTROL_EFFECT_MODE
* @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#FLASH_MODE
* @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5c76dff..6bf5783 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1376,6 +1376,16 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same aeRegions values at different
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} represent different parts of the scene. The aeRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to 1.0 (default), the same
+ * aeRegions at different {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} still represent the same parts of the
+ * scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p><b>Units</b>: Pixel coordinates within {@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 capability and mode</p>
@@ -1386,6 +1396,7 @@
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
* @see CameraCharacteristics#CONTROL_MAX_REGIONS_AE
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -1575,6 +1586,16 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same afRegions values at different
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} represent different parts of the scene. The afRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to 1.0 (default), the same
+ * afRegions at different {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} still represent the same parts of the
+ * scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p><b>Units</b>: Pixel coordinates within {@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 capability and mode</p>
@@ -1585,6 +1606,7 @@
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
* @see CameraCharacteristics#CONTROL_MAX_REGIONS_AF
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -1767,6 +1789,16 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same awbRegions values at different
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} represent different parts of the scene. The awbRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to 1.0 (default), the same
+ * awbRegions at different {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} still represent the same parts of
+ * the scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p><b>Units</b>: Pixel coordinates within {@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 capability and mode</p>
@@ -1777,6 +1809,7 @@
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
* @see CameraCharacteristics#CONTROL_MAX_REGIONS_AWB
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -2140,6 +2173,56 @@
new Key<Integer>("android.control.bokehMode", int.class);
/**
+ * <p>The desired zoom ratio</p>
+ * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} with dual purposes of crop and zoom, the
+ * application can now choose to use this tag to specify the desired zoom level. The
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} can still be used to specify the horizontal or vertical
+ * crop to achieve aspect ratios different than the native camera sensor.</p>
+ * <p>By using this control, the application gains a simpler way to control zoom, which can
+ * be a combination of optical and digital zoom. More specifically, for a logical
+ * multi-camera with more than one focal length, using a floating point zoom ratio offers
+ * more zoom precision when a telephoto lens is used, as well as allowing zoom ratio of
+ * less than 1.0 to zoom out to a wide field of view.</p>
+ * <p>Note that the coordinate system of cropRegion, AE/AWB/AF regions, and faces now changes
+ * to the effective after-zoom field-of-view represented by rectangle of (0, 0,
+ * activeArrayWidth, activeArrayHeight).</p>
+ * <p>For example, if {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} is 4032*3024, and the preview stream
+ * is configured to the same 4:3 aspect ratio, the application can achieve 2.0x zoom in
+ * one of two ways:</p>
+ * <ul>
+ * <li>zoomRatio = 2.0, scaler.cropRegion = (0, 0, 4032, 3024)</li>
+ * <li>zoomRatio = 1.0 (default), scaler.cropRegion = (1008, 756, 3024, 2268)</li>
+ * </ul>
+ * <p>If the application intends to set aeRegions to be top-left quarter of the preview
+ * field-of-view, the {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions} should be set to (0, 0, 2016, 1512) with
+ * zoomRatio set to 2.0. Alternatively, the application can set aeRegions to the equivalent
+ * region of (1008, 756, 2016, 1512) for zoomRatio of 1.0. If the application doesn't
+ * explicitly set {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, its value defaults to 1.0.</p>
+ * <p>This coordinate system change isn't applicable to RAW capture and its related metadata
+ * such as intrinsicCalibration and lensShadingMap.</p>
+ * <p>One limitation of controlling zoom using zoomRatio is that the {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}
+ * must only be used for letterboxing or pillarboxing of the sensor active array, and no
+ * FREEFORM cropping can be used with {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} other than 1.0.</p>
+ * <p><b>Range of valid values:</b><br>
+ * {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE android.control.zoomRatioRange}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * 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_AE_REGIONS
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Float> CONTROL_ZOOM_RATIO =
+ new Key<Float>("android.control.zoomRatio", float.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
@@ -2697,12 +2780,21 @@
* 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>
* <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
* capability and mode</p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 2d0ec6d..c995623 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -780,6 +780,16 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same aeRegions values at different
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} represent different parts of the scene. The aeRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to 1.0 (default), the same
+ * aeRegions at different {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} still represent the same parts of the
+ * scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p><b>Units</b>: Pixel coordinates within {@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 capability and mode</p>
@@ -790,6 +800,7 @@
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
* @see CameraCharacteristics#CONTROL_MAX_REGIONS_AE
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -1228,6 +1239,16 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same afRegions values at different
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} represent different parts of the scene. The afRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to 1.0 (default), the same
+ * afRegions at different {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} still represent the same parts of the
+ * scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p><b>Units</b>: Pixel coordinates within {@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 capability and mode</p>
@@ -1238,6 +1259,7 @@
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
* @see CameraCharacteristics#CONTROL_MAX_REGIONS_AF
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -1830,6 +1852,16 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same awbRegions values at different
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} represent different parts of the scene. The awbRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to 1.0 (default), the same
+ * awbRegions at different {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} still represent the same parts of
+ * the scene as they do before. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p><b>Units</b>: Pixel coordinates within {@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 capability and mode</p>
@@ -1840,6 +1872,7 @@
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
* @see CameraCharacteristics#CONTROL_MAX_REGIONS_AWB
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -2370,6 +2403,56 @@
new Key<Integer>("android.control.bokehMode", int.class);
/**
+ * <p>The desired zoom ratio</p>
+ * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} with dual purposes of crop and zoom, the
+ * application can now choose to use this tag to specify the desired zoom level. The
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} can still be used to specify the horizontal or vertical
+ * crop to achieve aspect ratios different than the native camera sensor.</p>
+ * <p>By using this control, the application gains a simpler way to control zoom, which can
+ * be a combination of optical and digital zoom. More specifically, for a logical
+ * multi-camera with more than one focal length, using a floating point zoom ratio offers
+ * more zoom precision when a telephoto lens is used, as well as allowing zoom ratio of
+ * less than 1.0 to zoom out to a wide field of view.</p>
+ * <p>Note that the coordinate system of cropRegion, AE/AWB/AF regions, and faces now changes
+ * to the effective after-zoom field-of-view represented by rectangle of (0, 0,
+ * activeArrayWidth, activeArrayHeight).</p>
+ * <p>For example, if {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} is 4032*3024, and the preview stream
+ * is configured to the same 4:3 aspect ratio, the application can achieve 2.0x zoom in
+ * one of two ways:</p>
+ * <ul>
+ * <li>zoomRatio = 2.0, scaler.cropRegion = (0, 0, 4032, 3024)</li>
+ * <li>zoomRatio = 1.0 (default), scaler.cropRegion = (1008, 756, 3024, 2268)</li>
+ * </ul>
+ * <p>If the application intends to set aeRegions to be top-left quarter of the preview
+ * field-of-view, the {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions} should be set to (0, 0, 2016, 1512) with
+ * zoomRatio set to 2.0. Alternatively, the application can set aeRegions to the equivalent
+ * region of (1008, 756, 2016, 1512) for zoomRatio of 1.0. If the application doesn't
+ * explicitly set {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, its value defaults to 1.0.</p>
+ * <p>This coordinate system change isn't applicable to RAW capture and its related metadata
+ * such as intrinsicCalibration and lensShadingMap.</p>
+ * <p>One limitation of controlling zoom using zoomRatio is that the {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}
+ * must only be used for letterboxing or pillarboxing of the sensor active array, and no
+ * FREEFORM cropping can be used with {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} other than 1.0.</p>
+ * <p><b>Range of valid values:</b><br>
+ * {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE android.control.zoomRatioRange}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * 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_AE_REGIONS
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Float> CONTROL_ZOOM_RATIO =
+ new Key<Float>("android.control.zoomRatio", float.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
@@ -3336,12 +3419,21 @@
* 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>
* <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
* capability and mode</p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -3877,10 +3969,21 @@
* 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>Only available if {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} == FULL
- * This key is available on all devices.</p>
+ * <p>Only available if {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} == FULL.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoomRatio field of view. This means that if the relative position of faces and
+ * the camera device doesn't change, when zooming in by increasing
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, the face landmarks move farther away from the center of the
+ * activeArray or preCorrectionActiveArray. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} is set to 1.0
+ * (default), the face landmarks coordinates won't change as {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}
+ * changes. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use activeArraySize or
+ * preCorrectionActiveArraySize still depends on distortion correction mode.</p>
+ * <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
+ * @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
* @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
@@ -3903,10 +4006,21 @@
* 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>Only available if {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} != OFF
- * This key is available on all devices.</p>
+ * <p>Only available if {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} != OFF.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoomRatio field of view. This means that if the relative position of faces and
+ * the camera device doesn't change, when zooming in by increasing
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, the face rectangles grow larger and move farther away from
+ * the center of the activeArray or preCorrectionActiveArray. If {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}
+ * is set to 1.0 (default), the face rectangles won't change as {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}
+ * changes. See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details. Whether to use activeArraySize or
+ * preCorrectionActiveArraySize still depends on distortion correction mode.</p>
+ * <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
+ * @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
* @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index bcbc337..41435c9 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -29,6 +29,7 @@
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
@@ -866,6 +867,38 @@
}
}
+ public void switchToOffline(ICameraDeviceCallbacks cbs, Surface[] offlineOutputs)
+ throws CameraAccessException {
+ if ((offlineOutputs == null) || (offlineOutputs.length == 0)) {
+ throw new IllegalArgumentException("Invalid offline outputs!");
+ }
+ if (cbs == null) {
+ throw new IllegalArgumentException("Invalid device callbacks!");
+ }
+
+ ICameraOfflineSession offlineSession = null;
+ synchronized(mInterfaceLock) {
+ int streamId = -1;
+ for (Surface surface : offlineOutputs) {
+ for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+ if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
+ streamId = mConfiguredOutputs.keyAt(i);
+ break;
+ }
+ }
+ if (streamId == -1) {
+ throw new IllegalArgumentException("Offline surface is not part of this" +
+ " session");
+ }
+ }
+
+ offlineSession = mRemoteDevice.switchToOffline(cbs,
+ offlineOutputs);
+ // TODO: Initialize CameraOfflineSession wrapper, clear 'mConfiguredOutputs',
+ // and update request tracking
+ }
+ }
+
public void tearDown(Surface surface) throws CameraAccessException {
if (surface == null) throw new IllegalArgumentException("Surface is null");
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 7c1ddad..c3ebe43 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -30,7 +30,6 @@
import android.hardware.camera2.marshal.Marshaler;
import android.hardware.camera2.marshal.impl.MarshalQueryableArray;
import android.hardware.camera2.marshal.impl.MarshalQueryableBlackLevelPattern;
-import android.hardware.camera2.marshal.impl.MarshalQueryableCapabilityAndMaxSize;
import android.hardware.camera2.marshal.impl.MarshalQueryableBoolean;
import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform;
import android.hardware.camera2.marshal.impl.MarshalQueryableEnum;
@@ -50,7 +49,7 @@
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration;
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
import android.hardware.camera2.marshal.impl.MarshalQueryableString;
-import android.hardware.camera2.params.CapabilityAndMaxSize;
+import android.hardware.camera2.params.Capability;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
@@ -71,6 +70,7 @@
import android.os.Parcelable;
import android.os.ServiceSpecificException;
import android.util.Log;
+import android.util.Range;
import android.util.Size;
import com.android.internal.util.Preconditions;
@@ -1385,22 +1385,57 @@
return samples;
}
- private CapabilityAndMaxSize[] getBokehCapabilities() {
- CapabilityAndMaxSize[] bcs = getBase(
- CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_CAPABILITIES);
+ private Capability[] getBokehCapabilities() {
+ int[] bokehMaxSizes = getBase(CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_MAX_SIZES);
+ float[] bokehZoomRanges = getBase(
+ CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_ZOOM_RATIO_RANGES);
+ Range<Float> zoomRange = getBase(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
+ float maxDigitalZoom = getBase(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
- if (bcs != null) {
- for (CapabilityAndMaxSize bc : bcs) {
- if (bc.getMode() < CameraMetadata.CONTROL_BOKEH_MODE_OFF ||
- bc.getMode() > CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS) {
- throw new AssertionError(String.format(
- "bokehMode %d is out of valid range [%d, %d]", bc.getMode(),
- CameraMetadata.CONTROL_BOKEH_MODE_OFF,
- CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS));
- }
+ if (bokehMaxSizes == null) {
+ return null;
+ }
+ if (bokehMaxSizes.length % 3 != 0) {
+ throw new AssertionError("availableBokehMaxSizes must be tuples of " +
+ "[mode, width, height]");
+ }
+ int numBokehModes = bokehMaxSizes.length / 3;
+ int numBokehZoomRanges = 0;
+ if (bokehZoomRanges != null) {
+ if (bokehZoomRanges.length % 2 != 0) {
+ throw new AssertionError("availableBokehZoomRanges must be tuples of " +
+ "[minZoom, maxZoom]");
+ }
+ numBokehZoomRanges = bokehZoomRanges.length / 2;
+ if (numBokehModes - numBokehZoomRanges != 1) {
+ throw new AssertionError("Number of bokeh zoom ranges must be 1 less than " +
+ "number of supported bokeh modes");
}
}
- return bcs;
+
+ float bokehOffMinZoomRatio = 1.0f;
+ float bokehOffMaxZoomRatio = maxDigitalZoom;
+ if (zoomRange != null) {
+ bokehOffMinZoomRatio = zoomRange.getLower();
+ bokehOffMaxZoomRatio = zoomRange.getUpper();
+ }
+
+ Capability[] capabilities = new Capability[numBokehModes];
+ for (int i = 0, j = 0; i < numBokehModes; i++) {
+ int mode = bokehMaxSizes[3 * i];
+ int width = bokehMaxSizes[3 * i + 1];
+ int height = bokehMaxSizes[3 * i + 2];
+ if (mode != CameraMetadata.CONTROL_BOKEH_MODE_OFF && j < numBokehZoomRanges) {
+ capabilities[i] = new Capability(mode, width, height, bokehZoomRanges[2 * j],
+ bokehZoomRanges[2 * j + 1]);
+ j++;
+ } else {
+ capabilities[i] = new Capability(mode, width, height, bokehOffMinZoomRatio,
+ bokehOffMaxZoomRatio);
+ }
+ }
+
+ return capabilities;
}
private <T> void setBase(CameraCharacteristics.Key<T> key, T value) {
@@ -1780,7 +1815,6 @@
new MarshalQueryableBlackLevelPattern(),
new MarshalQueryableHighSpeedVideoConfiguration(),
new MarshalQueryableRecommendedStreamConfiguration(),
- new MarshalQueryableCapabilityAndMaxSize(),
// generic parcelable marshaler (MUST BE LAST since it has lowest priority)
new MarshalQueryableParcelable(),
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 3660f29..397417b 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -28,6 +28,8 @@
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
@@ -248,6 +250,17 @@
}
}
+ public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
+ Surface[] offlineOutputs)
+ throws CameraAccessException {
+ try {
+ return mRemoteDevice.switchToOffline(cbs, offlineOutputs);
+ } catch (Throwable t) {
+ CameraManager.throwAsPublicException(t);
+ throw new UnsupportedOperationException("Unexpected exception", t);
+ }
+ }
+
public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig)
throws CameraAccessException {
try {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 5d1435a..6ab0c29 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -24,6 +24,7 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
@@ -789,6 +790,12 @@
}
@Override
+ public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
+ Surface[] offlineOutputs) {
+ throw new UnsupportedOperationException("Legacy device does not support switchToOffline");
+ }
+
+ @Override
public IBinder asBinder() {
// This is solely intended to be used for in-process binding.
return null;
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableCapabilityAndMaxSize.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableCapabilityAndMaxSize.java
deleted file mode 100644
index 5c1f301..0000000
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableCapabilityAndMaxSize.java
+++ /dev/null
@@ -1,77 +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 android.hardware.camera2.marshal.impl;
-
-import android.hardware.camera2.marshal.MarshalQueryable;
-import android.hardware.camera2.marshal.Marshaler;
-import android.hardware.camera2.params.CapabilityAndMaxSize;
-import android.hardware.camera2.utils.TypeReference;
-import android.util.Size;
-
-import java.nio.ByteBuffer;
-
-import static android.hardware.camera2.impl.CameraMetadataNative.TYPE_INT32;
-import static android.hardware.camera2.marshal.MarshalHelpers.SIZEOF_INT32;
-
-/**
- * Marshal {@link CapabilityAndMaxSize} to/from {@link #TYPE_INT32} {@code x CapabilityAndMaxSize.COUNT}
- */
-public class MarshalQueryableCapabilityAndMaxSize implements MarshalQueryable<CapabilityAndMaxSize> {
- private static final int SIZE = SIZEOF_INT32 * CapabilityAndMaxSize.COUNT;
-
- private class MarshalerCapabilityAndMaxSize extends Marshaler<CapabilityAndMaxSize> {
- protected MarshalerCapabilityAndMaxSize(TypeReference<CapabilityAndMaxSize> typeReference,
- int nativeType) {
- super(MarshalQueryableCapabilityAndMaxSize.this, typeReference, nativeType);
- }
-
- @Override
- public void marshal(CapabilityAndMaxSize value, ByteBuffer buffer) {
- Size maxStreamingSize = value.getMaxStreamingSize();
-
- buffer.putInt(value.getMode());
- buffer.putInt(maxStreamingSize.getWidth());
- buffer.putInt(maxStreamingSize.getHeight());
- }
-
- @Override
- public CapabilityAndMaxSize unmarshal(ByteBuffer buffer) {
- int mode = buffer.getInt();
- int maxWidth = buffer.getInt();
- int maxHeight = buffer.getInt();
-
- return new CapabilityAndMaxSize(mode, maxWidth, maxHeight);
- }
-
- @Override
- public int getNativeSize() {
- return SIZE;
- }
- }
-
- @Override
- public Marshaler<CapabilityAndMaxSize> createMarshaler(
- TypeReference<CapabilityAndMaxSize> managedType, int nativeType) {
- return new MarshalerCapabilityAndMaxSize(managedType, nativeType);
- }
-
- @Override
- public boolean isTypeMappingSupported(
- TypeReference<CapabilityAndMaxSize> managedType, int nativeType) {
- return nativeType == TYPE_INT32 &&
- (CapabilityAndMaxSize.class.equals(managedType.getType()));
- }
-}
diff --git a/core/java/android/hardware/camera2/params/CapabilityAndMaxSize.java b/core/java/android/hardware/camera2/params/Capability.java
similarity index 61%
rename from core/java/android/hardware/camera2/params/CapabilityAndMaxSize.java
rename to core/java/android/hardware/camera2/params/Capability.java
index be08299..367690c 100644
--- a/core/java/android/hardware/camera2/params/CapabilityAndMaxSize.java
+++ b/core/java/android/hardware/camera2/params/Capability.java
@@ -23,16 +23,17 @@
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.utils.HashCodeHelpers;
+import android.util.Range;
import android.util.Size;
/**
- * Immutable class to store the available camera capability and its
- * corresponding maximum streaming dimensions.
+ * Immutable class to store the camera capability, its corresponding maximum
+ * streaming dimension and zoom range.
*
* @see CameraCharacteristics#CONTROL_AVAILABLE_BOKEH_CAPABILITIES
*/
-public final class CapabilityAndMaxSize {
+public final class Capability {
/**
* @hide
*/
@@ -41,22 +42,31 @@
private final int mMode;
private final int mMaxStreamingWidth;
private final int mMaxStreamingHeight;
+ private final float mMinZoomRatio;
+ private final float mMaxZoomRatio;
/**
- * Create a new CapabilityAndMaxSize object.
+ * Create a new Capability object.
*
* @param mode supported mode for a camera capability.
- * @param maxStreamingWidth width >= 0
- * @param maxStreamingHeight height >= 0
+ * @param maxStreamingWidth The width of the maximum streaming size for this mode
+ * @param maxStreamingHeight The height of the maximum streaming size for this mode
+ * @param minZoomRatio the minimum zoom ratio this mode supports
+ * @param maxZoomRatio the maximum zoom ratio this mode supports
*
+ * @throws IllegalArgumentException if any of the argument is not valid
* @hide
*/
- public CapabilityAndMaxSize(int mode, int maxStreamingWidth, int maxStreamingHeight) {
+ public Capability(int mode, int maxStreamingWidth, int maxStreamingHeight,
+ float minZoomRatio, float maxZoomRatio) {
mMode = mode;
mMaxStreamingWidth = checkArgumentNonnegative(maxStreamingWidth,
"maxStreamingWidth must be nonnegative");
mMaxStreamingHeight = checkArgumentNonnegative(maxStreamingHeight,
"maxStreamingHeight must be nonnegative");
+ mMinZoomRatio = checkArgumentInRange(minZoomRatio, 0.0f, 1.0f,
+ "minZoomRatio must be between 0.0f and 1.0f");
+ mMaxZoomRatio = maxZoomRatio;
}
/**
@@ -81,11 +91,22 @@
}
/**
- * Compare two CapabilityAndMaxSize objects to see if they are equal.
+ * Return the zoom ratio range of this capability.
*
- * @param obj Another CapabilityAndMaxSize object
+ * @return The supported zoom ratio range supported by this capability
+ */
+ public @NonNull Range<Float> getZoomRatioRange() {
+ return new Range<Float>(mMinZoomRatio, mMaxZoomRatio);
+ }
+
+
+ /**
+ * Compare two Capability objects to see if they are equal.
*
- * @return {@code true} if the mode and max size are equal, {@code false} otherwise
+ * @param obj Another Capability object
+ *
+ * @return {@code true} if the mode, max size and zoom ratio range are equal,
+ * {@code false} otherwise
*/
@Override
public boolean equals(final Object obj) {
@@ -95,11 +116,13 @@
if (this == obj) {
return true;
}
- if (obj instanceof CapabilityAndMaxSize) {
- final CapabilityAndMaxSize other = (CapabilityAndMaxSize) obj;
+ if (obj instanceof Capability) {
+ final Capability other = (Capability) obj;
return (mMode == other.mMode
&& mMaxStreamingWidth == other.mMaxStreamingWidth
- && mMaxStreamingHeight == other.mMaxStreamingHeight);
+ && mMaxStreamingHeight == other.mMaxStreamingHeight
+ && mMinZoomRatio == other.mMinZoomRatio
+ && mMaxZoomRatio == other.mMaxZoomRatio);
}
return false;
}
@@ -109,18 +132,20 @@
*/
@Override
public int hashCode() {
- return HashCodeHelpers.hashCode(mMode, mMaxStreamingWidth, mMaxStreamingHeight);
+ return HashCodeHelpers.hashCode(mMode, mMaxStreamingWidth, mMaxStreamingHeight,
+ mMinZoomRatio, mMaxZoomRatio);
}
/**
- * Return the CapabilityAndMaxSize as a string representation
- * {@code "(mode:%d, maxStreamingSize:%d x %d)"}.
+ * Return the Capability as a string representation
+ * {@code "(mode:%d, maxStreamingSize:%d x %d, zoomRatio: %f-%f)"}.
*
* @return string representation of the capability and max streaming size.
*/
@Override
public String toString() {
- return String.format("(mode:%d, maxStreamingSize:%d x %d)",
- mMode, mMaxStreamingWidth, mMaxStreamingHeight);
+ return String.format("(mode:%d, maxStreamingSize:%d x %d, zoomRatio: %f-%f)",
+ mMode, mMaxStreamingWidth, mMaxStreamingHeight, mMinZoomRatio,
+ mMaxZoomRatio);
}
}
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 6eaf54b..23f18a8 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1163,7 +1163,7 @@
if (orderedPreviewSizes != null) {
for (Size size : orderedPreviewSizes) {
if ((mDisplaySize.getWidth() >= size.getWidth()) &&
- (mDisplaySize.getWidth() >= size.getHeight())) {
+ (mDisplaySize.getHeight() >= size.getHeight())) {
return size;
}
}
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
new file mode 100644
index 0000000..8231c58
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -0,0 +1,329 @@
+/*
+ * 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.hardware.soundtrigger;
+
+import android.hardware.soundtrigger.ModelParams;
+import android.media.AudioFormat;
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+import android.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+/** @hide */
+class ConversionUtil {
+ public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor(
+ SoundTriggerModuleDescriptor aidlDesc) {
+ SoundTriggerModuleProperties properties = aidlDesc.properties;
+ return new SoundTrigger.ModuleProperties(
+ aidlDesc.handle,
+ properties.implementor,
+ properties.description,
+ properties.uuid,
+ properties.version,
+ properties.maxSoundModels,
+ properties.maxKeyPhrases,
+ properties.maxUsers,
+ aidl2apiRecognitionModes(properties.recognitionModes),
+ properties.captureTransition,
+ properties.maxBufferMs,
+ properties.concurrentCapture,
+ properties.powerConsumptionMw,
+ properties.triggerInEvent
+ );
+ }
+
+ public static int aidl2apiRecognitionModes(int aidlModes) {
+ int result = 0;
+ if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+ }
+ if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+ }
+ if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
+ }
+ if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_GENERIC;
+ }
+ return result;
+ }
+
+ public static int api2aidlRecognitionModes(int apiModes) {
+ int result = 0;
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER) != 0) {
+ result |= RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION) != 0) {
+ result |= RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION) != 0) {
+ result |= RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_GENERIC) != 0) {
+ result |= RecognitionMode.GENERIC_TRIGGER;
+ }
+ return result;
+ }
+
+
+ public static SoundModel api2aidlGenericSoundModel(SoundTrigger.GenericSoundModel apiModel) {
+ return api2aidlSoundModel(apiModel);
+ }
+
+ public static SoundModel api2aidlSoundModel(SoundTrigger.SoundModel apiModel) {
+ SoundModel aidlModel = new SoundModel();
+ aidlModel.type = apiModel.type;
+ aidlModel.uuid = api2aidlUuid(apiModel.uuid);
+ aidlModel.vendorUuid = api2aidlUuid(apiModel.vendorUuid);
+ aidlModel.data = Arrays.copyOf(apiModel.data, apiModel.data.length);
+ return aidlModel;
+ }
+
+ public static String api2aidlUuid(UUID apiUuid) {
+ return apiUuid.toString();
+ }
+
+ public static PhraseSoundModel api2aidlPhraseSoundModel(
+ SoundTrigger.KeyphraseSoundModel apiModel) {
+ PhraseSoundModel aidlModel = new PhraseSoundModel();
+ aidlModel.common = api2aidlSoundModel(apiModel);
+ aidlModel.phrases = new Phrase[apiModel.keyphrases.length];
+ for (int i = 0; i < apiModel.keyphrases.length; ++i) {
+ aidlModel.phrases[i] = api2aidlPhrase(apiModel.keyphrases[i]);
+ }
+ return aidlModel;
+ }
+
+ public static Phrase api2aidlPhrase(SoundTrigger.Keyphrase apiPhrase) {
+ Phrase aidlPhrase = new Phrase();
+ aidlPhrase.id = apiPhrase.id;
+ aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.recognitionModes);
+ aidlPhrase.users = Arrays.copyOf(apiPhrase.users, apiPhrase.users.length);
+ aidlPhrase.locale = apiPhrase.locale;
+ aidlPhrase.text = apiPhrase.text;
+ return aidlPhrase;
+ }
+
+ public static RecognitionConfig api2aidlRecognitionConfig(
+ SoundTrigger.RecognitionConfig apiConfig) {
+ RecognitionConfig aidlConfig = new RecognitionConfig();
+ aidlConfig.captureRequested = apiConfig.captureRequested;
+ // apiConfig.allowMultipleTriggers is ignored by the lower layers.
+ aidlConfig.phraseRecognitionExtras =
+ new PhraseRecognitionExtra[apiConfig.keyphrases.length];
+ for (int i = 0; i < apiConfig.keyphrases.length; ++i) {
+ aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra(
+ apiConfig.keyphrases[i]);
+ }
+ aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+ return aidlConfig;
+ }
+
+ public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
+ SoundTrigger.KeyphraseRecognitionExtra apiExtra) {
+ PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+ aidlExtra.id = apiExtra.id;
+ aidlExtra.recognitionModes = api2aidlRecognitionModes(apiExtra.recognitionModes);
+ aidlExtra.confidenceLevel = apiExtra.coarseConfidenceLevel;
+ aidlExtra.levels = new ConfidenceLevel[apiExtra.confidenceLevels.length];
+ for (int i = 0; i < apiExtra.confidenceLevels.length; ++i) {
+ aidlExtra.levels[i] = api2aidlConfidenceLevel(apiExtra.confidenceLevels[i]);
+ }
+ return aidlExtra;
+ }
+
+ public static SoundTrigger.KeyphraseRecognitionExtra aidl2apiPhraseRecognitionExtra(
+ PhraseRecognitionExtra aidlExtra) {
+ SoundTrigger.ConfidenceLevel[] apiLevels =
+ new SoundTrigger.ConfidenceLevel[aidlExtra.levels.length];
+ for (int i = 0; i < aidlExtra.levels.length; ++i) {
+ apiLevels[i] = aidl2apiConfidenceLevel(aidlExtra.levels[i]);
+ }
+ return new SoundTrigger.KeyphraseRecognitionExtra(aidlExtra.id,
+ aidl2apiRecognitionModes(aidlExtra.recognitionModes),
+ aidlExtra.confidenceLevel, apiLevels);
+ }
+
+ public static ConfidenceLevel api2aidlConfidenceLevel(
+ SoundTrigger.ConfidenceLevel apiLevel) {
+ ConfidenceLevel aidlLevel = new ConfidenceLevel();
+ aidlLevel.levelPercent = apiLevel.confidenceLevel;
+ aidlLevel.userId = apiLevel.userId;
+ return aidlLevel;
+ }
+
+ public static SoundTrigger.ConfidenceLevel aidl2apiConfidenceLevel(
+ ConfidenceLevel apiLevel) {
+ return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
+ }
+
+ public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
+ int modelHandle, RecognitionEvent aidlEvent) {
+ return new SoundTrigger.GenericRecognitionEvent(
+ aidlEvent.status,
+ modelHandle, aidlEvent.captureAvailable, aidlEvent.captureSession,
+ aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
+ aidl2apiAudioFormat(aidlEvent.audioConfig), aidlEvent.data);
+ }
+
+ public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
+ int modelHandle,
+ PhraseRecognitionEvent aidlEvent) {
+ SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
+ new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
+ for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
+ apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
+ }
+ return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
+ aidlEvent.common.captureAvailable,
+ aidlEvent.common.captureSession, aidlEvent.common.captureDelayMs,
+ aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData,
+ aidl2apiAudioFormat(aidlEvent.common.audioConfig), aidlEvent.common.data,
+ apiExtras);
+ }
+
+ public static AudioFormat aidl2apiAudioFormat(AudioConfig audioConfig) {
+ AudioFormat.Builder apiBuilder = new AudioFormat.Builder();
+ apiBuilder.setSampleRate(audioConfig.sampleRateHz);
+ apiBuilder.setChannelMask(aidl2apiChannelInMask(audioConfig.channelMask));
+ apiBuilder.setEncoding(aidl2apiEncoding(audioConfig.format));
+ return apiBuilder.build();
+ }
+
+ public static int aidl2apiEncoding(int aidlFormat) {
+ switch (aidlFormat) {
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_16_BIT:
+ return AudioFormat.ENCODING_PCM_16BIT;
+
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_8_BIT:
+ return AudioFormat.ENCODING_PCM_8BIT;
+
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_FLOAT:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_8_24_BIT:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_24_BIT_PACKED:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_32_BIT:
+ return AudioFormat.ENCODING_PCM_FLOAT;
+
+ case android.media.audio.common.AudioFormat.AC3:
+ return AudioFormat.ENCODING_AC3;
+
+ case android.media.audio.common.AudioFormat.E_AC3:
+ return AudioFormat.ENCODING_E_AC3;
+
+ case android.media.audio.common.AudioFormat.DTS:
+ return AudioFormat.ENCODING_DTS;
+
+ case android.media.audio.common.AudioFormat.DTS_HD:
+ return AudioFormat.ENCODING_DTS_HD;
+
+ case android.media.audio.common.AudioFormat.MP3:
+ return AudioFormat.ENCODING_MP3;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_LC:
+ return AudioFormat.ENCODING_AAC_LC;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_HE_V1:
+ return AudioFormat.ENCODING_AAC_HE_V1;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_HE_V2:
+ return AudioFormat.ENCODING_AAC_HE_V2;
+
+ case android.media.audio.common.AudioFormat.IEC61937:
+ return AudioFormat.ENCODING_IEC61937;
+
+ case android.media.audio.common.AudioFormat.DOLBY_TRUEHD:
+ return AudioFormat.ENCODING_DOLBY_TRUEHD;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_ELD:
+ return AudioFormat.ENCODING_AAC_ELD;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_XHE:
+ return AudioFormat.ENCODING_AAC_XHE;
+
+ case android.media.audio.common.AudioFormat.AC4:
+ return AudioFormat.ENCODING_AC4;
+
+ case android.media.audio.common.AudioFormat.E_AC3
+ | android.media.audio.common.AudioFormat.E_AC3_SUB_JOC:
+ return AudioFormat.ENCODING_E_AC3_JOC;
+
+ case android.media.audio.common.AudioFormat.MAT:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_1_0:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_2_0:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_2_1:
+ return AudioFormat.ENCODING_DOLBY_MAT;
+
+ case android.media.audio.common.AudioFormat.DEFAULT:
+ return AudioFormat.ENCODING_DEFAULT;
+
+ default:
+ return AudioFormat.ENCODING_INVALID;
+ }
+ }
+
+ public static int api2aidlModelParameter(int apiParam) {
+ switch (apiParam) {
+ case ModelParams.THRESHOLD_FACTOR:
+ return android.media.soundtrigger_middleware.ModelParameter.THRESHOLD_FACTOR;
+ default:
+ return android.media.soundtrigger_middleware.ModelParameter.INVALID;
+ }
+ }
+
+ public static int aidl2apiChannelInMask(int aidlMask) {
+ // We're assuming AudioFormat.CHANNEL_IN_* constants are kept in sync with
+ // android.media.audio.common.AudioChannelMask.
+ return aidlMask;
+ }
+
+ public static SoundTrigger.ModelParamRange aidl2apiModelParameterRange(
+ @Nullable ModelParameterRange aidlRange) {
+ if (aidlRange == null) {
+ return null;
+ }
+ return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/ModelParams.aidl b/core/java/android/hardware/soundtrigger/ModelParams.aidl
new file mode 100644
index 0000000..d90dc81
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/ModelParams.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.hardware.soundtrigger;
+
+/**
+ * Model specific parameters to be used with parameter set and get APIs
+ * {@hide}
+ */
+@Backing(type="int")
+enum ModelParams {
+ /**
+ * Placeholder for invalid model parameter used for returning error or
+ * passing an invalid value.
+ */
+ INVALID = -1,
+ /**
+ * 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.
+ */
+ THRESHOLD_FACTOR = 0,
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
index 325a9ad..94c4216 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.aidl
@@ -24,5 +24,6 @@
parcelable SoundTrigger.KeyphraseRecognitionExtra;
parcelable SoundTrigger.KeyphraseSoundModel;
parcelable SoundTrigger.GenericSoundModel;
+parcelable SoundTrigger.ModelParamRange;
parcelable SoundTrigger.ModuleProperties;
parcelable SoundTrigger.RecognitionConfig;
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index b1134e1..5484df4 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -22,18 +22,29 @@
import static android.system.OsConstants.EPERM;
import static android.system.OsConstants.EPIPE;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.content.Context;
import android.media.AudioFormat;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.UUID;
/**
@@ -44,6 +55,7 @@
*/
@SystemApi
public class SoundTrigger {
+ private static final String TAG = "SoundTrigger";
private SoundTrigger() {
}
@@ -119,15 +131,15 @@
* recognition callback event */
public final boolean returnsTriggerInEvent;
- ModuleProperties(int id, String implementor, String description,
- String uuid, int version, int maxSoundModels, int maxKeyphrases,
+ 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) {
this.id = id;
- this.implementor = implementor;
- this.description = description;
- this.uuid = UUID.fromString(uuid);
+ this.implementor = requireNonNull(implementor);
+ this.description = requireNonNull(description);
+ this.uuid = UUID.fromString(requireNonNull(uuid));
this.version = version;
this.maxSoundModels = maxSoundModels;
this.maxKeyphrases = maxKeyphrases;
@@ -231,6 +243,7 @@
/** Unique sound model identifier */
@UnsupportedAppUsage
+ @NonNull
public final UUID uuid;
/** Sound model type (e.g. TYPE_KEYPHRASE); */
@@ -238,17 +251,20 @@
/** Unique sound model vendor identifier */
@UnsupportedAppUsage
+ @NonNull
public final UUID vendorUuid;
/** Opaque data. For use by vendor implementation and enrollment application */
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
- public SoundModel(UUID uuid, UUID vendorUuid, int type, byte[] data) {
- this.uuid = uuid;
- this.vendorUuid = vendorUuid;
+ public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
+ @Nullable byte[] data) {
+ this.uuid = requireNonNull(uuid);
+ this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
this.type = type;
- this.data = data;
+ this.data = data != null ? data : new byte[0];
}
@Override
@@ -271,8 +287,6 @@
if (!(obj instanceof SoundModel))
return false;
SoundModel other = (SoundModel) obj;
- if (!Arrays.equals(data, other.data))
- return false;
if (type != other.type)
return false;
if (uuid == null) {
@@ -285,6 +299,8 @@
return false;
} else if (!vendorUuid.equals(other.vendorUuid))
return false;
+ if (!Arrays.equals(data, other.data))
+ return false;
return true;
}
}
@@ -306,24 +322,28 @@
/** Locale of the keyphrase. JAVA Locale string e.g en_US */
@UnsupportedAppUsage
+ @NonNull
public final String locale;
/** Key phrase text */
@UnsupportedAppUsage
+ @NonNull
public final String text;
/** Users this key phrase has been trained for. countains sound trigger specific user IDs
* derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. */
@UnsupportedAppUsage
+ @NonNull
public final int[] users;
@UnsupportedAppUsage
- public Keyphrase(int id, int recognitionModes, String locale, String text, int[] users) {
+ public Keyphrase(int id, int recognitionModes, @NonNull String locale, @NonNull String text,
+ @Nullable int[] users) {
this.id = id;
this.recognitionModes = recognitionModes;
- this.locale = locale;
- this.text = text;
- this.users = users;
+ this.locale = requireNonNull(locale);
+ this.text = requireNonNull(text);
+ this.users = users != null ? users : new int[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<Keyphrase> CREATOR
@@ -427,13 +447,15 @@
public static class KeyphraseSoundModel extends SoundModel implements Parcelable {
/** Key phrases in this sound model */
@UnsupportedAppUsage
+ @NonNull
public final Keyphrase[] keyphrases; // keyword phrases in model
@UnsupportedAppUsage
public KeyphraseSoundModel(
- UUID uuid, UUID vendorUuid, byte[] data, Keyphrase[] keyphrases) {
+ @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
+ @Nullable Keyphrase[] keyphrases) {
super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
- this.keyphrases = keyphrases;
+ this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
@@ -528,7 +550,8 @@
};
@UnsupportedAppUsage
- public GenericSoundModel(UUID uuid, UUID vendorUuid, byte[] data) {
+ public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data) {
super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
}
@@ -567,6 +590,65 @@
}
}
+ /*****************************************************************************
+ * 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
+ */
+ public final int start;
+
+ /**
+ * end of supported range inclusive
+ */
+ public final int end;
+
+ ModelParamRange(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ 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);
+ }
+
+ @Override
+ @NonNull
+ public ModelParamRange[] newArray(int size) {
+ return new ModelParamRange[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(start);
+ dest.writeInt(end);
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "ModelParamRange [start=" + start + ", end=" + end + "]";
+ }
+ }
+
/**
* Modes for key phrase recognition
*/
@@ -589,6 +671,12 @@
* @hide
*/
public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4;
+ /**
+ * Generic (non-speech) recognition.
+ *
+ * @hide
+ */
+ public static final int RECOGNITION_MODE_GENERIC = 0x8;
/**
* Status codes for {@link RecognitionEvent}
@@ -680,6 +768,7 @@
*
* @hide
*/
+ @NonNull
public final AudioFormat captureFormat;
/**
* Opaque data for use by system applications who know about voice engine internals,
@@ -688,13 +777,14 @@
* @hide
*/
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
/** @hide */
@UnsupportedAppUsage
public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, AudioFormat captureFormat, byte[] data) {
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
this.captureAvailable = captureAvailable;
@@ -702,8 +792,8 @@
this.captureDelayMs = captureDelayMs;
this.capturePreambleMs = capturePreambleMs;
this.triggerInData = triggerInData;
- this.captureFormat = captureFormat;
- this.data = data;
+ this.captureFormat = requireNonNull(captureFormat);
+ this.data = data != null ? data : new byte[0];
}
/**
@@ -906,19 +996,21 @@
/** List of all keyphrases in the sound model for which recognition should be performed with
* options for each keyphrase. */
@UnsupportedAppUsage
+ @NonNull
public final KeyphraseRecognitionExtra keyphrases[];
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
@UnsupportedAppUsage
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- KeyphraseRecognitionExtra[] keyphrases, byte[] data) {
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
- this.keyphrases = keyphrases;
- this.data = data;
+ this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
+ this.data = data != null ? data : new byte[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1067,15 +1159,17 @@
/** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
* be recognized (RecognitionConfig) */
@UnsupportedAppUsage
+ @NonNull
public final ConfidenceLevel[] confidenceLevels;
@UnsupportedAppUsage
public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
- ConfidenceLevel[] confidenceLevels) {
+ @Nullable ConfidenceLevel[] confidenceLevels) {
this.id = id;
this.recognitionModes = recognitionModes;
this.coarseConfidenceLevel = coarseConfidenceLevel;
- this.confidenceLevels = confidenceLevels;
+ this.confidenceLevels =
+ confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
@@ -1158,16 +1252,18 @@
public static class KeyphraseRecognitionEvent extends RecognitionEvent implements Parcelable {
/** Indicates if the key phrase is present in the buffered audio available for capture */
@UnsupportedAppUsage
+ @NonNull
public final KeyphraseRecognitionExtra[] keyphraseExtras;
@UnsupportedAppUsage
public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, AudioFormat captureFormat, byte[] data,
- KeyphraseRecognitionExtra[] keyphraseExtras) {
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+ @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
capturePreambleMs, triggerInData, captureFormat, data);
- this.keyphraseExtras = keyphraseExtras;
+ this.keyphraseExtras =
+ keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
@@ -1284,8 +1380,8 @@
@UnsupportedAppUsage
public GenericRecognitionEvent(int status, int soundModelHandle,
boolean captureAvailable, int captureSession, int captureDelayMs,
- int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat,
- byte[] data) {
+ int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
+ @Nullable byte[] data) {
super(status, soundModelHandle, captureAvailable, captureSession,
captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
data);
@@ -1349,13 +1445,14 @@
/** The updated sound model handle */
public final int soundModelHandle;
/** New sound model data */
+ @NonNull
public final byte[] data;
@UnsupportedAppUsage
- SoundModelEvent(int status, int soundModelHandle, byte[] data) {
+ SoundModelEvent(int status, int soundModelHandle, @Nullable byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
- this.data = data;
+ this.data = data != null ? data : new byte[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<SoundModelEvent> CREATOR
@@ -1439,8 +1536,9 @@
* @hide
*/
public static final int SERVICE_STATE_DISABLED = 1;
-
- /**
+ private static Object mServiceLock = new Object();
+ private static ISoundTriggerMiddlewareService mService;
+ /**
* @return returns current package name.
*/
static String getCurrentOpPackageName() {
@@ -1464,25 +1562,22 @@
* @hide
*/
@UnsupportedAppUsage
- public static int listModules(ArrayList<ModuleProperties> modules) {
- return listModules(getCurrentOpPackageName(), modules);
+ public static int listModules(@NonNull ArrayList<ModuleProperties> modules) {
+ try {
+ SoundTriggerModuleDescriptor[] descs = getService().listModules();
+ modules.clear();
+ modules.ensureCapacity(descs.length);
+ for (SoundTriggerModuleDescriptor desc : descs) {
+ modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
+ }
+ return STATUS_OK;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception caught", e);
+ return STATUS_DEAD_OBJECT;
+ }
}
/**
- * Returns a list of descriptors for all hardware modules loaded.
- * @param opPackageName
- * @param modules A ModuleProperties array where the list will be returned.
- * @return - {@link #STATUS_OK} in case of success
- * - {@link #STATUS_ERROR} in case of unspecified error
- * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
- * - {@link #STATUS_NO_INIT} if the native service cannot be reached
- * - {@link #STATUS_BAD_VALUE} if modules is null
- * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
- */
- private static native int listModules(String opPackageName,
- ArrayList<ModuleProperties> modules);
-
- /**
* Get an interface on a hardware module to control sound models and recognition on
* this module.
* @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory.
@@ -1494,14 +1589,40 @@
* @hide
*/
@UnsupportedAppUsage
- public static SoundTriggerModule attachModule(int moduleId,
- StatusListener listener,
- Handler handler) {
- if (listener == null) {
+ public static @NonNull SoundTriggerModule attachModule(int moduleId,
+ @NonNull StatusListener listener,
+ @Nullable Handler handler) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
+ try {
+ return new SoundTriggerModule(getService(), moduleId, listener, looper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
return null;
}
- SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler);
- return module;
+ }
+
+ private static ISoundTriggerMiddlewareService getService() {
+ synchronized (mServiceLock) {
+ while (true) {
+ IBinder binder = null;
+ try {
+ binder =
+ ServiceManager.getServiceOrThrow(
+ Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
+ binder.linkToDeath(() -> {
+ synchronized (mServiceLock) {
+ mService = null;
+ }
+ }, 0);
+ mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
+ break;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to bind to soundtrigger service", e);
+ }
+ }
+ return mService;
+ }
+
}
/**
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 9113548..7cf5600 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -16,12 +16,23 @@
package android.hardware.soundtrigger;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.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;
+import android.media.soundtrigger_middleware.SoundModel;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-
-import java.lang.ref.WeakReference;
+import android.os.RemoteException;
+import android.util.Log;
/**
* The SoundTriggerModule provides APIs to control sound models and sound detection
@@ -30,39 +41,47 @@
* @hide
*/
public class SoundTriggerModule {
- @UnsupportedAppUsage
- private long mNativeContext;
+ private static final String TAG = "SoundTriggerModule";
- @UnsupportedAppUsage
- private int mId;
- private NativeEventHandlerDelegate mEventHandlerDelegate;
-
- // to be kept in sync with core/jni/android_hardware_SoundTrigger.cpp
private static final int EVENT_RECOGNITION = 1;
private static final int EVENT_SERVICE_DIED = 2;
- private static final int EVENT_SOUNDMODEL = 3;
- private static final int EVENT_SERVICE_STATE_CHANGE = 4;
+ private static final int EVENT_SERVICE_STATE_CHANGE = 3;
+ @UnsupportedAppUsage
+ private int mId;
+ private EventHandlerDelegate mEventHandlerDelegate;
+ private ISoundTriggerModule mService;
- SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
+ SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
+ int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper)
+ throws RemoteException {
mId = moduleId;
- mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler);
- native_setup(SoundTrigger.getCurrentOpPackageName(),
- new WeakReference<SoundTriggerModule>(this));
+ mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
+ mService = service.attach(moduleId, mEventHandlerDelegate);
+ mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
}
- private native void native_setup(String opPackageName, Object moduleThis);
@Override
protected void finalize() {
- native_finalize();
+ detach();
}
- private native void native_finalize();
/**
* Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called
* anymore and associated resources will be released.
- * */
+ * All models must have been unloaded prior to detaching.
+ */
@UnsupportedAppUsage
- public native void detach();
+ public synchronized void detach() {
+ try {
+ if (mService != null) {
+ mService.asBinder().unlinkToDeath(mEventHandlerDelegate, 0);
+ mService.detach();
+ mService = null;
+ }
+ } catch (Exception e) {
+ handleException(e);
+ }
+ }
/**
* Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in
@@ -80,7 +99,26 @@
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int loadSoundModel(SoundTrigger.SoundModel model, int[] soundModelHandle);
+ public synchronized int loadSoundModel(@NonNull SoundTrigger.SoundModel model,
+ @NonNull int[] soundModelHandle) {
+ try {
+ if (model instanceof SoundTrigger.GenericSoundModel) {
+ SoundModel aidlModel = ConversionUtil.api2aidlGenericSoundModel(
+ (SoundTrigger.GenericSoundModel) model);
+ soundModelHandle[0] = mService.loadModel(aidlModel);
+ return SoundTrigger.STATUS_OK;
+ }
+ if (model instanceof SoundTrigger.KeyphraseSoundModel) {
+ PhraseSoundModel aidlModel = ConversionUtil.api2aidlPhraseSoundModel(
+ (SoundTrigger.KeyphraseSoundModel) model);
+ soundModelHandle[0] = mService.loadPhraseModel(aidlModel);
+ return SoundTrigger.STATUS_OK;
+ }
+ return SoundTrigger.STATUS_BAD_VALUE;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition
@@ -95,7 +133,14 @@
* service fails
*/
@UnsupportedAppUsage
- public native int unloadSoundModel(int soundModelHandle);
+ public synchronized int unloadSoundModel(int soundModelHandle) {
+ try {
+ mService.unloadModel(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Start listening to all key phrases in a {@link SoundTrigger.SoundModel}.
@@ -115,7 +160,16 @@
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int startRecognition(int soundModelHandle, SoundTrigger.RecognitionConfig config);
+ public synchronized int startRecognition(int soundModelHandle,
+ SoundTrigger.RecognitionConfig config) {
+ try {
+ mService.startRecognition(soundModelHandle,
+ ConversionUtil.api2aidlRecognitionConfig(config));
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Stop listening to all key phrases in a {@link SoundTrigger.SoundModel}
@@ -131,12 +185,20 @@
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int stopRecognition(int soundModelHandle);
+ public synchronized int stopRecognition(int soundModelHandle) {
+ try {
+ mService.stopRecognition(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Get the current state of a {@link SoundTrigger.SoundModel}.
- * The state will be returned asynchronously as a {@link SoundTrigger#RecognitionEvent}
- * in the callback registered in the {@link SoundTrigger.startRecognition} method.
+ * The state will be returned asynchronously as a {@link SoundTrigger.RecognitionEvent}
+ * in the callback registered in the
+ * {@link SoundTrigger#attachModule(int, SoundTrigger.StatusListener, Handler)} method.
* @param soundModelHandle The sound model handle indicating which model's state to return
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
@@ -148,81 +210,170 @@
* service fails
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
- public native int getModelState(int soundModelHandle);
-
- private class NativeEventHandlerDelegate {
- private final Handler mHandler;
-
- NativeEventHandlerDelegate(final SoundTrigger.StatusListener listener,
- Handler handler) {
- // find the looper for our new event handler
- Looper looper;
- if (handler != null) {
- looper = handler.getLooper();
- } else {
- looper = Looper.getMainLooper();
- }
-
- // construct the event handler with this looper
- if (looper != null) {
- // implement the event handler delegate
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case EVENT_RECOGNITION:
- if (listener != null) {
- listener.onRecognition(
- (SoundTrigger.RecognitionEvent)msg.obj);
- }
- break;
- case EVENT_SOUNDMODEL:
- if (listener != null) {
- listener.onSoundModelUpdate(
- (SoundTrigger.SoundModelEvent)msg.obj);
- }
- break;
- case EVENT_SERVICE_STATE_CHANGE:
- if (listener != null) {
- listener.onServiceStateChange(msg.arg1);
- }
- break;
- case EVENT_SERVICE_DIED:
- if (listener != null) {
- listener.onServiceDied();
- }
- break;
- default:
- break;
- }
- }
- };
- } else {
- mHandler = null;
- }
- }
-
- Handler handler() {
- return mHandler;
+ public synchronized int getModelState(int soundModelHandle) {
+ try {
+ mService.forceRecognitionEvent(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
}
}
- @SuppressWarnings("unused")
- @UnsupportedAppUsage
- private static void postEventFromNative(Object module_ref,
- int what, int arg1, int arg2, Object obj) {
- SoundTriggerModule module = (SoundTriggerModule)((WeakReference)module_ref).get();
- if (module == null) {
- return;
+ /**
+ * 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 SoundTriggerModule#queryParameter(int, int)} should be checked first before calling
+ * this method.
+ *
+ * @param soundModelHandle handle of model to apply parameter
+ * @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 synchronized int setParameter(int soundModelHandle, @ModelParams int modelParam,
+ int value) {
+ try {
+ mService.setModelParameter(soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam), value);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
+
+ /**
+ * 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 SoundTriggerModule#queryParameter(int, int)} should be checked first before
+ * calling this method. Otherwise, an exception can be thrown.
+ *
+ * @param soundModelHandle handle 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 SoundTriggerModule#queryParameter(int, int)}
+ * should
+ * be checked first.
+ * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+ * {@link SoundTriggerModule#queryParameter(int, int)}
+ * should be checked first.
+ */
+ public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ try {
+ return mService.getModelParameter(soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling {@link SoundTriggerModule#setParameter} or
+ * {@link SoundTriggerModule#getParameter}.
+ *
+ * @param soundModelHandle handle of model to get parameter
+ * @param modelParam {@link ModelParams}
+ * @return supported range of parameter, null if not supported
+ */
+ @Nullable
+ public synchronized SoundTrigger.ModelParamRange queryParameter(int soundModelHandle,
+ @ModelParams int modelParam) {
+ try {
+ return ConversionUtil.aidl2apiModelParameterRange(mService.queryModelParameterSupport(
+ soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam)));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ 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;
+
+ EventHandlerDelegate(@NonNull final SoundTrigger.StatusListener listener,
+ @NonNull Looper looper) {
+
+ // construct the event handler with this looper
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RECOGNITION:
+ listener.onRecognition(
+ (SoundTrigger.RecognitionEvent) msg.obj);
+ break;
+ case EVENT_SERVICE_STATE_CHANGE:
+ listener.onServiceStateChange(msg.arg1);
+ break;
+ case EVENT_SERVICE_DIED:
+ listener.onServiceDied();
+ break;
+ default:
+ Log.e(TAG, "Unknown message: " + msg.toString());
+ break;
+ }
+ }
+ };
}
- NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate;
- if (delegate != null) {
- Handler handler = delegate.handler();
- if (handler != null) {
- Message m = handler.obtainMessage(what, arg1, arg2, obj);
- handler.sendMessage(m);
- }
+ @Override
+ public synchronized void onRecognition(int handle, RecognitionEvent event)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+ ConversionUtil.aidl2apiRecognitionEvent(handle, event));
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+ ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, event));
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ public synchronized void onRecognitionAvailabilityChange(boolean available)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_STATE_CHANGE,
+ available ? SoundTrigger.SERVICE_STATE_ENABLED
+ : SoundTrigger.SERVICE_STATE_DISABLED);
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ public synchronized void binderDied() {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+ mHandler.sendMessage(m);
}
}
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index a47f601..62f0196 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -19,6 +19,7 @@
import android.annotation.BinderThread;
import android.annotation.MainThread;
import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -28,6 +29,7 @@
import android.os.ResultReceiver;
import android.util.Log;
import android.view.InputChannel;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -39,6 +41,7 @@
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodSession;
@@ -72,6 +75,7 @@
private static final int DO_SHOW_SOFT_INPUT = 60;
private static final int DO_HIDE_SOFT_INPUT = 70;
private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
+ private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;
final WeakReference<AbstractInputMethodService> mTarget;
final Context mContext;
@@ -225,6 +229,11 @@
case DO_CHANGE_INPUTMETHOD_SUBTYPE:
inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
return;
+ case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
+ SomeArgs args = (SomeArgs) msg.obj;
+ inputMethod.onCreateInlineSuggestionsRequest((ComponentName) args.arg1,
+ (AutofillId) args.arg2, (IInlineSuggestionsRequestCallback) args.arg3);
+ return;
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -267,6 +276,15 @@
@BinderThread
@Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, componentName,
+ autofillId, cb));
+ }
+
+ @BinderThread
+ @Override
public void bindInput(InputBinding binding) {
if (mIsUnbindIssued != null) {
Log.e(TAG, "bindInput must be paired with unbindInput.");
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 43842c5..7da7dc1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -21,6 +21,9 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -34,6 +37,7 @@
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -46,6 +50,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.provider.Settings;
@@ -66,13 +72,17 @@
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.Window;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
@@ -89,11 +99,14 @@
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.Collections;
/**
@@ -434,6 +447,14 @@
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
+ @Nullable
+ private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;
+
+ @Nullable
+ private InlineSuggestionsResponseCallbackImpl mInlineSuggestionsResponseCallback = null;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
onComputeInsets(mTmpInsets);
if (isExtractViewShown()) {
@@ -493,6 +514,18 @@
/**
* {@inheritDoc}
+ * @hide
+ */
+ @MainThread
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
+ handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ }
+
+ /**
+ * {@inheritDoc}
*/
@MainThread
@Override
@@ -668,6 +701,103 @@
}
}
+ // TODO(b/137800469): Add detailed docs explaining the inline suggestions process.
+ /**
+ * Returns an {@link InlineSuggestionsRequest} to be sent to Autofill.
+ *
+ * <p>Should be implemented by subclasses.</p>
+ */
+ public @Nullable InlineSuggestionsRequest onCreateInlineSuggestionsRequest() {
+ return null;
+ }
+
+ /**
+ * Called when Autofill responds back with {@link InlineSuggestionsResponse} containing
+ * inline suggestions.
+ *
+ * <p>Should be implemented by subclasses.</p>
+ *
+ * @param response {@link InlineSuggestionsResponse} passed back by Autofill.
+ * @return Whether the IME will use and render the inline suggestions.
+ */
+ public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
+ return false;
+ }
+
+ /**
+ * Returns whether inline suggestions are enabled on this service.
+ *
+ * TODO(b/137800469): check XML for value.
+ */
+ private boolean isInlineSuggestionsEnabled() {
+ return true;
+ }
+
+ /**
+ * Sends an {@link InlineSuggestionsRequest} obtained from
+ * {@link #onCreateInlineSuggestionsRequest()} to the current Autofill Session through
+ * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
+ */
+ private void makeInlineSuggestionsRequest() {
+ if (mInlineSuggestionsRequestInfo == null) {
+ Log.w(TAG, "makeInlineSuggestionsRequest() called with null requestInfo cache");
+ return;
+ }
+
+ final IInlineSuggestionsRequestCallback requestCallback =
+ mInlineSuggestionsRequestInfo.mCallback;
+ try {
+ final InlineSuggestionsRequest request = onCreateInlineSuggestionsRequest();
+ if (request == null) {
+ Log.w(TAG, "onCreateInlineSuggestionsRequest() returned null request");
+ requestCallback.onInlineSuggestionsUnsupported();
+ } else {
+ if (mInlineSuggestionsResponseCallback == null) {
+ mInlineSuggestionsResponseCallback =
+ new InlineSuggestionsResponseCallbackImpl(this,
+ mInlineSuggestionsRequestInfo.mComponentName,
+ mInlineSuggestionsRequestInfo.mFocusedId);
+ }
+ requestCallback.onInlineSuggestionsRequest(request,
+ mInlineSuggestionsResponseCallback);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
+ }
+ }
+
+ private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName,
+ @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) {
+ mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId,
+ callback);
+
+ if (!isInlineSuggestionsEnabled()) {
+ try {
+ callback.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w(TAG, "handleMakeInlineSuggestionsRequest() RemoteException:" + e);
+ }
+ return;
+ }
+
+ if (!mInputStarted) {
+ Log.w(TAG, "onStartInput() not called yet");
+ return;
+ }
+
+ makeInlineSuggestionsRequest();
+ }
+
+ private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName,
+ @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) {
+ if (!mInlineSuggestionsRequestInfo.validate(componentName)) {
+ Log.d(TAG, "Response component=" + componentName + " differs from request component="
+ + mInlineSuggestionsRequestInfo.mComponentName + ", ignoring response");
+ return;
+ }
+ onInlineSuggestionsResponse(response);
+ }
+
private void notifyImeHidden() {
setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
onPreRenderedWindowVisibilityChanged(false /* setVisible */);
@@ -686,6 +816,63 @@
}
/**
+ * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
+ */
+ private static final class InlineSuggestionsResponseCallbackImpl
+ extends IInlineSuggestionsResponseCallback.Stub {
+ private final WeakReference<InputMethodService> mInputMethodService;
+
+ private final ComponentName mRequestComponentName;
+ private final AutofillId mRequestAutofillId;
+
+ private InlineSuggestionsResponseCallbackImpl(InputMethodService inputMethodService,
+ ComponentName componentName, AutofillId autofillId) {
+ mInputMethodService = new WeakReference<>(inputMethodService);
+ mRequestComponentName = componentName;
+ mRequestAutofillId = autofillId;
+ }
+
+ @Override
+ public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
+ throws RemoteException {
+ final InputMethodService service = mInputMethodService.get();
+ if (service != null) {
+ service.mHandler.sendMessage(obtainMessage(
+ InputMethodService::handleOnInlineSuggestionsResponse, service,
+ mRequestComponentName, mRequestAutofillId, response));
+ }
+ }
+ }
+
+ /**
+ * Information about incoming requests from Autofill Frameworks for inline suggestions.
+ */
+ private static final class InlineSuggestionsRequestInfo {
+ final ComponentName mComponentName;
+ final AutofillId mFocusedId;
+ final IInlineSuggestionsRequestCallback mCallback;
+
+ InlineSuggestionsRequestInfo(ComponentName componentName, AutofillId focusedId,
+ IInlineSuggestionsRequestCallback callback) {
+ this.mComponentName = componentName;
+ this.mFocusedId = focusedId;
+ this.mCallback = callback;
+ }
+
+ /**
+ * Returns whether the cached {@link ComponentName} matches the passed in activity.
+ */
+ public boolean validate(ComponentName componentName) {
+ final boolean result = componentName.equals(mComponentName);
+ if (!result) {
+ Log.d(TAG, "Cached request info ComponentName=" + mComponentName
+ + " differs from received ComponentName=" + componentName);
+ }
+ return result;
+ }
+ }
+
+ /**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
* all of the standard behavior for an input method session.
@@ -1019,6 +1206,16 @@
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
+ mWindow.getWindow().setFitWindowInsetsTypes(WindowInsets.Type.systemBars());
+ mWindow.getWindow().addPrivateFlags(PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND);
+ mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener(
+ (v, insets) -> v.onApplyWindowInsets(
+ new WindowInsets.Builder(insets).setSystemWindowInsets(
+ android.graphics.Insets.of(
+ insets.getSystemWindowInsetLeft(),
+ insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight(),
+ insets.getStableInsetBottom())).build()));
// For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
// by default (but IME developers can opt this out later if they want a new behavior).
mWindow.getWindow().setFlags(
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d95da91..3ed51d7 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -50,6 +50,7 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
+import android.os.SystemClock;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -57,7 +58,6 @@
import android.util.Log;
import android.util.SparseIntArray;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.internal.util.Protocol;
@@ -802,6 +802,7 @@
private INetworkManagementService mNMService;
private INetworkPolicyManager mNPManager;
+ private TetheringManager mTetheringManager;
/**
* Tests if a given integer represents a valid network type.
@@ -2339,6 +2340,28 @@
return getInstanceOrNull();
}
+ private static final int TETHERING_TIMEOUT_MS = 60_000;
+ private final Object mTetheringLock = new Object();
+
+ private TetheringManager getTetheringManager() {
+ synchronized (mTetheringLock) {
+ if (mTetheringManager != null) {
+ return mTetheringManager;
+ }
+ final long before = System.currentTimeMillis();
+ while ((mTetheringManager = (TetheringManager) mContext.getSystemService(
+ Context.TETHERING_SERVICE)) == null) {
+ if (System.currentTimeMillis() - before > TETHERING_TIMEOUT_MS) {
+ Log.e(TAG, "Timeout waiting tethering service not ready yet");
+ throw new IllegalStateException("No tethering service yet");
+ }
+ SystemClock.sleep(100);
+ }
+
+ return mTetheringManager;
+ }
+ }
+
/**
* Get the set of tetherable, available interfaces. This list is limited by
* device configuration and current interface existence.
@@ -2350,11 +2373,7 @@
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
@UnsupportedAppUsage
public String[] getTetherableIfaces() {
- try {
- return mService.getTetherableIfaces();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().getTetherableIfaces();
}
/**
@@ -2367,11 +2386,7 @@
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
@UnsupportedAppUsage
public String[] getTetheredIfaces() {
- try {
- return mService.getTetheredIfaces();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().getTetheredIfaces();
}
/**
@@ -2390,11 +2405,7 @@
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
@UnsupportedAppUsage
public String[] getTetheringErroredIfaces() {
- try {
- return mService.getTetheringErroredIfaces();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().getTetheringErroredIfaces();
}
/**
@@ -2405,11 +2416,7 @@
*/
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public String[] getTetheredDhcpRanges() {
- try {
- return mService.getTetheredDhcpRanges();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().getTetheredDhcpRanges();
}
/**
@@ -2438,13 +2445,7 @@
*/
@UnsupportedAppUsage
public int tether(String iface) {
- try {
- String pkgName = mContext.getOpPackageName();
- Log.i(TAG, "tether caller:" + pkgName);
- return mService.tether(iface, pkgName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().tether(iface);
}
/**
@@ -2467,13 +2468,7 @@
*/
@UnsupportedAppUsage
public int untether(String iface) {
- try {
- String pkgName = mContext.getOpPackageName();
- Log.i(TAG, "untether caller:" + pkgName);
- return mService.untether(iface, pkgName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().untether(iface);
}
/**
@@ -2498,16 +2493,7 @@
@RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS})
public boolean isTetheringSupported() {
- String pkgName = mContext.getOpPackageName();
- try {
- return mService.isTetheringSupported(pkgName);
- } catch (SecurityException e) {
- // This API is not available to this caller, but for backward-compatibility
- // this will just return false instead of throwing.
- return false;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().isTetheringSupported();
}
/**
@@ -2576,14 +2562,7 @@
}
};
- try {
- String pkgName = mContext.getOpPackageName();
- Log.i(TAG, "startTethering caller:" + pkgName);
- mService.startTethering(type, wrappedCallback, showProvisioningUi, pkgName);
- } catch (RemoteException e) {
- Log.e(TAG, "Exception trying to start tethering.", e);
- wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
- }
+ getTetheringManager().startTethering(type, wrappedCallback, showProvisioningUi);
}
/**
@@ -2599,13 +2578,7 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void stopTethering(int type) {
- try {
- String pkgName = mContext.getOpPackageName();
- Log.i(TAG, "stopTethering caller:" + pkgName);
- mService.stopTethering(type, pkgName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ getTetheringManager().stopTethering(type);
}
/**
@@ -2627,10 +2600,6 @@
public void onUpstreamChanged(@Nullable Network network) {}
}
- @GuardedBy("mTetheringEventCallbacks")
- private final ArrayMap<OnTetheringEventCallback, ITetheringEventCallback>
- mTetheringEventCallbacks = new ArrayMap<>();
-
/**
* Start listening to tethering change events. Any new added callback will receive the last
* tethering status right away. If callback is registered when tethering has no upstream or
@@ -2648,27 +2617,7 @@
@NonNull final OnTetheringEventCallback callback) {
Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null.");
- synchronized (mTetheringEventCallbacks) {
- Preconditions.checkArgument(!mTetheringEventCallbacks.containsKey(callback),
- "callback was already registered.");
- ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() {
- @Override
- public void onUpstreamChanged(Network network) throws RemoteException {
- Binder.withCleanCallingIdentity(() ->
- executor.execute(() -> {
- callback.onUpstreamChanged(network);
- }));
- }
- };
- try {
- String pkgName = mContext.getOpPackageName();
- Log.i(TAG, "registerTetheringUpstreamCallback:" + pkgName);
- mService.registerTetheringEventCallback(remoteCallback, pkgName);
- mTetheringEventCallbacks.put(callback, remoteCallback);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ getTetheringManager().registerTetheringEventCallback(executor, callback);
}
/**
@@ -2682,17 +2631,7 @@
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void unregisterTetheringEventCallback(
@NonNull final OnTetheringEventCallback callback) {
- synchronized (mTetheringEventCallbacks) {
- ITetheringEventCallback remoteCallback = mTetheringEventCallbacks.remove(callback);
- Preconditions.checkNotNull(remoteCallback, "callback was not registered.");
- try {
- String pkgName = mContext.getOpPackageName();
- Log.i(TAG, "unregisterTetheringEventCallback:" + pkgName);
- mService.unregisterTetheringEventCallback(remoteCallback, pkgName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ getTetheringManager().unregisterTetheringEventCallback(callback);
}
@@ -2709,11 +2648,7 @@
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
@UnsupportedAppUsage
public String[] getTetherableUsbRegexs() {
- try {
- return mService.getTetherableUsbRegexs();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().getTetherableUsbRegexs();
}
/**
@@ -2729,11 +2664,7 @@
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
@UnsupportedAppUsage
public String[] getTetherableWifiRegexs() {
- try {
- return mService.getTetherableWifiRegexs();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().getTetherableWifiRegexs();
}
/**
@@ -2749,11 +2680,7 @@
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
@UnsupportedAppUsage
public String[] getTetherableBluetoothRegexs() {
- try {
- return mService.getTetherableBluetoothRegexs();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().getTetherableBluetoothRegexs();
}
/**
@@ -2775,13 +2702,7 @@
*/
@UnsupportedAppUsage
public int setUsbTethering(boolean enable) {
- try {
- String pkgName = mContext.getOpPackageName();
- Log.i(TAG, "setUsbTethering caller:" + pkgName);
- return mService.setUsbTethering(enable, pkgName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().setUsbTethering(enable);
}
/** {@hide} */
@@ -2829,11 +2750,7 @@
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
@UnsupportedAppUsage
public int getLastTetherError(String iface) {
- try {
- return mService.getLastTetherError(iface);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getTetheringManager().getLastTetherError(iface);
}
/** @hide */
@@ -2899,14 +2816,8 @@
}
};
- try {
- String pkgName = mContext.getOpPackageName();
- Log.i(TAG, "getLatestTetheringEntitlementResult:" + pkgName);
- mService.getLatestTetheringEntitlementResult(type, wrappedListener,
- showEntitlementUi, pkgName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ getTetheringManager().requestLatestTetheringEntitlementResult(type, wrappedListener,
+ showEntitlementUi);
}
/**
@@ -4331,6 +4242,7 @@
public void factoryReset() {
try {
mService.factoryReset();
+ getTetheringManager().stopAllTethering();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 5f662f9..09c02ef 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -19,7 +19,6 @@
import android.app.PendingIntent;
import android.net.ConnectionInfo;
import android.net.LinkProperties;
-import android.net.ITetheringEventCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
@@ -78,41 +77,31 @@
boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
- int tether(String iface, String callerPkg);
-
- int untether(String iface, String callerPkg);
-
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 29,
+ publicAlternatives = "Use {@code TetheringManager#getLastTetherError} as alternative")
int getLastTetherError(String iface);
- boolean isTetheringSupported(String callerPkg);
-
- void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi,
- String callerPkg);
-
- void stopTethering(int type, String callerPkg);
-
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 29,
+ publicAlternatives = "Use {@code TetheringManager#getTetherableIfaces} as alternative")
String[] getTetherableIfaces();
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 29,
+ publicAlternatives = "Use {@code TetheringManager#getTetheredIfaces} as alternative")
String[] getTetheredIfaces();
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 29,
+ publicAlternatives = "Use {@code TetheringManager#getTetheringErroredIfaces} "
+ + "as Alternative")
String[] getTetheringErroredIfaces();
- String[] getTetheredDhcpRanges();
-
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 29,
+ publicAlternatives = "Use {@code TetheringManager#getTetherableUsbRegexs} as alternative")
String[] getTetherableUsbRegexs();
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 29,
+ publicAlternatives = "Use {@code TetheringManager#getTetherableWifiRegexs} as alternative")
String[] getTetherableWifiRegexs();
- String[] getTetherableBluetoothRegexs();
-
- int setUsbTethering(boolean enable, String callerPkg);
-
@UnsupportedAppUsage(maxTargetSdk = 28)
void reportInetCondition(int networkType, int percentage);
@@ -217,11 +206,5 @@
boolean isCallerCurrentAlwaysOnVpnApp();
boolean isCallerCurrentAlwaysOnVpnLockdownApp();
- void getLatestTetheringEntitlementResult(int type, in ResultReceiver receiver,
- boolean showEntitlementUi, String callerPkg);
-
- void registerTetheringEventCallback(ITetheringEventCallback callback, String callerPkg);
- void unregisterTetheringEventCallback(ITetheringEventCallback callback, String callerPkg);
-
IBinder startOrGetTestNetworkService();
}
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
index c9a999c..1ae44e1 100644
--- a/core/java/android/net/InterfaceConfiguration.java
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -20,8 +20,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.google.android.collect.Sets;
-
import java.util.HashSet;
/**
@@ -32,7 +30,7 @@
public class InterfaceConfiguration implements Parcelable {
private String mHwAddr;
private LinkAddress mAddr;
- private HashSet<String> mFlags = Sets.newHashSet();
+ private HashSet<String> mFlags = new HashSet<>();
// Must be kept in sync with constant in INetd.aidl
private static final String FLAG_UP = "up";
diff --git a/core/java/android/net/InvalidPacketException.java b/core/java/android/net/InvalidPacketException.java
new file mode 100644
index 0000000..909998d
--- /dev/null
+++ b/core/java/android/net/InvalidPacketException.java
@@ -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 android.net;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Thrown when a packet is invalid.
+ * @hide
+ */
+@SystemApi
+public class InvalidPacketException extends Exception {
+ public final int error;
+
+ // Must match SocketKeepalive#ERROR_INVALID_IP_ADDRESS.
+ /** Invalid IP address. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+
+ // Must match SocketKeepalive#ERROR_INVALID_PORT.
+ /** Invalid port number. */
+ public static final int ERROR_INVALID_PORT = -22;
+
+ // Must match SocketKeepalive#ERROR_INVALID_LENGTH.
+ /** Invalid packet length. */
+ public static final int ERROR_INVALID_LENGTH = -23;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_IP_ADDRESS,
+ ERROR_INVALID_PORT,
+ ERROR_INVALID_LENGTH
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * This packet is invalid.
+ * See the error code for details.
+ */
+ public InvalidPacketException(@ErrorCode final int error) {
+ this.error = error;
+ }
+}
diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java
index 9b8b732..2b8b7e6 100644
--- a/core/java/android/net/KeepalivePacketData.java
+++ b/core/java/android/net/KeepalivePacketData.java
@@ -16,13 +16,13 @@
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.net.SocketKeepalive.InvalidPacketException;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.net.util.IpUtils;
import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import java.net.InetAddress;
@@ -33,13 +33,16 @@
*
* @hide
*/
-public class KeepalivePacketData implements Parcelable {
+@SystemApi
+public class KeepalivePacketData {
private static final String TAG = "KeepalivePacketData";
/** Source IP address */
+ @NonNull
public final InetAddress srcAddress;
/** Destination IP address */
+ @NonNull
public final InetAddress dstAddress;
/** Source port */
@@ -51,13 +54,14 @@
/** Packet data. A raw byte string of packet data, not including the link-layer header. */
private final byte[] mPacket;
- protected static final int IPV4_HEADER_LENGTH = 20;
- protected static final int UDP_HEADER_LENGTH = 8;
-
// This should only be constructed via static factory methods, such as
- // nattKeepalivePacket
- protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
- InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
+ // nattKeepalivePacket.
+ /**
+ * A holding class for data necessary to build a keepalive packet.
+ */
+ protected KeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort,
+ @NonNull InetAddress dstAddress, int dstPort,
+ @NonNull byte[] data) throws InvalidPacketException {
this.srcAddress = srcAddress;
this.dstAddress = dstAddress;
this.srcPort = srcPort;
@@ -78,16 +82,12 @@
}
}
+ @NonNull
public byte[] getPacket() {
return mPacket.clone();
}
- /* Parcelable Implementation */
- public int describeContents() {
- return 0;
- }
-
- /** Write to parcel */
+ /** @hide */
public void writeToParcel(Parcel out, int flags) {
out.writeString(srcAddress.getHostAddress());
out.writeString(dstAddress.getHostAddress());
@@ -96,6 +96,7 @@
out.writeByteArray(mPacket);
}
+ /** @hide */
protected KeepalivePacketData(Parcel in) {
srcAddress = NetworkUtils.numericToInetAddress(in.readString());
dstAddress = NetworkUtils.numericToInetAddress(in.readString());
@@ -103,17 +104,4 @@
dstPort = in.readInt();
mPacket = in.createByteArray();
}
-
- /** Parcelable Creator */
- public static final @android.annotation.NonNull Parcelable.Creator<KeepalivePacketData> CREATOR =
- new Parcelable.Creator<KeepalivePacketData>() {
- public KeepalivePacketData createFromParcel(Parcel in) {
- return new KeepalivePacketData(in);
- }
-
- public KeepalivePacketData[] newArray(int size) {
- return new KeepalivePacketData[size];
- }
- };
-
}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 8e18341..ed509cb 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -74,6 +74,8 @@
private static final int MIN_MTU_V6 = 1280;
private static final int MAX_MTU = 10000;
+ private static final int INET6_ADDR_LENGTH = 16;
+
// Stores the properties of links that are "stacked" above this link.
// Indexed by interface name to allow modification and to prevent duplicates being added.
private Hashtable<String, LinkProperties> mStackedLinks = new Hashtable<>();
@@ -227,7 +229,7 @@
/**
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public @NonNull List<String> getAllInterfaceNames() {
List<String> interfaceNames = new ArrayList<>(mStackedLinks.size() + 1);
if (mIfaceName != null) interfaceNames.add(mIfaceName);
@@ -247,7 +249,7 @@
* @return An unmodifiable {@link List} of {@link InetAddress} for this link.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public @NonNull List<InetAddress> getAddresses() {
final List<InetAddress> addresses = new ArrayList<>();
for (LinkAddress linkAddress : mLinkAddresses) {
@@ -342,8 +344,8 @@
* Returns all the addresses on this link and all the links stacked above it.
* @hide
*/
- @UnsupportedAppUsage
- public List<LinkAddress> getAllLinkAddresses() {
+ @SystemApi
+ public @NonNull List<LinkAddress> getAllLinkAddresses() {
List<LinkAddress> addresses = new ArrayList<>(mLinkAddresses);
for (LinkProperties stacked: mStackedLinks.values()) {
addresses.addAll(stacked.getAllLinkAddresses());
@@ -542,6 +544,7 @@
* @return true if the PCSCF server was added, false otherwise.
* @hide
*/
+ @SystemApi
public boolean addPcscfServer(@NonNull InetAddress pcscfServer) {
if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) {
mPcscfs.add(pcscfServer);
@@ -729,7 +732,7 @@
* Returns all the routes on this link and all the links stacked above it.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public @NonNull List<RouteInfo> getAllRoutes() {
List<RouteInfo> routes = new ArrayList<>(mRoutes);
for (LinkProperties stacked: mStackedLinks.values()) {
@@ -1025,7 +1028,7 @@
* @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public boolean hasIpv4DefaultRoute() {
for (RouteInfo r : mRoutes) {
if (r.isIPv4Default()) {
@@ -1082,7 +1085,7 @@
* @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public boolean hasIpv4DnsServer() {
for (InetAddress ia : mDnses) {
if (ia instanceof Inet4Address) {
@@ -1110,7 +1113,7 @@
* @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public boolean hasIpv6DnsServer() {
for (InetAddress ia : mDnses) {
if (ia instanceof Inet6Address) {
@@ -1626,20 +1629,11 @@
dest.writeParcelable(linkAddress, flags);
}
- dest.writeInt(mDnses.size());
- for (InetAddress d : mDnses) {
- dest.writeByteArray(d.getAddress());
- }
- dest.writeInt(mValidatedPrivateDnses.size());
- for (InetAddress d : mValidatedPrivateDnses) {
- dest.writeByteArray(d.getAddress());
- }
+ writeAddresses(dest, mDnses);
+ writeAddresses(dest, mValidatedPrivateDnses);
dest.writeBoolean(mUsePrivateDns);
dest.writeString(mPrivateDnsServerName);
- dest.writeInt(mPcscfs.size());
- for (InetAddress d : mPcscfs) {
- dest.writeByteArray(d.getAddress());
- }
+ writeAddresses(dest, mPcscfs);
dest.writeString(mDomains);
dest.writeInt(mMtu);
dest.writeString(mTcpBufferSizes);
@@ -1662,6 +1656,35 @@
dest.writeBoolean(mWakeOnLanSupported);
}
+ private static void writeAddresses(@NonNull Parcel dest, @NonNull List<InetAddress> list) {
+ dest.writeInt(list.size());
+ for (InetAddress d : list) {
+ writeAddress(dest, d);
+ }
+ }
+
+ private static void writeAddress(@NonNull Parcel dest, @NonNull InetAddress addr) {
+ dest.writeByteArray(addr.getAddress());
+ if (addr instanceof Inet6Address) {
+ final Inet6Address v6Addr = (Inet6Address) addr;
+ final boolean hasScopeId = v6Addr.getScopeId() != 0;
+ dest.writeBoolean(hasScopeId);
+ if (hasScopeId) dest.writeInt(v6Addr.getScopeId());
+ }
+ }
+
+ @NonNull
+ private static InetAddress readAddress(@NonNull Parcel p) throws UnknownHostException {
+ final byte[] addr = p.createByteArray();
+ if (addr.length == INET6_ADDR_LENGTH) {
+ final boolean hasScopeId = p.readBoolean();
+ final int scopeId = hasScopeId ? p.readInt() : 0;
+ return Inet6Address.getByAddress(null /* host */, addr, scopeId);
+ }
+
+ return InetAddress.getByAddress(addr);
+ }
+
/**
* Implement the Parcelable interface.
*/
@@ -1681,14 +1704,13 @@
addressCount = in.readInt();
for (int i = 0; i < addressCount; i++) {
try {
- netProp.addDnsServer(InetAddress.getByAddress(in.createByteArray()));
+ netProp.addDnsServer(readAddress(in));
} catch (UnknownHostException e) { }
}
addressCount = in.readInt();
for (int i = 0; i < addressCount; i++) {
try {
- netProp.addValidatedPrivateDnsServer(
- InetAddress.getByAddress(in.createByteArray()));
+ netProp.addValidatedPrivateDnsServer(readAddress(in));
} catch (UnknownHostException e) { }
}
netProp.setUsePrivateDns(in.readBoolean());
@@ -1696,7 +1718,7 @@
addressCount = in.readInt();
for (int i = 0; i < addressCount; i++) {
try {
- netProp.addPcscfServer(InetAddress.getByAddress(in.createByteArray()));
+ netProp.addPcscfServer(readAddress(in));
} catch (UnknownHostException e) { }
}
netProp.setDomains(in.readString());
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
index a77c244..3fb52f1 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -19,7 +19,6 @@
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
-import android.net.SocketKeepalive.InvalidPacketException;
import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,6 +31,9 @@
/** @hide */
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,
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index adc497a..2992127 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
@@ -461,6 +462,14 @@
return networkCapabilities.hasTransport(transportType);
}
+ /**
+ * @see Builder#setNetworkSpecifier(NetworkSpecifier)
+ */
+ @Nullable
+ public NetworkSpecifier getNetworkSpecifier() {
+ return networkCapabilities.getNetworkSpecifier();
+ }
+
public String toString() {
return "NetworkRequest [ " + type + " id=" + requestId +
(legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") +
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index ec73866..fb224fb 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -147,17 +147,6 @@
}
}
- /**
- * This packet is invalid.
- * See the error code for details.
- * @hide
- */
- public static class InvalidPacketException extends ErrorCodeException {
- public InvalidPacketException(final int error) {
- super(error);
- }
- }
-
@NonNull final IConnectivityManager mService;
@NonNull final Network mNetwork;
@NonNull final ParcelFileDescriptor mPfd;
diff --git a/core/java/android/net/nsd/NsdServiceInfo.java b/core/java/android/net/nsd/NsdServiceInfo.java
index 459b140..0946499 100644
--- a/core/java/android/net/nsd/NsdServiceInfo.java
+++ b/core/java/android/net/nsd/NsdServiceInfo.java
@@ -17,13 +17,13 @@
package android.net.nsd;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Parcelable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
+import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Base64;
import android.util.Log;
-import android.util.ArrayMap;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
diff --git a/core/java/android/nfc/ErrorCodes.java b/core/java/android/nfc/ErrorCodes.java
index 98e31ad..d2c81cd 100644
--- a/core/java/android/nfc/ErrorCodes.java
+++ b/core/java/android/nfc/ErrorCodes.java
@@ -16,7 +16,7 @@
package android.nfc;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* This class defines all the error codes that can be returned by the service
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index dd2c0d4..848b6d5 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -39,4 +39,5 @@
boolean setPreferredService(in ComponentName service);
boolean unsetPreferredService();
boolean supportsAidPrefixRegistration();
+ ApduServiceInfo getPreferredPaymentService(int userHandle);
}
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 5044a86..fe316c4 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -16,7 +16,7 @@
package android.nfc;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index abfa133..935374d1 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -16,9 +16,9 @@
package android.nfc;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.Intent;
import android.net.Uri;
@@ -26,7 +26,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 875d17a..d320f61 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -21,11 +21,11 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.OnActivityPausedListener;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
@@ -164,6 +164,18 @@
"android.nfc.action.TRANSACTION_DETECTED";
/**
+ * Broadcast Action: Intent to notify if the preferred payment service changed.
+ *
+ * <p>This intent will only be sent to the application has requested permission for
+ * {@link android.Manifest.permission#NFC_PREFERRED_PAYMENT_INFO} and if the application
+ * has the necessary access to Secure Element which witnessed the particular event.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PREFERRED_PAYMENT_CHANGED =
+ "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
+
+ /**
* Broadcast to only the activity that handles ACTION_TAG_DISCOVERED
* @hide
*/
@@ -231,6 +243,17 @@
*/
public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
+ /**
+ * Mandatory String extra field in {@link #ACTION_PREFERRED_PAYMENT_CHANGED}
+ * Indicates the condition when trigger this event.
+ */
+ public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON =
+ "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
+
+ public static final int PREFERRED_PAYMENT_LOADED = 1;
+ public static final int PREFERRED_PAYMENT_CHANGED = 2;
+ public static final int PREFERRED_PAYMENT_UPDATED = 3;
+
public static final int STATE_OFF = 1;
public static final int STATE_TURNING_ON = 2;
public static final int STATE_ON = 3;
@@ -1410,7 +1433,7 @@
/**
* Enable foreground dispatch to the given Activity.
*
- * <p>This will give give priority to the foreground activity when
+ * <p>This will give priority to the foreground activity when
* dispatching a discovered {@link Tag} to an application.
*
* <p>If any IntentFilters are provided to this method they are used to match dispatch Intents
diff --git a/core/java/android/nfc/NfcManager.java b/core/java/android/nfc/NfcManager.java
index 030066e..644e312 100644
--- a/core/java/android/nfc/NfcManager.java
+++ b/core/java/android/nfc/NfcManager.java
@@ -17,7 +17,7 @@
package android.nfc;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index 5a4c465..8bb2df0 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -16,7 +16,7 @@
package android.nfc;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
index 77b5552..c4f5e0b 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -16,18 +16,18 @@
package android.nfc.cardemulation;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import android.compat.annotation.UnsupportedAppUsage;
+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 android.annotation.UnsupportedAppUsage;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
/**
* The AidGroup class represents a group of Application Identifiers (AIDs).
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index d9000e4..8da9db1 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -16,7 +16,7 @@
package android.nfc.cardemulation;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index aa93611..f1c74a6 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -17,6 +17,7 @@
package android.nfc.cardemulation;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -657,6 +658,109 @@
}
/**
+ * Retrieves the registered AIDs for the preferred payment service.
+ *
+ * @return The list of AIDs registered for this category, or null if it couldn't be found.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Nullable
+ public List<String> getAidsForPreferredPaymentService() {
+ try {
+ ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUserId());
+ return (serviceInfo != null ? serviceInfo.getAids() : null);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ ApduServiceInfo serviceInfo =
+ sService.getPreferredPaymentService(mContext.getUserId());
+ return (serviceInfo != null ? serviceInfo.getAids() : null);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Retrieves the route destination for the preferred payment service.
+ *
+ * @return The route destination secure element name of the preferred payment service.
+ * HCE payment: "Host"
+ * OffHost payment: prefix SIM or prefix eSE string.
+ * "OffHost" if the payment service does not specify secure element
+ * name.
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Nullable
+ public String getRouteDestinationForPreferredPaymentService() {
+ try {
+ ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUserId());
+ if (serviceInfo != null) {
+ if (!serviceInfo.isOnHost()) {
+ return serviceInfo.getOffHostSecureElement() == null ?
+ "OffHost" : serviceInfo.getOffHostSecureElement();
+ }
+ return "Host";
+ }
+ return null;
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ ApduServiceInfo serviceInfo =
+ sService.getPreferredPaymentService(mContext.getUserId());
+ if (serviceInfo != null) {
+ if (!serviceInfo.isOnHost()) {
+ return serviceInfo.getOffHostSecureElement() == null ?
+ "Offhost" : serviceInfo.getOffHostSecureElement();
+ }
+ return "Host";
+ }
+ return null;
+
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Returns a user-visible description of the preferred payment service.
+ *
+ * @return the preferred payment service description
+ */
+ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Nullable
+ public String getDescriptionForPreferredPaymentService() {
+ try {
+ ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUserId());
+ return (serviceInfo != null ? serviceInfo.getDescription() : null);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ ApduServiceInfo serviceInfo =
+ sService.getPreferredPaymentService(mContext.getUserId());
+ return (serviceInfo != null ? serviceInfo.getDescription() : null);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ /**
* @hide
*/
public boolean setDefaultServiceForCategory(ComponentName service, String category) {
diff --git a/core/java/android/os/AsyncResult.java b/core/java/android/os/AsyncResult.java
index 58a2701..e80528b 100644
--- a/core/java/android/os/AsyncResult.java
+++ b/core/java/android/os/AsyncResult.java
@@ -16,8 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Message;
+import android.compat.annotation.UnsupportedAppUsage;
/** @hide */
public class AsyncResult
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index b37e176..e1dabd3 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -18,8 +18,8 @@
import android.annotation.MainThread;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.ArrayDeque;
import java.util.concurrent.Callable;
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 9d9c683..6453af8 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import android.util.Log;
import android.util.MathUtils;
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 5ced86c..12ec0a0 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -21,7 +21,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.Context;
import android.content.Intent;
import android.hardware.health.V1_0.Constants;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9fed269..fbe6a50 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -17,13 +17,17 @@
package android.os;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
+import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.job.JobParameters;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.os.BatteryStatsManager.WifiState;
+import android.os.BatteryStatsManager.WifiSupplState;
import android.server.ServerProtoEnums;
import android.service.batterystats.BatteryStatsServiceDumpHistoryProto;
import android.service.batterystats.BatteryStatsServiceDumpProto;
@@ -2422,7 +2426,7 @@
public static final int DATA_CONNECTION_OUT_OF_SERVICE = 0;
public static final int DATA_CONNECTION_EMERGENCY_SERVICE =
- TelephonyManager.MAX_NETWORK_TYPE + 1;
+ TelephonyManager.getAllNetworkTypes().length + 1;
public static final int DATA_CONNECTION_OTHER = DATA_CONNECTION_EMERGENCY_SERVICE + 1;
@@ -2458,41 +2462,6 @@
*/
public abstract Timer getPhoneDataConnectionTimer(int dataType);
- public static final int WIFI_SUPPL_STATE_INVALID = 0;
- public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
- public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
- public static final int WIFI_SUPPL_STATE_INACTIVE = 3;
- public static final int WIFI_SUPPL_STATE_SCANNING = 4;
- public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5;
- public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6;
- public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7;
- public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8;
- public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9;
- public static final int WIFI_SUPPL_STATE_COMPLETED = 10;
- public static final int WIFI_SUPPL_STATE_DORMANT = 11;
- public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12;
-
- public static final int NUM_WIFI_SUPPL_STATES = WIFI_SUPPL_STATE_UNINITIALIZED+1;
-
- /** @hide */
- @IntDef(flag = true, prefix = { "WIFI_SUPPL_STATE_" }, value = {
- WIFI_SUPPL_STATE_INVALID,
- WIFI_SUPPL_STATE_DISCONNECTED,
- WIFI_SUPPL_STATE_INTERFACE_DISABLED,
- WIFI_SUPPL_STATE_INACTIVE,
- WIFI_SUPPL_STATE_SCANNING,
- WIFI_SUPPL_STATE_AUTHENTICATING,
- WIFI_SUPPL_STATE_ASSOCIATING,
- WIFI_SUPPL_STATE_ASSOCIATED,
- WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE,
- WIFI_SUPPL_STATE_GROUP_HANDSHAKE,
- WIFI_SUPPL_STATE_COMPLETED,
- WIFI_SUPPL_STATE_DORMANT,
- WIFI_SUPPL_STATE_UNINITIALIZED,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface WifiSupplState {}
-
static final String[] WIFI_SUPPL_STATE_NAMES = {
"invalid", "disconn", "disabled", "inactive", "scanning",
"authenticating", "associating", "associated", "4-way-handshake",
@@ -2635,31 +2604,6 @@
@UnsupportedAppUsage
public abstract long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which);
- public static final int WIFI_STATE_OFF = 0;
- public static final int WIFI_STATE_OFF_SCANNING = 1;
- public static final int WIFI_STATE_ON_NO_NETWORKS = 2;
- public static final int WIFI_STATE_ON_DISCONNECTED = 3;
- public static final int WIFI_STATE_ON_CONNECTED_STA = 4;
- public static final int WIFI_STATE_ON_CONNECTED_P2P = 5;
- public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6;
- public static final int WIFI_STATE_SOFT_AP = 7;
-
- public static final int NUM_WIFI_STATES = WIFI_STATE_SOFT_AP + 1;
-
- /** @hide */
- @IntDef(flag = true, prefix = { "WIFI_STATE_" }, value = {
- WIFI_STATE_OFF,
- WIFI_STATE_OFF_SCANNING,
- WIFI_STATE_ON_NO_NETWORKS,
- WIFI_STATE_ON_DISCONNECTED,
- WIFI_STATE_ON_CONNECTED_STA,
- WIFI_STATE_ON_CONNECTED_P2P,
- WIFI_STATE_ON_CONNECTED_STA_P2P,
- WIFI_STATE_SOFT_AP
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface WifiState {}
-
static final String[] WIFI_STATE_NAMES = {
"off", "scanning", "no_net", "disconn",
"sta", "p2p", "sta_p2p", "soft_ap"
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index e5650ae..0545666 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +29,9 @@
import com.android.internal.app.IBatteryStats;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* This class provides an API surface for internal system components to report events that are
* needed for battery usage/estimation and battery blaming for apps.
@@ -39,6 +43,116 @@
@SystemApi
@SystemService(Context.BATTERY_STATS_SERVICE)
public class BatteryStatsManager {
+ /**
+ * Wifi states.
+ *
+ * @see #noteWifiState(int, String)
+ */
+ /**
+ * Wifi fully off.
+ */
+ public static final int WIFI_STATE_OFF = 0;
+ /**
+ * Wifi connectivity off, but scanning enabled.
+ */
+ public static final int WIFI_STATE_OFF_SCANNING = 1;
+ /**
+ * Wifi on, but no saved infrastructure networks to connect to.
+ */
+ public static final int WIFI_STATE_ON_NO_NETWORKS = 2;
+ /**
+ * Wifi on, but not connected to any infrastructure networks.
+ */
+ public static final int WIFI_STATE_ON_DISCONNECTED = 3;
+ /**
+ * Wifi on and connected to a infrastructure network.
+ */
+ public static final int WIFI_STATE_ON_CONNECTED_STA = 4;
+ /**
+ * Wifi on and connected to a P2P device, but no infrastructure connection to a network.
+ */
+ public static final int WIFI_STATE_ON_CONNECTED_P2P = 5;
+ /**
+ * Wifi on and connected to both a P2P device and infrastructure connection to a network.
+ */
+ public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6;
+ /**
+ * SoftAp/Hotspot turned on.
+ */
+ public static final int WIFI_STATE_SOFT_AP = 7;
+
+ /** @hide */
+ public static final int NUM_WIFI_STATES = WIFI_STATE_SOFT_AP + 1;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "WIFI_STATE_" }, value = {
+ WIFI_STATE_OFF,
+ WIFI_STATE_OFF_SCANNING,
+ WIFI_STATE_ON_NO_NETWORKS,
+ WIFI_STATE_ON_DISCONNECTED,
+ WIFI_STATE_ON_CONNECTED_STA,
+ WIFI_STATE_ON_CONNECTED_P2P,
+ WIFI_STATE_ON_CONNECTED_STA_P2P,
+ WIFI_STATE_SOFT_AP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiState {}
+
+ /**
+ * Wifi supplicant daemon states.
+ *
+ * @see android.net.wifi.SupplicantState for detailed description of states.
+ * @see #noteWifiSupplicantStateChanged(int)
+ */
+ /** @see android.net.wifi.SupplicantState#INVALID */
+ public static final int WIFI_SUPPL_STATE_INVALID = 0;
+ /** @see android.net.wifi.SupplicantState#DISCONNECTED*/
+ public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
+ /** @see android.net.wifi.SupplicantState#INTERFACE_DISABLED */
+ public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
+ /** @see android.net.wifi.SupplicantState#INACTIVE*/
+ public static final int WIFI_SUPPL_STATE_INACTIVE = 3;
+ /** @see android.net.wifi.SupplicantState#SCANNING*/
+ public static final int WIFI_SUPPL_STATE_SCANNING = 4;
+ /** @see android.net.wifi.SupplicantState#AUTHENTICATING */
+ public static final int WIFI_SUPPL_STATE_AUTHENTICATING = 5;
+ /** @see android.net.wifi.SupplicantState#ASSOCIATING */
+ public static final int WIFI_SUPPL_STATE_ASSOCIATING = 6;
+ /** @see android.net.wifi.SupplicantState#ASSOCIATED */
+ public static final int WIFI_SUPPL_STATE_ASSOCIATED = 7;
+ /** @see android.net.wifi.SupplicantState#FOUR_WAY_HANDSHAKE */
+ public static final int WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE = 8;
+ /** @see android.net.wifi.SupplicantState#GROUP_HANDSHAKE */
+ public static final int WIFI_SUPPL_STATE_GROUP_HANDSHAKE = 9;
+ /** @see android.net.wifi.SupplicantState#COMPLETED */
+ public static final int WIFI_SUPPL_STATE_COMPLETED = 10;
+ /** @see android.net.wifi.SupplicantState#DORMANT */
+ public static final int WIFI_SUPPL_STATE_DORMANT = 11;
+ /** @see android.net.wifi.SupplicantState#UNINITIALIZED */
+ public static final int WIFI_SUPPL_STATE_UNINITIALIZED = 12;
+
+ /** @hide */
+ public static final int NUM_WIFI_SUPPL_STATES = WIFI_SUPPL_STATE_UNINITIALIZED + 1;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "WIFI_SUPPL_STATE_" }, value = {
+ WIFI_SUPPL_STATE_INVALID,
+ WIFI_SUPPL_STATE_DISCONNECTED,
+ WIFI_SUPPL_STATE_INTERFACE_DISABLED,
+ WIFI_SUPPL_STATE_INACTIVE,
+ WIFI_SUPPL_STATE_SCANNING,
+ WIFI_SUPPL_STATE_AUTHENTICATING,
+ WIFI_SUPPL_STATE_ASSOCIATING,
+ WIFI_SUPPL_STATE_ASSOCIATED,
+ WIFI_SUPPL_STATE_FOUR_WAY_HANDSHAKE,
+ WIFI_SUPPL_STATE_GROUP_HANDSHAKE,
+ WIFI_SUPPL_STATE_COMPLETED,
+ WIFI_SUPPL_STATE_DORMANT,
+ WIFI_SUPPL_STATE_UNINITIALIZED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiSupplState {}
+
private final IBatteryStats mBatteryStats;
/** @hide */
@@ -91,7 +205,7 @@
* @param accessPoint SSID of the network if wifi is connected to STA, else null.
*/
@RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
- public void noteWifiState(@BatteryStats.WifiState int newWifiState,
+ public void noteWifiState(@WifiState int newWifiState,
@Nullable String accessPoint) {
try {
mBatteryStats.noteWifiState(newWifiState, accessPoint);
@@ -224,7 +338,7 @@
* authentication failure.
*/
@RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
- public void noteWifiSupplicantStateChanged(@BatteryStats.WifiSupplState int newSupplState,
+ public void noteWifiSupplicantStateChanged(@WifiSupplState int newSupplState,
boolean failedAuth) {
try {
mBatteryStats.noteWifiSupplicantStateChanged(newSupplState, failedAuth);
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index a9c5a91..a90ab85 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -19,8 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index ee95fce..b0c2546 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -479,8 +479,14 @@
// For now, avoid spamming the log by disabling after we've logged
// about this interface at least once
mWarnOnBlocking = false;
- Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
- new Throwable());
+ if (Build.IS_USERDEBUG) {
+ // Log this as a WTF on userdebug builds.
+ Log.wtf(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
+ new Throwable());
+ } else {
+ Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
+ new Throwable());
+ }
}
final boolean tracingEnabled = Binder.isTracingEnabled();
diff --git a/core/java/android/os/Broadcaster.java b/core/java/android/os/Broadcaster.java
index 6ac7f1a..d1a953f 100644
--- a/core/java/android/os/Broadcaster.java
+++ b/core/java/android/os/Broadcaster.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/** @hide */
public class Broadcaster
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 6eaea99..a291734 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -22,9 +22,9 @@
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.sysprop.TelephonyProperties;
import android.text.TextUtils;
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c5b4222..f8f8bf7 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -17,7 +17,7 @@
package android.os;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import android.util.Size;
import android.util.SizeF;
diff --git a/core/java/android/os/CancellationSignal.java b/core/java/android/os/CancellationSignal.java
index 99fb998..260f7ad 100644
--- a/core/java/android/os/CancellationSignal.java
+++ b/core/java/android/os/CancellationSignal.java
@@ -16,9 +16,7 @@
package android.os;
-import android.os.ICancellationSignal;
-
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Provides the ability to cancel an operation in progress.
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 5634929..1c4ef382 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.AppGlobals;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.Log;
@@ -1938,8 +1938,14 @@
public static final int MEMINFO_PAGE_TABLES = 13;
/** @hide */
public static final int MEMINFO_KERNEL_STACK = 14;
+ /**
+ * Note: MEMINFO_KRECLAIMABLE includes MEMINFO_SLAB_RECLAIMABLE (see KReclaimable field
+ * description in kernel documentation).
+ * @hide
+ */
+ public static final int MEMINFO_KRECLAIMABLE = 15;
/** @hide */
- public static final int MEMINFO_COUNT = 15;
+ public static final int MEMINFO_COUNT = 16;
/**
* Retrieves /proc/meminfo. outSizes is filled with fields
@@ -2561,4 +2567,27 @@
* @hide
*/
public static native long getZramFreeKb();
+
+ /**
+ * Return memory size in kilobytes allocated for ION heaps.
+ *
+ * @hide
+ */
+ public static native long getIonHeapsSizeKb();
+
+ /**
+ * Return memory size in kilobytes allocated for ION pools.
+ *
+ * @hide
+ */
+ public static native long getIonPoolsSizeKb();
+
+ /**
+ * Return ION memory mapped by processes in kB.
+ * Notes:
+ * * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process.
+ *
+ * @hide
+ */
+ public static native long getIonMappedSizeKb();
}
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index b7cccc6..18ba5a8 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -24,7 +24,7 @@
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.util.Log;
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index a92237b..44f12a6 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -20,10 +20,10 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -34,6 +34,8 @@
import android.util.Log;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.LinkedList;
/**
@@ -528,6 +530,22 @@
}
/**
+ * Return locations where media files (such as ringtones, notification
+ * sounds, or alarm sounds) may be located on internal storage. These are
+ * typically indexed under {@link MediaStore#VOLUME_INTERNAL}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static @NonNull Collection<File> getInternalMediaDirectories() {
+ final ArrayList<File> res = new ArrayList<>();
+ res.add(new File(Environment.getRootDirectory(), "media"));
+ res.add(new File(Environment.getOemDirectory(), "media"));
+ res.add(new File(Environment.getProductDirectory(), "media"));
+ return res;
+ }
+
+ /**
* Return the primary shared/external storage directory. This directory may
* not currently be accessible if it has been mounted by the user on their
* computer, has been removed from the device, or some other problem has
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
index 5b715c0..ca303d9 100644
--- a/core/java/android/os/FileObserver.java
+++ b/core/java/android/os/FileObserver.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.util.Log;
import java.io.File;
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 2ac3def..a47fbba 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -40,7 +40,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.provider.DocumentsContract.Document;
import android.system.ErrnoException;
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 928df3c..af776d1 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
import android.util.Printer;
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index 09afdc7..7c42c36 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -18,7 +18,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import libcore.util.NativeAllocationRegistry;
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index 9786f16..fb36e6f 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.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 dalvik.annotation.optimization.FastNative;
diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java
index 72ec958..756202e 100644
--- a/core/java/android/os/HwRemoteBinder.java
+++ b/core/java/android/os/HwRemoteBinder.java
@@ -16,7 +16,8 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import libcore.util.NativeAllocationRegistry;
/** @hide */
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 12bce8a..f336fda 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.FileDescriptor;
@@ -182,6 +182,14 @@
public static final int MAX_IPC_SIZE = 64 * 1024;
/**
+ * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction
+ * buffer limit.
+ */
+ static int getSuggestedMaxIpcSizeBytes() {
+ return MAX_IPC_SIZE;
+ }
+
+ /**
* Get the canonical name of the interface supported by this binder.
*/
public @Nullable String getInterfaceDescriptor() throws RemoteException;
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 1456ff7..6b881fe 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,6 +24,7 @@
{
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);
void cancelVibrate(IBinder token);
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 0de09ef..0bf387e 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.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 android.icu.util.ULocale;
import com.android.internal.annotations.GuardedBy;
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index df16ffe..b478dbe 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
import android.util.Printer;
import android.util.Slog;
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
index 5a1e3d4..f84f9f05 100644
--- a/core/java/android/os/MemoryFile.java
+++ b/core/java/android/os/MemoryFile.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
import java.io.FileDescriptor;
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 9d101e5..c62df40 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index b5f4bc6..a72795d 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -19,8 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
-import android.os.MessageQueueProto;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
import android.util.Printer;
import android.util.SparseArray;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 9eb6445..c9ebed3 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -19,8 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1815,8 +1815,12 @@
p.writeToParcel(this, parcelableFlags);
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * Flatten the name of the class of the Parcelable into this Parcel.
+ *
+ * @param p The Parcelable object to be written.
+ * @see #readParcelableCreator
+ */
public final void writeParcelableCreator(@NonNull Parcelable p) {
String name = p.getClass().getName();
writeString(name);
@@ -3011,8 +3015,19 @@
return (T) creator.createFromParcel(this);
}
- /** @hide */
- @UnsupportedAppUsage
+ /**
+ * Read and return a Parcelable.Creator from the parcel. The given class loader will be used to
+ * load the {@link Parcelable.Creator}. If it is null, the default class loader will be used.
+ *
+ * @param loader A ClassLoader from which to instantiate the {@link Parcelable.Creator}
+ * object, or null for the default class loader.
+ * @return the previously written {@link Parcelable.Creator}, or null if a null Creator was
+ * written.
+ * @throws BadParcelableException Throws BadParcelableException if there was an error trying to
+ * read the {@link Parcelable.Creator}.
+ *
+ * @see #writeParcelableCreator
+ */
@Nullable
public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
String name = readString();
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index fdb44e7..983053b 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -32,7 +32,7 @@
import static android.system.OsConstants.S_IWOTH;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentResolver;
diff --git a/core/java/android/os/ParcelUuid.java b/core/java/android/os/ParcelUuid.java
index cc50c89..0b52c75 100644
--- a/core/java/android/os/ParcelUuid.java
+++ b/core/java/android/os/ParcelUuid.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.UUID;
diff --git a/core/java/android/os/ParcelableParcel.java b/core/java/android/os/ParcelableParcel.java
index 61f3968..38d980e 100644
--- a/core/java/android/os/ParcelableParcel.java
+++ b/core/java/android/os/ParcelableParcel.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.MathUtils;
/**
diff --git a/core/java/android/os/PerformanceCollector.java b/core/java/android/os/PerformanceCollector.java
index 33c86b8..27de48d 100644
--- a/core/java/android/os/PerformanceCollector.java
+++ b/core/java/android/os/PerformanceCollector.java
@@ -17,7 +17,8 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.ArrayList;
/**
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 725e0fb..82b04a6 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -20,12 +20,13 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.service.dreams.Sandman;
import android.util.ArrayMap;
@@ -44,70 +45,10 @@
* <p>
* <b>Device battery life will be significantly affected by the use of this API.</b>
* Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
- * possible, and be sure to release them as soon as possible.
- * </p><p>
- * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
- * This will create a {@link PowerManager.WakeLock} object. You can then use methods
- * on the wake lock object to control the power state of the device.
- * </p><p>
- * In practice it's quite simple:
- * {@samplecode
- * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
- * wl.acquire();
- * ..screen will stay on during this section..
- * wl.release();
- * }
- * </p><p>
- * The following wake lock levels are defined, with varying effects on system power.
- * <i>These levels are mutually exclusive - you may only specify one of them.</i>
+ * possible, and be sure to release them as soon as possible. In most cases,
+ * you'll want to use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
*
- * <table>
- * <tr><th>Flag Value</th>
- * <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
- *
- * <tr><td>{@link #PARTIAL_WAKE_LOCK}</td>
- * <td>On*</td> <td>Off</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #SCREEN_DIM_WAKE_LOCK}</td>
- * <td>On</td> <td>Dim</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #SCREEN_BRIGHT_WAKE_LOCK}</td>
- * <td>On</td> <td>Bright</td> <td>Off</td>
- * </tr>
- *
- * <tr><td>{@link #FULL_WAKE_LOCK}</td>
- * <td>On</td> <td>Bright</td> <td>Bright</td>
- * </tr>
- * </table>
- * </p><p>
- * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any
- * display timeouts or the state of the screen and even after the user presses the power button.
- * In all other wake locks, the CPU will run, but the user can still put the device to sleep
- * using the power button.</i>
- * </p><p>
- * In addition, you can add two more flags, which affect behavior of the screen only.
- * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i></p>
- *
- * <table>
- * <tr><th>Flag Value</th> <th>Description</th></tr>
- *
- * <tr><td>{@link #ACQUIRE_CAUSES_WAKEUP}</td>
- * <td>Normal wake locks don't actually turn on the illumination. Instead, they cause
- * the illumination to remain on once it turns on (e.g. from user activity). This flag
- * will force the screen and/or keyboard to turn on immediately, when the WakeLock is
- * acquired. A typical use would be for notifications which are important for the user to
- * see immediately.</td>
- * </tr>
- *
- * <tr><td>{@link #ON_AFTER_RELEASE}</td>
- * <td>If this flag is set, the user activity timer will be reset when the WakeLock is
- * released, causing the illumination to remain on a bit longer. This can be used to
- * reduce flicker if you are cycling between wake lock conditions.</td>
- * </tr>
- * </table>
* <p>
* Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
* permission in an {@code <uses-permission>} element of the application's manifest.
@@ -612,6 +553,13 @@
public static final String REBOOT_SAFE_MODE = "safemode";
/**
+ * The 'reason' value used for rebooting userspace.
+ * @hide
+ */
+ @SystemApi
+ public static final String REBOOT_USERSPACE = "userspace";
+
+ /**
* The 'reason' value used when rebooting the device without turning on the screen.
* @hide
*/
@@ -931,7 +879,8 @@
* {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK}
* and {@link #SCREEN_BRIGHT_WAKE_LOCK}. Exactly one wake lock level must be
* specified as part of the {@code levelAndFlags} parameter.
- * </p><p>
+ * </p>
+ * <p>
* The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP}
* and {@link #ON_AFTER_RELEASE}. Multiple flags can be combined as part of the
* {@code levelAndFlags} parameters.
@@ -1385,6 +1334,14 @@
}
/**
+ * Returns {@code true} if this device supports rebooting userspace.
+ */
+ // TODO(b/138605180): add link to documentation once it's ready.
+ public boolean isRebootingUserspaceSupported() {
+ return SystemProperties.getBoolean("ro.init.userspace_reboot.is_supported", false);
+ }
+
+ /**
* Reboot the device. Will not return if the reboot is successful.
* <p>
* Requires the {@link android.Manifest.permission#REBOOT} permission.
@@ -1392,8 +1349,14 @@
*
* @param reason code to pass to the kernel (e.g., "recovery") to
* request special boot modes, or null.
+ * @throws UnsupportedOperationException if userspace reboot was requested on a device that
+ * doesn't support it.
*/
- public void reboot(String reason) {
+ public void reboot(@Nullable String reason) {
+ if (REBOOT_USERSPACE.equals(reason) && !isRebootingUserspaceSupported()) {
+ throw new UnsupportedOperationException(
+ "Attempted userspace reboot on a device that doesn't support it");
+ }
try {
mService.reboot(false, reason, true);
} catch (RemoteException e) {
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 6408f61..94623bc 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -19,13 +19,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.system.Os;
import android.system.OsConstants;
+import android.util.Pair;
import android.webkit.WebViewZygote;
import dalvik.system.VMRuntime;
+import java.util.Map;
+
/**
* Tools for managing OS processes.
*/
@@ -519,11 +522,14 @@
* @param invokeWith null-ok the command to invoke with.
* @param packageName null-ok the name of the package this process belongs to.
* @param isTopApp whether the process starts for high priority application.
- *
+ * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+ * started.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
- *
+ *
* {@hide}
*/
public static ProcessStartResult start(@NonNull final String processClass,
@@ -539,11 +545,15 @@
@Nullable String invokeWith,
@Nullable String packageName,
boolean isTopApp,
+ @Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] zygoteArgs) {
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ true, isTopApp, zygoteArgs);
+ /*useUsapPool=*/ true, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, zygoteArgs);
}
/** @hide */
@@ -559,11 +569,15 @@
@Nullable String appDataDir,
@Nullable String invokeWith,
@Nullable String packageName,
+ @Nullable long[] disabledCompatChanges,
@Nullable String[] zygoteArgs) {
+ // Webview zygote can't access app private data files, so doesn't need to know its data
+ // info.
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ false, /*isTopApp=*/ false, zygoteArgs);
+ /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges,
+ /* pkgDataInfoMap */ null, zygoteArgs);
}
/**
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index e3f6e12..1901820 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -22,8 +22,8 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
diff --git a/core/java/android/os/Registrant.java b/core/java/android/os/Registrant.java
index 572b975..d6afd04 100644
--- a/core/java/android/os/Registrant.java
+++ b/core/java/android/os/Registrant.java
@@ -16,9 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Handler;
-import android.os.Message;
+import android.compat.annotation.UnsupportedAppUsage;
import java.lang.ref.WeakReference;
diff --git a/core/java/android/os/RegistrantList.java b/core/java/android/os/RegistrantList.java
index 9c017df..98f949b 100644
--- a/core/java/android/os/RegistrantList.java
+++ b/core/java/android/os/RegistrantList.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.ArrayList;
diff --git a/core/java/android/os/RemoteCallback.java b/core/java/android/os/RemoteCallback.java
index da58d0f..373060f 100644
--- a/core/java/android/os/RemoteCallback.java
+++ b/core/java/android/os/RemoteCallback.java
@@ -20,8 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index 6165146..df4ade0 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import android.util.Slog;
diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java
index 34809e7..f64a811 100644
--- a/core/java/android/os/SELinux.java
+++ b/core/java/android/os/SELinux.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Slog;
import java.io.File;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index bf9225a..74ff310 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import android.util.Log;
@@ -140,7 +140,7 @@
/**
* Returns a reference to a service with the given name, or throws
- * {@link NullPointerException} if none is found.
+ * {@link ServiceNotFoundException} if none is found.
*
* @hide
*/
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 124b6c6..39ddcb2 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Native implementation of the service manager. Most clients will only
@@ -90,6 +90,15 @@
throw new RemoteException();
}
+ public void registerClientCallback(String name, IBinder service, IClientCallback cb)
+ throws RemoteException {
+ throw new RemoteException();
+ }
+
+ public void tryUnregisterService(String name, IBinder service) throws RemoteException {
+ throw new RemoteException();
+ }
+
/**
* Same as mServiceManager but used by apps.
*
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index 57a8801..26d9c7d 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index f3a6869..0be3d68 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Slog;
import com.android.internal.util.FastPrintWriter;
diff --git a/core/java/android/os/StatFs.java b/core/java/android/os/StatFs.java
index 881d0b4..6d1a116 100644
--- a/core/java/android/os/StatFs.java
+++ b/core/java/android/os/StatFs.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStatVfs;
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 0bf634e..3faaff7 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -20,10 +20,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.IActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index c104abd..fd68c2b 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -17,8 +17,8 @@
package android.os;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
import android.app.IAlarmManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.location.ILocationManager;
import android.location.LocationTime;
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index e7a271c..c5e5cc4 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.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.util.Log;
import android.util.MutableInt;
diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java
index 968c761..5871d2d 100644
--- a/core/java/android/os/SystemService.java
+++ b/core/java/android/os/SystemService.java
@@ -16,9 +16,10 @@
package android.os;
+import android.compat.annotation.UnsupportedAppUsage;
+
import com.google.android.collect.Maps;
-import android.annotation.UnsupportedAppUsage;
import java.util.HashMap;
import java.util.concurrent.TimeoutException;
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index a5188e7..f585c75 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioAttributes;
import android.util.Log;
@@ -70,6 +70,20 @@
}
@Override
+ public boolean setAlwaysOnEffect(int id, 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);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to set always-on effect.", e);
+ }
+ return false;
+ }
+
+ @Override
public void vibrate(int uid, String opPkg, VibrationEffect effect,
String reason, AudioAttributes attributes) {
if (mService == null) {
diff --git a/core/java/android/os/TelephonyServiceManager.java b/core/java/android/os/TelephonyServiceManager.java
new file mode 100644
index 0000000..1211dd6
--- /dev/null
+++ b/core/java/android/os/TelephonyServiceManager.java
@@ -0,0 +1,138 @@
+/*
+ * 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the telephony
+ * service.
+ *
+ * <p>Only the telephony mainline module will be able to access an instance of this class.
+ *
+ * @hide
+ */
+@SystemApi
+public class TelephonyServiceManager {
+ /**
+ * @hide
+ */
+ public TelephonyServiceManager() {
+ }
+
+ /**
+ * A class that exposes the methods to register and obtain each system service.
+ */
+ public final class ServiceRegisterer {
+ private final String mServiceName;
+
+ /**
+ * @hide
+ */
+ public ServiceRegisterer(String serviceName) {
+ mServiceName = serviceName;
+ }
+
+ /**
+ * Register a system server binding object for a service.
+ */
+ public void register(@NonNull IBinder binder) {
+ ServiceManager.addService(mServiceName, binder);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it returns null.
+ */
+ @Nullable
+ public IBinder get() {
+ return ServiceManager.getService(mServiceName);
+ }
+
+ /**
+ * Get the system server binding object for a service.
+ *
+ * <p>This blocks until the service instance is ready,
+ * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+ */
+ @NonNull
+ public IBinder getOrThrow() throws ServiceNotFoundException {
+ try {
+ return ServiceManager.getServiceOrThrow(mServiceName);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new ServiceNotFoundException(mServiceName);
+ }
+ }
+
+ /**
+ * Get the system server binding object for a service. If the specified service is
+ * not available, it returns null.
+ */
+ @Nullable
+ public IBinder tryGet() {
+ return ServiceManager.checkService(mServiceName);
+ }
+ }
+
+ /**
+ * See {@link ServiceRegisterer#getOrThrow}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+ /**
+ * Constructor.
+ *
+ * @param name the name of the binder service that cannot be found.
+ *
+ */
+ public ServiceNotFoundException(@NonNull String name) {
+ super(name);
+ }
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "telephony" service.
+ */
+ @NonNull
+ public ServiceRegisterer getTelephonyServiceRegisterer() {
+ return new ServiceRegisterer("phone");
+ }
+
+
+// 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");
+// }
+}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 1456a73..25584f1 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -17,10 +17,9 @@
package android.os;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
-import com.android.internal.os.Zygote;
-
+import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
/**
@@ -107,12 +106,15 @@
private static final int MAX_SECTION_NAME_LEN = 127;
// Must be volatile to avoid word tearing.
+ // This is only kept in case any apps get this by reflection but do not
+ // check the return value for null.
@UnsupportedAppUsage
private static volatile long sEnabledTags = TRACE_TAG_NOT_READY;
private static int sZygoteDebugFlags = 0;
@UnsupportedAppUsage
+ @CriticalNative
private static native long nativeGetEnabledTags();
private static native void nativeSetAppTracingAllowed(boolean allowed);
private static native void nativeSetTracingEnabled(boolean allowed);
@@ -128,47 +130,10 @@
@FastNative
private static native void nativeAsyncTraceEnd(long tag, String name, int cookie);
- static {
- // We configure two separate change callbacks, one in Trace.cpp and one here. The
- // native callback reads the tags from the system property, and this callback
- // reads the value that the native code retrieved. It's essential that the native
- // callback executes first.
- //
- // The system provides ordering through a priority level. Callbacks made through
- // SystemProperties.addChangeCallback currently have a negative priority, while
- // our native code is using a priority of zero.
- SystemProperties.addChangeCallback(() -> {
- cacheEnabledTags();
- if ((sZygoteDebugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) {
- traceCounter(TRACE_TAG_ALWAYS, "java_debuggable", 1);
- }
- });
- }
-
private Trace() {
}
/**
- * Caches a copy of the enabled-tag bits. The "master" copy is held by the native code,
- * and comes from the PROPERTY_TRACE_TAG_ENABLEFLAGS property.
- * <p>
- * If the native code hasn't yet read the property, we will cause it to do one-time
- * initialization. We don't want to do this during class init, because this class is
- * preloaded, so all apps would be stuck with whatever the zygote saw. (The zygote
- * doesn't see the system-property update broadcasts.)
- * <p>
- * We want to defer initialization until the first use by an app, post-zygote.
- * <p>
- * We're okay if multiple threads call here simultaneously -- the native state is
- * synchronized, and sEnabledTags is volatile (prevents word tearing).
- */
- private static long cacheEnabledTags() {
- long tags = nativeGetEnabledTags();
- sEnabledTags = tags;
- return tags;
- }
-
- /**
* Returns true if a trace tag is enabled.
*
* @param traceTag The trace tag to check.
@@ -178,10 +143,7 @@
*/
@UnsupportedAppUsage
public static boolean isTagEnabled(long traceTag) {
- long tags = sEnabledTags;
- if (tags == TRACE_TAG_NOT_READY) {
- tags = cacheEnabledTags();
- }
+ long tags = nativeGetEnabledTags();
return (tags & traceTag) != 0;
}
@@ -210,10 +172,6 @@
@UnsupportedAppUsage
public static void setAppTracingAllowed(boolean allowed) {
nativeSetAppTracingAllowed(allowed);
-
- // Setting whether app tracing is allowed may change the tags, so we update the cached
- // tags here.
- cacheEnabledTags();
}
/**
@@ -227,10 +185,6 @@
public static void setTracingEnabled(boolean enabled, int debugFlags) {
nativeSetTracingEnabled(enabled);
sZygoteDebugFlags = debugFlags;
-
- // Setting whether tracing is enabled may change the tags, so we update the cached tags
- // here.
- cacheEnabledTags();
}
/**
diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java
index dc98c42..fa30e509 100644
--- a/core/java/android/os/UEventObserver.java
+++ b/core/java/android/os/UEventObserver.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/os/UpdateLock.java b/core/java/android/os/UpdateLock.java
index ea273ce..036d095 100644
--- a/core/java/android/os/UpdateLock.java
+++ b/core/java/android/os/UpdateLock.java
@@ -16,7 +16,7 @@
package android.os;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.Log;
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 62e83aa..b92fb47 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -20,8 +20,8 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.PrintWriter;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 137f537..fa9569b 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -28,12 +28,12 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -442,7 +442,13 @@
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
+ * @deprecated As the ability to have a managed profile on a fully-managed device has been
+ * removed from the platform, this restriction will be silently ignored when applied by the
+ * device owner.
+ * When the device is provisioned with a managed profile on an organization-owned device,
+ * the managed profile could not be removed anyway.
*/
+ @Deprecated
public static final String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile";
/**
@@ -589,7 +595,11 @@
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
+ * @deprecated As the ability to have a managed profile on a fully-managed device has been
+ * removed from the platform, this restriction will be silently ignored when applied by the
+ * device owner.
*/
+ @Deprecated
public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
/**
@@ -1540,6 +1550,9 @@
* set by the user and is not a placeholder string provided by the system.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
public boolean isUserNameSet() {
try {
return mService.isUserNameSet(getUserHandle());
@@ -2391,19 +2404,11 @@
*/
public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType,
@UserInfoFlag int flags) {
- UserInfo user = null;
try {
- user = mService.createUser(name, userType, flags);
- // TODO: Keep this in sync with
- // UserManagerService.LocalService.createUserEvenWhenDisallowed
- if (user != null && !user.isAdmin() && !user.isDemo()) {
- mService.setUserRestriction(DISALLOW_SMS, true, user.id);
- mService.setUserRestriction(DISALLOW_OUTGOING_CALLS, true, user.id);
- }
+ return mService.createUser(name, userType, flags);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
- return user;
}
/**
@@ -2748,8 +2753,7 @@
/**
* Assigns admin privileges to the user, if such a user exists.
*
- * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} and
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permissions.
+ * <p>Note that this does not alter the user's pre-existing user restrictions.
*
* @param userId the id of the user to become admin
* @hide
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 5769c34..75b4724 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
@@ -27,8 +28,6 @@
import android.net.Uri;
import android.util.MathUtils;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 28909c8..ccbb0f1 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -17,10 +17,11 @@
package android.os;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioAttributes;
import android.util.Log;
@@ -152,6 +153,24 @@
public abstract boolean hasAmplitudeControl();
/**
+ * Configure an always-on haptics effect.
+ *
+ * @param id 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
+ * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
+ * vibrations associated with incoming calls. May only be null when effect is null.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
+ @Nullable AudioAttributes attributes) {
+ Log.w(TAG, "Always-on effects aren't supported");
+ return false;
+ }
+
+ /**
* Vibrate constantly for the specified period of time.
*
* @param milliseconds The number of milliseconds to vibrate.
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 1c78b08..7af8f71 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.TestApi;
+import android.util.Slog;
import java.util.Map;
@@ -28,6 +29,8 @@
@TestApi
public class VintfObject {
+ private static final String LOG_TAG = "VintfObject";
+
/**
* Slurps all device information (both manifests and both matrices)
* and report them.
@@ -46,12 +49,33 @@
* @param packageInfo a list of serialized form of HalManifest's /
* CompatibilityMatri'ces (XML).
* @return = 0 if success (compatible)
- * > 0 if incompatible
- * < 0 if any error (mount partition fails, illformed XML, etc.)
+ * > 0 if incompatible
+ * < 0 if any error (mount partition fails, illformed XML, etc.)
+ *
+ * @deprecated Checking compatibility against an OTA package is no longer
+ * supported because the format of VINTF metadata in the OTA package may not
+ * be recognized by the current system.
+ *
+ * <p>
+ * <ul>
+ * <li>This function always returns 0 for non-empty {@code packageInfo}.
+ * </li>
+ * <li>This function returns the result of {@link #verifyWithoutAvb} for
+ * null or empty {@code packageInfo}.</li>
+ * </ul>
*
* @hide
*/
- public static native int verify(String[] packageInfo);
+ @Deprecated
+ public static int verify(String[] packageInfo) {
+ if (packageInfo != null && packageInfo.length > 0) {
+ Slog.w(LOG_TAG, "VintfObject.verify() with non-empty packageInfo is deprecated. "
+ + "Skipping compatibility checks for update package.");
+ return 0;
+ }
+ Slog.w(LOG_TAG, "VintfObject.verify() is deprecated. Call verifyWithoutAvb() instead.");
+ return verifyWithoutAvb();
+ }
/**
* Verify Vintf compatibility on the device without checking AVB
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 397c2a9..7161b07 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -4,9 +4,8 @@
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.os.WorkSourceProto;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.Log;
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 907eae8..62b8953 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -39,6 +40,7 @@
import java.util.Base64;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
/*package*/ class ZygoteStartFailedEx extends Exception {
@@ -306,8 +308,12 @@
* @param appDataDir null-ok the data directory of the app.
* @param invokeWith null-ok the command to invoke with.
* @param packageName null-ok the name of the package this process belongs to.
+ * @param disabledCompatChanges null-ok list of disabled compat changes for the process being
+ * started.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @param isTopApp Whether the process starts for high priority application.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
*
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
@@ -325,6 +331,9 @@
@Nullable String packageName,
boolean useUsapPool,
boolean isTopApp,
+ @Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] zygoteArgs) {
// TODO (chriswailes): Is there a better place to check this value?
if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -335,7 +344,8 @@
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
- packageName, useUsapPool, isTopApp, zygoteArgs);
+ packageName, useUsapPool, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -535,6 +545,9 @@
* that has its state cloned from this zygote process.
* @param packageName null-ok the name of the package this process belongs to.
* @param isTopApp Whether the process starts for high priority application.
+ * @param disabledCompatChanges a list of disabled compat changes for the process being started.
+ * @param pkgDataInfoMap Map from related package names to private data directory volume UUID
+ * and inode number.
* @param extraArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws ZygoteStartFailedEx if process start failed for any reason
@@ -554,6 +567,9 @@
@Nullable String packageName,
boolean useUsapPool,
boolean isTopApp,
+ @Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<>();
@@ -584,10 +600,10 @@
// --setgroups is a comma-separated list
if (gids != null && gids.length > 0) {
- StringBuilder sb = new StringBuilder();
+ final StringBuilder sb = new StringBuilder();
sb.append("--setgroups=");
- int sz = gids.length;
+ final int sz = gids.length;
for (int i = 0; i < sz; i++) {
if (i != 0) {
sb.append(',');
@@ -630,6 +646,39 @@
if (isTopApp) {
argsForZygote.add(Zygote.START_AS_TOP_APP_ARG);
}
+ if (pkgDataInfoMap != null && pkgDataInfoMap.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Zygote.PKG_DATA_INFO_MAP);
+ sb.append("=");
+ boolean started = false;
+ for (Map.Entry<String, Pair<String, Long>> entry : pkgDataInfoMap.entrySet()) {
+ if (started) {
+ sb.append(',');
+ }
+ started = true;
+ sb.append(entry.getKey());
+ sb.append(',');
+ sb.append(entry.getValue().first);
+ sb.append(',');
+ sb.append(entry.getValue().second);
+ }
+ argsForZygote.add(sb.toString());
+ }
+
+ if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("--disabled-compat-changes=");
+
+ int sz = disabledCompatChanges.length;
+ for (int i = 0; i < sz; i++) {
+ if (i != 0) {
+ sb.append(',');
+ }
+ sb.append(disabledCompatChanges[i]);
+ }
+
+ argsForZygote.add(sb.toString());
+ }
argsForZygote.add(processClass);
@@ -1162,11 +1211,14 @@
Process.ProcessStartResult result;
try {
+ // As app zygote is for generating isolated process, at the end it can't access
+ // apps data, so doesn't need to its data info.
result = startViaZygote(processClass, niceName, uid, gid,
gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
true /* startChildZygote */, null /* packageName */,
- false /* useUsapPool */, false /* isTopApp */, extraArgs);
+ false /* useUsapPool */, false /* isTopApp */,
+ null /* disabledCompatChanges */, null /* pkgDataInfoMap */, extraArgs);
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
diff --git a/core/java/android/os/connectivity/WifiBatteryStats.java b/core/java/android/os/connectivity/WifiBatteryStats.java
index e9b3837..895d837 100644
--- a/core/java/android/os/connectivity/WifiBatteryStats.java
+++ b/core/java/android/os/connectivity/WifiBatteryStats.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.BatteryStats;
+import android.os.BatteryStatsManager;
import android.os.Parcel;
import android.os.Parcelable;
@@ -46,9 +47,9 @@
private long mEnergyConsumedMaMillis = 0;
private long mNumAppScanRequest = 0;
private long[] mTimeInStateMillis =
- new long[BatteryStats.NUM_WIFI_STATES];
+ new long[BatteryStatsManager.NUM_WIFI_STATES];
private long[] mTimeInSupplicantStateMillis =
- new long[BatteryStats.NUM_WIFI_SUPPL_STATES];
+ new long[BatteryStatsManager.NUM_WIFI_SUPPL_STATES];
private long[] mTimeInRxSignalStrengthLevelMillis =
new long[BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS];
private long mMonitoredRailChargeConsumedMaMillis = 0;
@@ -369,7 +370,7 @@
/** @hide */
public void setTimeInStateMillis(long[] t) {
mTimeInStateMillis = Arrays.copyOfRange(t, 0,
- Math.min(t.length, BatteryStats.NUM_WIFI_STATES));
+ Math.min(t.length, BatteryStatsManager.NUM_WIFI_STATES));
return;
}
@@ -383,7 +384,7 @@
/** @hide */
public void setTimeInSupplicantStateMillis(long[] t) {
mTimeInSupplicantStateMillis = Arrays.copyOfRange(
- t, 0, Math.min(t.length, BatteryStats.NUM_WIFI_SUPPL_STATES));
+ t, 0, Math.min(t.length, BatteryStatsManager.NUM_WIFI_SUPPL_STATES));
return;
}
diff --git a/core/java/android/os/health/HealthStatsParceler.java b/core/java/android/os/health/HealthStatsParceler.java
index 384342c..f28a974 100644
--- a/core/java/android/os/health/HealthStatsParceler.java
+++ b/core/java/android/os/health/HealthStatsParceler.java
@@ -17,11 +17,10 @@
package android.os.health;
import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
/**
* Class to allow sending the HealthStats through aidl generated glue.
*
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index a92e28a..6e259ea 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -17,14 +17,13 @@
package android.os.health;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
import com.android.internal.app.IBatteryStats;
diff --git a/core/java/android/os/incremental/IIncrementalManager.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl
index d6446d4..17a310a 100644
--- a/core/java/android/os/incremental/IIncrementalManager.aidl
+++ b/core/java/android/os/incremental/IIncrementalManager.aidl
@@ -16,89 +16,22 @@
package android.os.incremental;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.IDataLoaderStatusListener;
-/** @hide */
+/**
+ * Binder service to receive calls from native Incremental Service and handle Java tasks such as
+ * looking up data loader service package names, binding and talking to the data loader service.
+ * @hide
+ */
interface IIncrementalManager {
- /**
- * A set of flags for the |createMode| parameters when creating a new Incremental storage.
- */
- const int CREATE_MODE_TEMPORARY_BIND = 1;
- const int CREATE_MODE_PERMANENT_BIND = 2;
- const int CREATE_MODE_CREATE = 4;
- const int CREATE_MODE_OPEN_EXISTING = 8;
-
- /**
- * Opens or creates a storage given a target path and data loader params. Returns the storage ID.
- */
- int openStorage(in @utf8InCpp String path);
- int createStorage(in @utf8InCpp String path, in IncrementalDataLoaderParamsParcel params, int createMode);
- int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
-
- /**
- * Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
- */
- const int BIND_TEMPORARY = 0;
- const int BIND_PERMANENT = 1;
- int makeBindMount(int storageId, in @utf8InCpp String pathUnderStorage, in @utf8InCpp String targetFullPath, int bindType);
-
- /**
- * Deletes an existing bind mount on a path under a storage. Returns 0 on success, and -errno on failure.
- */
- int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath);
-
- /**
- * Creates a directory under a storage. The target directory is specified by its relative path under the storage.
- */
- int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage);
-
- /**
- * Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage.
- * All the parent directories of the target directory will be created if they do not exist already.
- */
- int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage);
-
- /**
- * Creates a file under a storage, specifying its name, size and metadata.
- */
- int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata);
-
- /**
- * Creates a file under a storage. Content of the file is from a range inside another file.
- * Both files are specified by relative paths under storage.
- */
- int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end);
-
- /**
- * Creates a hard link between two files in two storage instances.
- * Source and dest specified by parent storage IDs and their relative paths under the storage.
- * The source and dest storage instances should be in the same fs mount.
- * Note: destStorageId can be the same as sourceStorageId.
- */
- int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage);
-
- /**
- * Deletes a hard link in a storage, specified by the relative path of the link target under storage.
- */
- int unlink(int storageId, in @utf8InCpp String pathUnderStorage);
-
- /**
- * Checks if a file's certain range is loaded. File is specified by relative file path under storage.
- */
- boolean isFileRangeLoaded(int storageId, in @utf8InCpp String pathUnderStorage, long start, long end);
-
- /**
- * Reads the metadata of a file. File is specified by relative path under storage.
- */
- byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage);
-
- /**
- * Starts loading data for a storage.
- */
- boolean startLoading(int storageId);
-
- /**
- * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
- */
- void deleteStorage(int storageId);
+ boolean prepareDataLoader(int mountId,
+ in FileSystemControlParcel control,
+ in DataLoaderParamsParcel params,
+ in IDataLoaderStatusListener listener);
+ boolean startDataLoader(int mountId);
+ void showHealthBlockedUI(int mountId);
+ void destroyDataLoader(int mountId);
+ void newFileForDataLoader(int mountId, long inode, in byte[] metadata);
}
diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
new file mode 100644
index 0000000..14215b1
--- /dev/null
+++ b/core/java/android/os/incremental/IIncrementalManagerNative.aidl
@@ -0,0 +1,104 @@
+/*
+ * 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.incremental;
+
+import android.content.pm.DataLoaderParamsParcel;
+
+/** @hide */
+interface IIncrementalManagerNative {
+ /**
+ * A set of flags for the |createMode| parameters when creating a new Incremental storage.
+ */
+ const int CREATE_MODE_TEMPORARY_BIND = 1;
+ const int CREATE_MODE_PERMANENT_BIND = 2;
+ const int CREATE_MODE_CREATE = 4;
+ const int CREATE_MODE_OPEN_EXISTING = 8;
+
+ /**
+ * Opens or creates a storage given a target path and data loader params. Returns the storage ID.
+ */
+ int openStorage(in @utf8InCpp String path);
+ int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode);
+ int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
+
+ /**
+ * Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
+ */
+ const int BIND_TEMPORARY = 0;
+ const int BIND_PERMANENT = 1;
+ int makeBindMount(int storageId, in @utf8InCpp String pathUnderStorage, in @utf8InCpp String targetFullPath, int bindType);
+
+ /**
+ * Deletes an existing bind mount on a path under a storage. Returns 0 on success, and -errno on failure.
+ */
+ int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath);
+
+ /**
+ * Creates a directory under a storage. The target directory is specified by its relative path under the storage.
+ */
+ int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage);
+
+ /**
+ * Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage.
+ * All the parent directories of the target directory will be created if they do not exist already.
+ */
+ int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage);
+
+ /**
+ * Creates a file under a storage, specifying its name, size and metadata.
+ */
+ int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata);
+
+ /**
+ * Creates a file under a storage. Content of the file is from a range inside another file.
+ * Both files are specified by relative paths under storage.
+ */
+ int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end);
+
+ /**
+ * Creates a hard link between two files in two storage instances.
+ * Source and dest specified by parent storage IDs and their relative paths under the storage.
+ * The source and dest storage instances should be in the same fs mount.
+ * Note: destStorageId can be the same as sourceStorageId.
+ */
+ int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage);
+
+ /**
+ * Deletes a hard link in a storage, specified by the relative path of the link target under storage.
+ */
+ int unlink(int storageId, in @utf8InCpp String pathUnderStorage);
+
+ /**
+ * Checks if a file's certain range is loaded. File is specified by relative file path under storage.
+ */
+ boolean isFileRangeLoaded(int storageId, in @utf8InCpp String pathUnderStorage, long start, long end);
+
+ /**
+ * Reads the metadata of a file. File is specified by relative path under storage.
+ */
+ byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage);
+
+ /**
+ * Starts loading data for a storage.
+ */
+ boolean startLoading(int storageId);
+
+ /**
+ * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
+ */
+ void deleteStorage(int storageId);
+}
diff --git a/core/java/android/os/incremental/IIncrementalServiceProxy.aidl b/core/java/android/os/incremental/IIncrementalServiceProxy.aidl
deleted file mode 100644
index ffff52e..0000000
--- a/core/java/android/os/incremental/IIncrementalServiceProxy.aidl
+++ /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 android.os.incremental;
-
-import android.os.incremental.IncrementalFileSystemControlParcel;
-import android.os.incremental.IncrementalDataLoaderParamsParcel;
-import android.content.pm.IDataLoaderStatusListener;
-
-/**
- * Binder service to receive calls from native Incremental Service and handle Java tasks such as
- * looking up data loader service package names, binding and talking to the data loader service.
- * @hide
- */
-interface IIncrementalServiceProxy {
- boolean prepareDataLoader(int mountId,
- in IncrementalFileSystemControlParcel control,
- in IncrementalDataLoaderParamsParcel params,
- in IDataLoaderStatusListener listener);
- boolean startDataLoader(int mountId);
- void showHealthBlockedUI(int mountId);
- void destroyDataLoader(int mountId);
- void newFileForDataLoader(int mountId, long inode, in byte[] metadata);
-}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParams.java b/core/java/android/os/incremental/IncrementalDataLoaderParams.java
deleted file mode 100644
index 701f1cc..0000000
--- a/core/java/android/os/incremental/IncrementalDataLoaderParams.java
+++ /dev/null
@@ -1,84 +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 android.os.incremental;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.ParcelFileDescriptor;
-
-import java.util.Arrays;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-/**
- * This class represents the parameters used to configure an Incremental Data Loader.
- * Hide for now.
- * @hide
- */
-public class IncrementalDataLoaderParams {
- @NonNull private final IncrementalDataLoaderParamsParcel mData;
-
- public IncrementalDataLoaderParams(@NonNull String url, @NonNull String packageName,
- @Nullable Map<String, ParcelFileDescriptor> namedFds) {
- IncrementalDataLoaderParamsParcel data = new IncrementalDataLoaderParamsParcel();
- data.staticArgs = url;
- data.packageName = packageName;
- if (namedFds == null || namedFds.isEmpty()) {
- data.dynamicArgs = new NamedParcelFileDescriptor[0];
- } else {
- data.dynamicArgs = new NamedParcelFileDescriptor[namedFds.size()];
- int i = 0;
- for (Map.Entry<String, ParcelFileDescriptor> namedFd : namedFds.entrySet()) {
- data.dynamicArgs[i] = new NamedParcelFileDescriptor();
- data.dynamicArgs[i].name = namedFd.getKey();
- data.dynamicArgs[i].fd = namedFd.getValue();
- i += 1;
- }
- }
- mData = data;
- }
-
- public IncrementalDataLoaderParams(@NonNull IncrementalDataLoaderParamsParcel data) {
- mData = data;
- }
-
- /**
- * @return static server's URL
- */
- public final @NonNull String getStaticArgs() {
- return mData.staticArgs;
- }
-
- /**
- * @return data loader's package name
- */
- public final @NonNull String getPackageName() {
- return mData.packageName;
- }
-
- public final @NonNull IncrementalDataLoaderParamsParcel getData() {
- return mData;
- }
-
- /**
- * @return data loader's dynamic arguments such as file descriptors
- */
- public final @NonNull Map<String, ParcelFileDescriptor> getDynamicArgs() {
- return Arrays.stream(mData.dynamicArgs).collect(
- Collectors.toMap(p->p.name, p->p.fd));
- }
-}
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 5bd0748..2138d553 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -35,6 +35,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.os.IVold;
import android.os.RemoteException;
@@ -82,12 +83,12 @@
public IncrementalFileStorages(@NonNull String packageName,
@NonNull File stageDir,
@NonNull IncrementalManager incrementalManager,
- @NonNull IncrementalDataLoaderParams incrementalDataLoaderParams) {
+ @NonNull DataLoaderParams dataLoaderParams) {
mPackageName = packageName;
mStageDir = stageDir;
mIncrementalManager = incrementalManager;
- if (incrementalDataLoaderParams.getPackageName().equals("local")) {
- final String incrementalPath = incrementalDataLoaderParams.getStaticArgs();
+ if (dataLoaderParams.getComponentName().getPackageName().equals("local")) {
+ final String incrementalPath = dataLoaderParams.getArguments();
mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
mDefaultDir = incrementalPath;
return;
@@ -97,7 +98,7 @@
return;
}
mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
- incrementalDataLoaderParams,
+ dataLoaderParams,
IncrementalManager.CREATE_MODE_CREATE
| IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
}
@@ -265,7 +266,7 @@
}
private String getTempDir() {
- final String tmpDirRoot = "/data/tmp";
+ final String tmpDirRoot = "/data/incremental/tmp";
final Random random = new Random();
final Path tmpDir =
Paths.get(tmpDirRoot, String.valueOf(random.nextInt(Integer.MAX_VALUE - 1)));
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 5aabf86..9c6672d 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.content.pm.DataLoaderParams;
import android.os.RemoteException;
import android.system.ErrnoException;
import android.system.Os;
@@ -36,31 +37,28 @@
import java.nio.file.Paths;
/**
- * Provides operations to open or create an IncrementalStorage, using IIncrementalManager service.
- * Example Usage:
+ * Provides operations to open or create an IncrementalStorage, using IIncrementalManagerNative
+ * service. Example Usage:
*
* <blockquote><pre>
- * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
* IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
* </pre></blockquote>
*
* @hide
*/
@SystemService(Context.INCREMENTAL_SERVICE)
-public class IncrementalManager {
+public final class IncrementalManager {
private static final String TAG = "IncrementalManager";
- private final IIncrementalManager mService;
- @GuardedBy("mStorages")
- private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
public static final int CREATE_MODE_TEMPORARY_BIND =
- IIncrementalManager.CREATE_MODE_TEMPORARY_BIND;
+ IIncrementalManagerNative.CREATE_MODE_TEMPORARY_BIND;
public static final int CREATE_MODE_PERMANENT_BIND =
- IIncrementalManager.CREATE_MODE_PERMANENT_BIND;
+ IIncrementalManagerNative.CREATE_MODE_PERMANENT_BIND;
public static final int CREATE_MODE_CREATE =
- IIncrementalManager.CREATE_MODE_CREATE;
+ IIncrementalManagerNative.CREATE_MODE_CREATE;
public static final int CREATE_MODE_OPEN_EXISTING =
- IIncrementalManager.CREATE_MODE_OPEN_EXISTING;
+ IIncrementalManagerNative.CREATE_MODE_OPEN_EXISTING;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"CREATE_MODE_"}, value = {
@@ -72,8 +70,12 @@
public @interface CreateMode {
}
- public IncrementalManager(@NonNull IIncrementalManager is) {
- mService = is;
+ private final @Nullable IIncrementalManagerNative mNativeService;
+ @GuardedBy("mStorages")
+ private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
+
+ public IncrementalManager(IIncrementalManagerNative nativeService) {
+ mNativeService = nativeService;
}
/**
@@ -82,6 +84,7 @@
* @param storageId The storage ID to identify the storage object.
* @return IncrementalStorage object corresponding to storage ID.
*/
+ // TODO(b/136132412): remove this
@Nullable
public IncrementalStorage getStorage(int storageId) {
synchronized (mStorages) {
@@ -95,21 +98,21 @@
*
* @param path Absolute path to mount Incremental File System on.
* @param params IncrementalDataLoaderParams object to configure data loading.
- * @param createMode Mode for opening an old Incremental File System mount or
- * creating a new mount.
+ * @param createMode Mode for opening an old Incremental File System mount or creating
+ * a new mount.
* @param autoStartDataLoader Set true to immediately start data loader after creating storage.
* @return IncrementalStorage object corresponding to the mounted directory.
*/
@Nullable
public IncrementalStorage createStorage(@NonNull String path,
- @NonNull IncrementalDataLoaderParams params, @CreateMode int createMode,
+ @NonNull DataLoaderParams params, @CreateMode int createMode,
boolean autoStartDataLoader) {
try {
- final int id = mService.createStorage(path, params.getData(), createMode);
+ final int id = mNativeService.createStorage(path, params.getData(), createMode);
if (id < 0) {
return null;
}
- final IncrementalStorage storage = new IncrementalStorage(mService, id);
+ final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
@@ -123,8 +126,8 @@
}
/**
- * Opens an existing Incremental File System mounted directory and returns an
- * IncrementalStorage object.
+ * Opens an existing Incremental File System mounted directory and returns an IncrementalStorage
+ * object.
*
* @param path Absolute target path that Incremental File System has been mounted on.
* @return IncrementalStorage object corresponding to the mounted directory.
@@ -132,11 +135,11 @@
@Nullable
public IncrementalStorage openStorage(@NonNull String path) {
try {
- final int id = mService.openStorage(path);
+ final int id = mNativeService.openStorage(path);
if (id < 0) {
return null;
}
- final IncrementalStorage storage = new IncrementalStorage(mService, id);
+ final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
@@ -155,11 +158,12 @@
public IncrementalStorage createStorage(@NonNull String path,
@NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
try {
- final int id = mService.createLinkedStorage(path, linkedStorage.getId(), createMode);
+ final int id = mNativeService.createLinkedStorage(
+ path, linkedStorage.getId(), createMode);
if (id < 0) {
return null;
}
- final IncrementalStorage storage = new IncrementalStorage(mService, id);
+ final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
@@ -175,6 +179,7 @@
* @param file Target file to search storage for.
* @return Absolute path which is a bind-mount point of Incremental File System.
*/
+ @Nullable
private Path getStoragePathForFile(File file) {
File currentPath = new File(file.getParent());
while (currentPath.getParent() != null) {
@@ -198,18 +203,19 @@
* </li>
* </ol>
*
- * @param sourcePath Absolute path to the source. Should be the same type as the destPath
- * (file or dir). Expected to already exist and is an Incremental path.
- * @param destPath Absolute path to the destination.
- * @throws IllegalArgumentException when 1) source does not exist,
- * or 2) source and dest type mismatch (one is file and the other is dir),
- * or 3) source path is not on Incremental File System,
- * @throws IOException when 1) cannot find the root path of the Incremental storage of source,
- * or 2) cannot retrieve the Incremental storage instance of the source,
- * or 3) renaming a file, but dest is not on the same Incremental Storage,
- * or 4) renaming a dir, dest dir does not exist but fails to be created.
- *
- * TODO(b/136132412): add unit tests
+ * @param sourcePath Absolute path to the source. Should be the same type as the destPath (file
+ * or dir). Expected to already exist and is an Incremental path.
+ * @param destPath Absolute path to the destination.
+ * @throws IllegalArgumentException when 1) source does not exist, or 2) source and dest type
+ * mismatch (one is file and the other is dir), or 3) source
+ * path is not on Incremental File System,
+ * @throws IOException when 1) cannot find the root path of the Incremental storage
+ * of source, or 2) cannot retrieve the Incremental storage
+ * instance of the source, or 3) renaming a file, but dest is
+ * not on the same Incremental Storage, or 4) renaming a dir,
+ * dest dir does not exist but fails to be created.
+ * <p>
+ * TODO(b/136132412): add unit tests
*/
public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
final File source = new File(sourcePath);
@@ -243,6 +249,7 @@
if (storage == null) {
throw new IOException("Failed to retrieve storage from Incremental Service.");
}
+
if (source.isFile()) {
renameFile(storage, storagePath, source, dest);
} else {
@@ -304,11 +311,11 @@
*/
public void closeStorage(@NonNull String path) {
try {
- final int id = mService.openStorage(path);
+ final int id = mNativeService.openStorage(path);
if (id < 0) {
return;
}
- mService.deleteStorage(id);
+ mNativeService.deleteStorage(id);
synchronized (mStorages) {
mStorages.remove(id);
}
@@ -321,7 +328,9 @@
* Checks if path is mounted on Incremental File System.
*/
public static boolean isIncrementalPath(@NonNull String path) {
- // TODO(b/136132412): implement native method
- return false;
+ return nativeIsIncrementalPath(path);
}
+
+ /* Native methods */
+ private static native boolean nativeIsIncrementalPath(@NonNull String path);
}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index 2bf89ed..2750868 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -24,11 +24,11 @@
import java.io.IOException;
/**
- * Provides operations on an Incremental File System directory, using IncrementalService. Example
- * usage:
+ * Provides operations on an Incremental File System directory, using IncrementalServiceNative.
+ * Example usage:
*
* <blockquote><pre>
- * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
* IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
* storage.makeDirectory("subdir");
* </pre></blockquote>
@@ -38,10 +38,10 @@
public final class IncrementalStorage {
private static final String TAG = "IncrementalStorage";
private final int mId;
- private final IIncrementalManager mService;
+ private final IIncrementalManagerNative mService;
- public IncrementalStorage(@NonNull IIncrementalManager is, int id) {
+ public IncrementalStorage(@NonNull IIncrementalManagerNative is, int id) {
mService = is;
mId = id;
}
@@ -72,7 +72,7 @@
throws IOException {
try {
int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
- IIncrementalManager.BIND_TEMPORARY);
+ IIncrementalManagerNative.BIND_TEMPORARY);
if (res < 0) {
throw new IOException("bind() failed with errno " + -res);
}
@@ -103,7 +103,7 @@
throws IOException {
try {
int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
- IIncrementalManager.BIND_PERMANENT);
+ IIncrementalManagerNative.BIND_PERMANENT);
if (res < 0) {
throw new IOException("bind() permanent failed with errno " + -res);
}
@@ -274,7 +274,8 @@
throw new IOException("moveDir() requires that destination dir already exists.");
}
try {
- int res = mService.makeBindMount(mId, "", destPath, IIncrementalManager.BIND_PERMANENT);
+ int res = mService.makeBindMount(mId, "", destPath,
+ IIncrementalManagerNative.BIND_PERMANENT);
if (res < 0) {
throw new IOException("moveDir() failed at making bind mount, errno " + -res);
}
diff --git a/tests/utils/testutils/java/test/package-info.java b/core/java/android/os/storage/CrateInfo.aidl
similarity index 77%
copy from tests/utils/testutils/java/test/package-info.java
copy to core/java/android/os/storage/CrateInfo.aidl
index c34d7b2..dd91053 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/core/java/android/os/storage/CrateInfo.aidl
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.os.storage;
/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+ * @hide
+ */
+parcelable CrateInfo;
diff --git a/core/java/android/os/storage/CrateInfo.java b/core/java/android/os/storage/CrateInfo.java
new file mode 100644
index 0000000..418d39e
--- /dev/null
+++ b/core/java/android/os/storage/CrateInfo.java
@@ -0,0 +1,282 @@
+/*
+ * 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.storage;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.UUID;
+
+/**
+ * The CrateInfo describe the crate information.
+ * <p>
+ * It describe the following items.
+ * <ul>
+ * <li>The crate id that is the name of the child directory in
+ * {@link Context#getCrateDir(String)}</li>
+ * <li>Label to provide human readable text for the users.</li>
+ * <li>Expiration information. When the crate is expired and the run .</li>
+ *
+ * </ul>for the directory
+ * </p>
+ * @hide
+ */
+@TestApi
+public final class CrateInfo implements Parcelable {
+ private static final String TAG = "CrateInfo";
+
+ /**
+ * The following member fields whose value are set by apps and retrieved by system_server.
+ */
+ private CharSequence mLabel;
+ @CurrentTimeMillisLong
+ private long mExpiration;
+
+ /**
+ * The following member fields whose value are retrieved by installd.
+ * <p>{@link android.app.usage.StorageStatsManager#queryCratesForUser(UUID, UserHandle)} query
+ * all of crates for the specified UserHandle. That means the return crate list whose elements
+ * may have the same userId but different package name. Each crate needs the information to tell
+ * the caller from where package comes.
+ * </p>
+ */
+ private int mUid;
+
+ /**
+ * The following member fields whose value are retrieved by installd.
+ * <p>Both {@link StorageStatsManager#queryCratesForUid(UUID, int)} and
+ * {@link android.app.usage.StorageStatsManager#queryCratesForUser(UUID, UserHandle)} query
+ * all of crates for the specified uid or userId. That means the return crate list whose
+ * elements may have the same uid or userId but different package name. Each crate needs the
+ * information to tell the caller from where package comes.
+ * </p>
+ */
+ @Nullable
+ private String mPackageName;
+
+ /**
+ * The following member fields whose value are retrieved by system_server.
+ * <p>
+ * The child directories in {@link Context#getCrateDir(String)} are crates. Each directories
+ * is a crate. The folder name is the crate id.
+ * </p><p>
+ * Can't apply check if the path is validated or not because it need pass through the
+ * parcel.
+ * </p>
+ */
+ @Nullable
+ private String mId;
+
+ private CrateInfo() {
+ mExpiration = 0;
+ }
+
+ /**
+ * To create the crateInfo by passing validated label.
+ * @param label a display name for the crate
+ * @param expiration It's positive integer. if current time is larger than the expiration, the
+ * files under this crate will be considered to be deleted. Default value is 0.
+ * @throws IllegalArgumentException cause IllegalArgumentException when label is null
+ * or empty string
+ */
+ public CrateInfo(@NonNull CharSequence label, @CurrentTimeMillisLong long expiration) {
+ Preconditions.checkStringNotEmpty(label,
+ "Label should not be either null or empty string");
+ Preconditions.checkArgumentNonnegative(expiration,
+ "Expiration should be non negative number");
+
+ mLabel = label;
+ mExpiration = expiration;
+ }
+
+ /**
+ * To create the crateInfo by passing validated label.
+ * @param label a display name for the crate
+ * @throws IllegalArgumentException cause IllegalArgumentException when label is null
+ * or empty string
+ */
+ public CrateInfo(@NonNull CharSequence label) {
+ this(label, 0);
+ }
+
+ /**
+ * To get the meaningful text of the crate for the users.
+ * @return the meaningful text
+ */
+ @NonNull
+ public CharSequence getLabel() {
+ if (TextUtils.isEmpty(mLabel)) {
+ return mId;
+ }
+ return mLabel;
+ }
+
+
+ /**
+ * To return the expiration time.
+ * <p>
+ * If the current time is larger than expiration time, the crate files are considered to be
+ * deleted.
+ * </p>
+ * @return the expiration time
+ */
+ @CurrentTimeMillisLong
+ public long getExpirationMillis() {
+ return mExpiration;
+ }
+
+ /**
+ * To set the expiration time.
+ * @param expiration the expiration time
+ * @hide
+ */
+ public void setExpiration(@CurrentTimeMillisLong long expiration) {
+ Preconditions.checkArgumentNonnegative(expiration);
+ mExpiration = expiration;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ /**
+ * To compare with crateinfo when selves' mId is validated.
+ * <p>The validated crateinfo.mId must be validated the following items.
+ * <ul>
+ * <li>mId is not null</li>
+ * <li>mId is not empty string</li>
+ * </ul>
+ * </p>
+ * @param obj the reference object with which to compare.
+ * @return true when selves's mId is validated and equal to crateinfo.mId.
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj instanceof CrateInfo) {
+ CrateInfo crateInfo = (CrateInfo) obj;
+ if (!TextUtils.isEmpty(mId)
+ && TextUtils.equals(mId, crateInfo.mId)) {
+ return true;
+ }
+ }
+
+ return super.equals(obj);
+ }
+
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@Nullable Parcel dest, int flags) {
+ if (dest == null) {
+ return;
+ }
+
+ dest.writeCharSequence(mLabel);
+ dest.writeLong(mExpiration);
+
+ dest.writeInt(mUid);
+ dest.writeString(mPackageName);
+ dest.writeString(mId);
+ }
+
+ /**
+ * To read the data from parcel.
+ * <p>
+ * It's called by StorageStatsService.
+ * </p>
+ * @hide
+ */
+ public void readFromParcel(@Nullable Parcel in) {
+ if (in == null) {
+ return;
+ }
+
+ mLabel = in.readCharSequence();
+ mExpiration = in.readLong();
+
+ mUid = in.readInt();
+ mPackageName = in.readString();
+ mId = in.readString();
+ }
+
+ @NonNull
+ public static final Creator<CrateInfo> CREATOR = new Creator<CrateInfo>() {
+ @NonNull
+ @Override
+ public CrateInfo createFromParcel(@NonNull Parcel in) {
+ CrateInfo crateInfo = new CrateInfo();
+ crateInfo.readFromParcel(in);
+ return crateInfo;
+ }
+
+ @NonNull
+ @Override
+ public CrateInfo[] newArray(int size) {
+ return new CrateInfo[size];
+ }
+ };
+
+ /**
+ * To copy the information from service into crateinfo.
+ * <p>
+ * This function is called in system_server. The copied information includes
+ * <ul>
+ * <li>uid</li>
+ * <li>package name</li>
+ * <li>crate id</li>
+ * </ul>
+ * </p>
+ * @param uid the uid that the crate belong to
+ * @param packageName the package name that the crate belong to
+ * @param id the crate dir
+ * @return the CrateInfo instance
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public static CrateInfo copyFrom(int uid, @Nullable String packageName, @Nullable String id) {
+ if (!UserHandle.isApp(uid) || TextUtils.isEmpty(packageName) || TextUtils.isEmpty(id)) {
+ return null;
+ }
+
+ CrateInfo crateInfo = new CrateInfo(id /* default label = id */, 0);
+ crateInfo.mUid = uid;
+ crateInfo.mPackageName = packageName;
+ crateInfo.mId = id;
+ return crateInfo;
+ }
+}
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index b797324..df3c4d5 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.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.res.Resources;
import android.os.Build;
import android.os.Parcel;
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index 39d5b45..9fd9e4e 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -16,7 +16,7 @@
package android.os.storage;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Used for receiving notifications from the StorageManager
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2d70986d..8959fcf 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -40,12 +40,12 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -80,6 +80,7 @@
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.DataUnit;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -156,7 +157,8 @@
/** {@hide} */
public static final String PROP_FUSE = "persist.sys.fuse";
/** {@hide} */
- public static final String PROP_FUSE_SNAPSHOT = "sys.fuse_snapshot";
+ public static final String PROP_SETTINGS_FUSE = FeatureFlagUtils.PERSIST_PREFIX
+ + FeatureFlagUtils.SETTINGS_FUSE_FLAG;
/** {@hide} */
@@ -262,6 +264,8 @@
public static final int FLAG_REAL_STATE = 1 << 9;
/** {@hide} */
public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
+ /** {@hide} */
+ public static final int FLAG_INCLUDE_RECENT = 1 << 11;
/** {@hide} */
public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
@@ -1123,7 +1127,7 @@
* Return the {@link StorageVolume} that contains the given file, or
* {@code null} if none.
*/
- public @Nullable StorageVolume getStorageVolume(File file) {
+ public @Nullable StorageVolume getStorageVolume(@NonNull File file) {
return getStorageVolume(getVolumeList(), file);
}
@@ -1138,7 +1142,7 @@
return getPrimaryStorageVolume();
default:
for (StorageVolume vol : getStorageVolumes()) {
- if (Objects.equals(vol.getNormalizedUuid(), volumeName)) {
+ if (Objects.equals(vol.getMediaStoreVolumeName(), volumeName)) {
return vol;
}
}
@@ -1199,12 +1203,13 @@
}
/**
- * Return the list of shared/external storage volumes available to the
- * current user. This includes both the primary shared storage device and
- * any attached external volumes including SD cards and USB drives.
- *
- * @see Environment#getExternalStorageDirectory()
- * @see StorageVolume#createAccessIntent(String)
+ * Return the list of shared/external storage volumes currently available to
+ * the calling user.
+ * <p>
+ * These storage volumes are actively attached to the device, but may be in
+ * any mount state, as returned by {@link StorageVolume#getState()}. Returns
+ * both the primary shared storage device and any attached external volumes,
+ * including SD cards and USB drives.
*/
public @NonNull List<StorageVolume> getStorageVolumes() {
final ArrayList<StorageVolume> res = new ArrayList<>();
@@ -1214,6 +1219,22 @@
}
/**
+ * Return the list of shared/external storage volumes both currently and
+ * recently available to the calling user.
+ * <p>
+ * Recently available storage volumes are likely to reappear in the future,
+ * so apps are encouraged to preserve any indexed metadata related to these
+ * volumes to optimize user experiences.
+ */
+ public @NonNull List<StorageVolume> getRecentStorageVolumes() {
+ final ArrayList<StorageVolume> res = new ArrayList<>();
+ Collections.addAll(res,
+ getVolumeList(mContext.getUserId(),
+ FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_RECENT));
+ return res;
+ }
+
+ /**
* Return the primary shared/external storage volume available to the
* current user. This volume is the same storage device returned by
* {@link Environment#getExternalStorageDirectory()} and
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 14c299d..5cac5f5 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -109,4 +109,13 @@
*/
public abstract void onAppOpsChanged(int code, int uid,
@Nullable String packageName, int mode);
+
+ /**
+ * Asks the StorageManager to reset all state for the provided user; this will result
+ * in the unmounting for all volumes of the user, and, if the user is still running, the
+ * volumes will be re-mounted as well.
+ *
+ * @param userId the userId for which to reset storage
+ */
+ public abstract void resetUser(int userId);
}
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index aefe843..2ab226f 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.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.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -29,6 +29,7 @@
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.DocumentsContract;
+import android.provider.MediaStore;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -173,7 +174,7 @@
* @return the mount path
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
@TestApi
public String getPath() {
return mPath.toString();
@@ -190,12 +191,35 @@
}
/** {@hide} */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
public File getPathFile() {
return mPath;
}
/**
+ * Returns the directory where this volume is currently mounted.
+ * <p>
+ * Direct filesystem access via this path has significant emulation
+ * overhead, and apps are instead strongly encouraged to interact with media
+ * on storage volumes via the {@link MediaStore} APIs.
+ * <p>
+ * This directory does not give apps any additional access beyond what they
+ * already have via {@link MediaStore}.
+ *
+ * @return directory where this volume is mounted, or {@code null} if the
+ * volume is not currently mounted.
+ */
+ public @Nullable File getDirectory() {
+ switch (mState) {
+ case Environment.MEDIA_MOUNTED:
+ case Environment.MEDIA_MOUNTED_READ_ONLY:
+ return mPath;
+ default:
+ return null;
+ }
+ }
+
+ /**
* Returns a user-visible description of the volume.
*
* @return the volume description
@@ -265,6 +289,24 @@
return mFsUuid;
}
+ /**
+ * Return the volume name that can be used to interact with this storage
+ * device through {@link MediaStore}.
+ *
+ * @return opaque volume name, or {@code null} if this volume is not indexed
+ * by {@link MediaStore}.
+ * @see android.provider.MediaStore.Audio.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Video.Media#getContentUri(String)
+ * @see android.provider.MediaStore.Images.Media#getContentUri(String)
+ */
+ public @Nullable String getMediaStoreVolumeName() {
+ if (isPrimary()) {
+ return MediaStore.VOLUME_EXTERNAL_PRIMARY;
+ } else {
+ return getNormalizedUuid();
+ }
+ }
+
/** {@hide} */
public static @Nullable String normalizeUuid(@Nullable String fsUuid) {
return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index d6ec52f..fb90655 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.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.Intent;
import android.content.res.Resources;
diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java
index 1a794eb..60df981 100644
--- a/core/java/android/os/storage/VolumeRecord.java
+++ b/core/java/android/os/storage/VolumeRecord.java
@@ -16,15 +16,19 @@
package android.os.storage;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.DebugUtils;
import android.util.TimeUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import java.io.File;
import java.util.Locale;
import java.util.Objects;
@@ -92,6 +96,27 @@
return (userFlags & USER_FLAG_SNOOZED) != 0;
}
+ public StorageVolume buildStorageVolume(Context context) {
+ final String id = "unknown:" + fsUuid;
+ final File userPath = new File("/dev/null");
+ final File internalPath = new File("/dev/null");
+ final boolean primary = false;
+ final boolean removable = true;
+ final boolean emulated = false;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(UserHandle.USER_NULL);
+ final String envState = Environment.MEDIA_UNKNOWN;
+
+ String description = nickname;
+ if (description == null) {
+ description = context.getString(android.R.string.unknownName);
+ }
+
+ return new StorageVolume(id, userPath, internalPath, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, fsUuid, envState);
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("VolumeRecord:");
pw.increaseIndent();
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 26c1ec1..0483514 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -43,4 +43,5 @@
String permission, int grantState, in AndroidFuture callback);
void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback);
void updateUserSensitive(in AndroidFuture callback);
+ void notifyOneTimePermissionSessionTimeout(String packageName);
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 60c8811..2615c98 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -20,6 +20,7 @@
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.os.UserHandle;
import android.permission.IOnPermissionsChangeListener;
/**
@@ -100,4 +101,9 @@
boolean isPermissionRevokedByPolicy(String permName, String packageName, int userId);
List<SplitPermissionInfoParcelable> getSplitPermissions();
+
+ void startOneTimePermissionSession(String packageName, int userId, long timeout,
+ int importanceToResetTimer, int importanceToKeepSessionAlive);
+
+ void stopOneTimePermissionSession(String packageName, int userId);
}
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 421e29e..2a1857f 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -636,4 +636,18 @@
return future;
});
}
+
+ /**
+ * Called when a package that has permissions registered as "one-time" is considered
+ * inactive.
+ *
+ * @param packageName The package which became inactive
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+ public void notifyOneTimePermissionSessionTimeout(@NonNull String packageName) {
+ mRemoteService.run(
+ service -> service.notifyOneTimePermissionSessionTimeout(packageName));
+ }
}
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 8f765fa..5d4561c 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -221,7 +221,7 @@
* permission controller package.
*/
@BinderThread
- public void onUpdateUserSensitive() {
+ public void onUpdateUserSensitivePermissionFlags() {
throw new AbstractMethodError("Must be overridden in implementing class");
}
@@ -240,6 +240,18 @@
@NonNull String permission, @PermissionGrantState int grantState,
@NonNull Consumer<Boolean> callback);
+ /**
+ * Called when a package is considered inactive based on the criteria given by
+ * {@link PermissionManager#startOneTimePermissionSession(String, long, int, int)}.
+ * This method is called at the end of a one-time permission session
+ *
+ * @param packageName The package that has been inactive
+ */
+ @BinderThread
+ public void onOneTimePermissionSessionTimeout(@NonNull String packageName) {
+ throw new AbstractMethodError("Must be overridden in implementing class");
+ }
+
@Override
public final @NonNull IBinder onBind(Intent intent) {
return new IPermissionController.Stub() {
@@ -449,9 +461,18 @@
public void updateUserSensitive(AndroidFuture callback) {
Preconditions.checkNotNull(callback, "callback cannot be null");
- onUpdateUserSensitive();
+ onUpdateUserSensitivePermissionFlags();
callback.complete(null);
}
+
+ @Override
+ public void notifyOneTimePermissionSessionTimeout(String packageName) {
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
+ packageName = Preconditions.checkNotNull(packageName,
+ "packageName cannot be null");
+ onOneTimePermissionSessionTimeout(packageName);
+ }
};
}
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 09286fe..a3215a4 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -25,6 +25,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.IPackageManager;
@@ -284,4 +285,69 @@
mSplitPermissionInfoParcelable = parcelable;
}
}
+
+ /**
+ * Starts a one-time permission session for a given package. A one-time permission session is
+ * ended if app becomes inactive. Inactivity is defined as the package's uid importance level
+ * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
+ * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
+ * until going > importanceToResetTimer.
+ * <p>
+ * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
+ * then the session is extended until either the importance goes above
+ * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
+ * will continue the session and reset the timer.
+ * </p>
+ * <p>
+ * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
+ * </p>
+ * <p>
+ * Once the session ends
+ * {@link PermissionControllerService#onOneTimePermissionSessionTimeout(String)} is invoked.
+ * </p>
+ * <p>
+ * Note that if there is currently an active session for a package a new one isn't created and
+ * the existing one isn't changed.
+ * </p>
+ * @param packageName The package to start a one-time permission session for
+ * @param timeoutMillis Number of milliseconds for an app to be in an inactive state
+ * @param importanceToResetTimer The least important level to uid must be to reset the timer
+ * @param importanceToKeepSessionAlive The least important level the uid must be to keep the
+ * session alive
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.PACKAGE_USAGE_STATS})
+ public void startOneTimePermissionSession(@NonNull String packageName, long timeoutMillis,
+ @ActivityManager.RunningAppProcessInfo.Importance int importanceToResetTimer,
+ @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
+ try {
+ mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(),
+ timeoutMillis, importanceToResetTimer, importanceToKeepSessionAlive);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stops the one-time permission session for the package. The callback to the end of session is
+ * not invoked. If there is no one-time session for the package then nothing happens.
+ *
+ * @param packageName Package to stop the one-time permission session for
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.PACKAGE_USAGE_STATS})
+ public void stopOneTimePermissionSession(@NonNull String packageName) {
+ try {
+ mPermissionManager.stopOneTimePermissionSession(packageName,
+ mContext.getUserId());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
index 32511e9..dfdb57c4 100644
--- a/core/java/android/preference/DialogPreference.java
+++ b/core/java/android/preference/DialogPreference.java
@@ -21,9 +21,9 @@
import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
index 74c5e3e..af6f184 100644
--- a/core/java/android/preference/EditTextPreference.java
+++ b/core/java/android/preference/EditTextPreference.java
@@ -17,7 +17,7 @@
package android.preference;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java
index 830de525..0b64809 100644
--- a/core/java/android/preference/ListPreference.java
+++ b/core/java/android/preference/ListPreference.java
@@ -17,8 +17,8 @@
package android.preference;
import android.annotation.ArrayRes;
-import android.annotation.UnsupportedAppUsage;
import android.app.AlertDialog.Builder;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index e82e60d..f508dda 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -21,7 +21,7 @@
import android.annotation.LayoutRes;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 4750971..ae4a626 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -19,13 +19,13 @@
import android.animation.LayoutTransition;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
import android.app.Fragment;
import android.app.FragmentBreadCrumbs;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.ListActivity;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index d6c069f0..3f6e505 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -17,10 +17,10 @@
package android.preference;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
import android.app.Activity;
import android.app.Fragment;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index dcc5d4c..b263f50 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -16,7 +16,7 @@
package android.preference;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.preference.Preference.OnPreferenceChangeInternalListener;
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index f741bd6..9d3f349 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -18,9 +18,9 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
import android.app.Activity;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index a353dbc..01fe2f3 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -16,8 +16,8 @@
package android.preference;
-import android.annotation.UnsupportedAppUsage;
import android.app.Dialog;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
diff --git a/core/java/android/preference/RingtonePreference.java b/core/java/android/preference/RingtonePreference.java
index 025aad0..c6d8c08 100644
--- a/core/java/android/preference/RingtonePreference.java
+++ b/core/java/android/preference/RingtonePreference.java
@@ -16,7 +16,7 @@
package android.preference;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
diff --git a/core/java/android/preference/SeekBarDialogPreference.java b/core/java/android/preference/SeekBarDialogPreference.java
index 32ef821..46be122 100644
--- a/core/java/android/preference/SeekBarDialogPreference.java
+++ b/core/java/android/preference/SeekBarDialogPreference.java
@@ -16,7 +16,7 @@
package android.preference;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java
index 99ab9db..a2852bc 100644
--- a/core/java/android/preference/SeekBarPreference.java
+++ b/core/java/android/preference/SeekBarPreference.java
@@ -16,7 +16,7 @@
package android.preference;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 4dd9bab..0cdad9f 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -17,8 +17,8 @@
package android.preference;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
import android.app.NotificationManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 9dea1c8..baa023e 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -17,7 +17,7 @@
package android.preference;
import android.annotation.StringRes;
-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/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java
index bb771d7..5eb5d17 100644
--- a/core/java/android/preference/TwoStatePreference.java
+++ b/core/java/android/preference/TwoStatePreference.java
@@ -17,7 +17,7 @@
package android.preference;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index a2d5a23..6eb524a 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -16,8 +16,8 @@
package android.preference;
-import android.annotation.UnsupportedAppUsage;
import android.app.Dialog;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java
index d1b6efc..7ea5655 100644
--- a/core/java/android/print/PrintDocumentAdapter.java
+++ b/core/java/android/print/PrintDocumentAdapter.java
@@ -16,7 +16,7 @@
package android.print;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 25f383c..63f38f8 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -23,7 +23,7 @@
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Bundle;
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index e1ede93..9abce5d 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -22,9 +22,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index 42570c6..75ca750 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -17,7 +17,7 @@
package android.print;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/provider/BaseColumns.java b/core/java/android/provider/BaseColumns.java
index 00c9e72..b216e2b 100644
--- a/core/java/android/provider/BaseColumns.java
+++ b/core/java/android/provider/BaseColumns.java
@@ -16,13 +16,11 @@
package android.provider;
-import android.database.Cursor;
-
public interface BaseColumns {
/**
* The unique ID for a row.
*/
- @Column(Cursor.FIELD_TYPE_INTEGER)
+ // @Column(Cursor.FIELD_TYPE_INTEGER)
public static final String _ID = "_id";
/**
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index 30021b4..afa7b80 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -16,10 +16,8 @@
package android.provider;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@@ -30,8 +28,6 @@
import android.provider.BrowserContract.Bookmarks;
import android.provider.BrowserContract.Combined;
import android.provider.BrowserContract.History;
-import android.provider.BrowserContract.Searches;
-import android.util.Log;
import android.webkit.WebIconDatabase;
public class Browser {
diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java
index 57dde66..5083b8b2 100644
--- a/core/java/android/provider/BrowserContract.java
+++ b/core/java/android/provider/BrowserContract.java
@@ -17,7 +17,7 @@
package android.provider;
import android.accounts.Account;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 7285166..9c6c92a 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -20,11 +20,11 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 30db638..a0e92b3 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -17,7 +17,7 @@
package android.provider;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
diff --git a/core/java/android/provider/Column.java b/core/java/android/provider/Column.java
deleted file mode 100644
index 1364fb8..0000000
--- a/core/java/android/provider/Column.java
+++ /dev/null
@@ -1,50 +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 android.provider;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-/**
- * Denotes that a field is a {@link ContentProvider} column. It can be used as a
- * key for {@link ContentValues} when inserting or updating data, or as a
- * projection when querying.
- *
- * @hide
- */
-@Documented
-@Retention(RUNTIME)
-@Target({FIELD})
-public @interface Column {
- /**
- * The {@link Cursor#getType(int)} of the data stored in this column.
- */
- int value();
-
- /**
- * This column is read-only and cannot be defined during insert or updates.
- */
- boolean readOnly() default false;
-}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index f10e184..e383a37 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -17,12 +17,14 @@
package android.provider;
import android.accounts.Account;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProviderClient;
@@ -2379,7 +2381,11 @@
* This id is provided by its own data source, and can be used to backup metadata
* to the server.
* This should be unique within each set of account_name/account_type/data_set
+ *
+ * @deprecated This column is no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
public static final String BACKUP_ID = "backup_id";
/**
@@ -2443,7 +2449,11 @@
* Flag indicating that a raw contact's metadata has changed, and its metadata
* needs to be synchronized by the server.
* <P>Type: INTEGER (boolean)</P>
+ *
+ * @deprecated This column is no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
public static final String METADATA_DIRTY = "metadata_dirty";
}
@@ -2902,6 +2912,44 @@
}
/**
+ * The default value used for {@link #ACCOUNT_NAME} of raw contacts when they are inserted
+ * without a value for this column.
+ *
+ * <p>This account is used to identify contacts that are only stored locally in the
+ * contacts database instead of being associated with an {@link Account} managed by an
+ * installed application.
+ *
+ * <p>When this returns null then {@link #getLocalAccountType} will also return null and
+ * when it is non-null {@link #getLocalAccountType} will also return a non-null value.
+ */
+ @Nullable
+ public static String getLocalAccountName(@NonNull Context context) {
+ // config_rawContactsLocalAccountName is defined in
+ // platform/frameworks/base/core/res/res/values/config.xml
+ return TextUtils.nullIfEmpty(context.getString(
+ com.android.internal.R.string.config_rawContactsLocalAccountName));
+ }
+
+ /**
+ * The default value used for {@link #ACCOUNT_TYPE} of raw contacts when they are inserted
+ * without a value for this column.
+ *
+ * <p>This account is used to identify contacts that are only stored locally in the
+ * contacts database instead of being associated with an {@link Account} managed by an
+ * installed application.
+ *
+ * <p>When this returns null then {@link #getLocalAccountName} will also return null and
+ * when it is non-null {@link #getLocalAccountName} will also return a non-null value.
+ */
+ @Nullable
+ public static String getLocalAccountType(@NonNull Context context) {
+ // config_rawContactsLocalAccountType is defined in
+ // platform/frameworks/base/core/res/res/values/config.xml
+ return TextUtils.nullIfEmpty(context.getString(
+ com.android.internal.R.string.config_rawContactsLocalAccountType));
+ }
+
+ /**
* A sub-directory of a single raw contact that contains all of its
* {@link ContactsContract.Data} rows. To access this directory
* append {@link Data#CONTENT_DIRECTORY} to the raw contact URI.
@@ -4148,7 +4196,10 @@
* Hash id on the data fields, used for backup and restore.
*
* @hide
+ * @deprecated This column is no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
public static final String HASH_ID = "hash_id";
/**
@@ -9455,7 +9506,10 @@
/**
* @hide
+ * @deprecated These columns are no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
@SystemApi
protected interface MetadataSyncColumns {
@@ -9562,7 +9616,10 @@
* from server before it is merged into other CP2 tables.
*
* @hide
+ * @deprecated These columns are no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
@SystemApi
public static final class MetadataSync implements BaseColumns, MetadataSyncColumns {
@@ -9598,7 +9655,10 @@
/**
* @hide
+ * @deprecated These columns are no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
@SystemApi
protected interface MetadataSyncStateColumns {
@@ -9632,7 +9692,10 @@
* sync state for a set of accounts.
*
* @hide
+ * @deprecated These columns are no longer supported as of Android version
+ * {@link android.os.Build.VERSION_CODES#R}.
*/
+ @Deprecated
@SystemApi
public static final class MetadataSyncState implements BaseColumns, MetadataSyncStateColumns {
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index abf34ca..6650cf2 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -40,6 +40,7 @@
import com.android.internal.util.Preconditions;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -349,6 +350,15 @@
public static final String NAMESPACE_PRIVACY = "privacy";
/**
+ * Namespace for biometrics related features
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final String NAMESPACE_BIOMETRICS = "biometrics";
+
+ /**
* Permission related properties definitions.
*
* @hide
@@ -357,6 +367,13 @@
@TestApi
public static final String NAMESPACE_PERMISSIONS = "permissions";
+ /**
+ * Namespace for all widget related features.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_WIDGET = "widget";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
@@ -571,11 +588,13 @@
* none or all of this update is picked up, but never only part of it.
*
* @param properties the complete set of properties to set for a specific namespace.
+ * @throws BadConfigException if the provided properties are banned by RescueParty.
+ * @return True if the values were set, false otherwise.
* @hide
*/
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
- public static boolean setProperties(@NonNull Properties properties) {
+ public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
return Settings.Config.setStrings(contentResolver, properties.getNamespace(),
properties.mMap);
@@ -725,19 +744,19 @@
List<String> pathSegments = uri.getPathSegments();
// pathSegments(0) is "config"
final String namespace = pathSegments.get(1);
- Map<String, String> propertyMap = new ArrayMap<>();
+ Properties.Builder propBuilder = new Properties.Builder(namespace);
try {
Properties allProperties = getProperties(namespace);
for (int i = 2; i < pathSegments.size(); ++i) {
String key = pathSegments.get(i);
- propertyMap.put(key, allProperties.getString(key, null));
+ propBuilder.setString(key, allProperties.getString(key, null));
}
} catch (SecurityException e) {
// Silently failing to not crash binder or listener threads.
Log.e(TAG, "OnPropertyChangedListener update failed: permission violation.");
return;
}
- Properties properties = new Properties(namespace, propertyMap);
+ Properties properties = propBuilder.build();
synchronized (sLock) {
for (int i = 0; i < sListeners.size(); i++) {
@@ -792,6 +811,15 @@
}
/**
+ * Thrown by {@link #setProperties(Properties)} when a configuration is rejected. This
+ * happens if RescueParty has identified a bad configuration and reset the namespace.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static class BadConfigException extends Exception {}
+
+ /**
* A mapping of properties to values, as well as a single namespace which they all belong to.
*
* @hide
@@ -801,6 +829,7 @@
public static class Properties {
private final String mNamespace;
private final HashMap<String, String> mMap;
+ private Set<String> mKeyset;
/**
* Create a mapping of properties to values and the namespace they belong to.
@@ -831,7 +860,10 @@
*/
@NonNull
public Set<String> getKeyset() {
- return mMap.keySet();
+ if (mKeyset == null) {
+ mKeyset = Collections.unmodifiableSet(mMap.keySet());
+ }
+ return mKeyset;
}
/**
@@ -926,5 +958,93 @@
return defaultValue;
}
}
+
+ /**
+ * Builder class for the construction of {@link Properties} objects.
+ */
+ public static final class Builder {
+ @NonNull
+ private final String mNamespace;
+ @NonNull
+ private final Map<String, String> mKeyValues = new HashMap<>();
+
+ /**
+ * Create a new Builders for the specified namespace.
+ * @param namespace non null namespace.
+ */
+ public Builder(@NonNull String namespace) {
+ mNamespace = namespace;
+ }
+
+ /**
+ * Add a new property with the specified key and value.
+ * @param name non null name of the property.
+ * @param value nullable string value of the property.
+ * @return this Builder object
+ */
+ @NonNull
+ public Builder setString(@NonNull String name, @Nullable String value) {
+ mKeyValues.put(name, value);
+ return this;
+ }
+
+ /**
+ * Add a new property with the specified key and value.
+ * @param name non null name of the property.
+ * @param value nullable string value of the property.
+ * @return this Builder object
+ */
+ @NonNull
+ public Builder setBoolean(@NonNull String name, boolean value) {
+ mKeyValues.put(name, Boolean.toString(value));
+ return this;
+ }
+
+ /**
+ * Add a new property with the specified key and value.
+ * @param name non null name of the property.
+ * @param value int value of the property.
+ * @return this Builder object
+ */
+ @NonNull
+ public Builder setInt(@NonNull String name, int value) {
+ mKeyValues.put(name, Integer.toString(value));
+ return this;
+ }
+
+ /**
+ * Add a new property with the specified key and value.
+ * @param name non null name of the property.
+ * @param value long value of the property.
+ * @return this Builder object
+ */
+ @NonNull
+ public Builder setLong(@NonNull String name, long value) {
+ mKeyValues.put(name, Long.toString(value));
+ return this;
+ }
+
+ /**
+ * Add a new property with the specified key and value.
+ * @param name non null name of the property.
+ * @param value float value of the property.
+ * @return this Builder object
+ */
+ @NonNull
+ public Builder setFloat(@NonNull String name, float value) {
+ mKeyValues.put(name, Float.toString(value));
+ return this;
+ }
+
+ /**
+ * Create a new {@link Properties} object.
+ * @return non null Properties.
+ */
+ @NonNull
+ public Properties build() {
+ return new Properties(mNamespace, mKeyValues);
+ }
+ }
}
+
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
deleted file mode 100644
index 2fa3386..0000000
--- a/core/java/android/provider/MediaStore.java
+++ /dev/null
@@ -1,4039 +0,0 @@
-/*
- * Copyright (C) 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 android.provider;
-
-import android.annotation.BytesLong;
-import android.annotation.CurrentTimeMillisLong;
-import android.annotation.CurrentTimeSecondsLong;
-import android.annotation.DurationMillisLong;
-import android.annotation.IntDef;
-import android.annotation.IntRange;
-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.annotation.UnsupportedAppUsage;
-import android.app.Activity;
-import android.app.AppGlobals;
-import android.content.ClipData;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.UriPermission;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.ImageDecoder;
-import android.graphics.Point;
-import android.graphics.PostProcessor;
-import android.media.ExifInterface;
-import android.media.MediaFile;
-import android.media.MediaFormat;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.OperationCanceledException;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
-import android.os.storage.VolumeInfo;
-import android.os.storage.VolumeRecord;
-import android.service.media.CameraPrewarmService;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import libcore.util.HexEncoding;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-/**
- * The contract between the media provider and applications. Contains
- * definitions for the supported URIs and columns.
- * <p>
- * The media provider provides an indexed collection of common media types, such
- * as {@link Audio}, {@link Video}, and {@link Images}, from any attached
- * storage devices. Each collection is organized based on the primary MIME type
- * of the underlying content; for example, {@code image/*} content is indexed
- * under {@link Images}. The {@link Files} collection provides a broad view
- * across all collections, and does not filter by MIME type.
- */
-public final class MediaStore {
- private final static String TAG = "MediaStore";
-
- /** The authority for the media provider */
- public static final String AUTHORITY = "media";
- /** A content:// style uri to the authority for the media provider */
- public static final @NonNull Uri AUTHORITY_URI =
- Uri.parse("content://" + AUTHORITY);
-
- /** @hide */
- public static final String AUTHORITY_LEGACY = "media_legacy";
- /** @hide */
- public static final @NonNull Uri AUTHORITY_LEGACY_URI =
- Uri.parse("content://" + AUTHORITY_LEGACY);
-
- /**
- * Synthetic volume name that provides a view of all content across the
- * "internal" storage of the device.
- * <p>
- * This synthetic volume provides a merged view of all media distributed
- * with the device, such as built-in ringtones and wallpapers.
- * <p>
- * Because this is a synthetic volume, you can't insert new content into
- * this volume.
- */
- public static final String VOLUME_INTERNAL = "internal";
-
- /**
- * Synthetic volume name that provides a view of all content across the
- * "external" storage of the device.
- * <p>
- * This synthetic volume provides a merged view of all media across all
- * currently attached external storage devices.
- * <p>
- * Because this is a synthetic volume, you can't insert new content into
- * this volume. Instead, you can insert content into a specific storage
- * volume obtained from {@link #getExternalVolumeNames(Context)}.
- */
- public static final String VOLUME_EXTERNAL = "external";
-
- /**
- * Specific volume name that represents the primary external storage device
- * at {@link Environment#getExternalStorageDirectory()}.
- * <p>
- * This volume may not always be available, such as when the user has
- * ejected the device. You can find a list of all specific volume names
- * using {@link #getExternalVolumeNames(Context)}.
- */
- public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary";
-
- /** {@hide} */
- public static final String WAIT_FOR_IDLE_CALL = "wait_for_idle";
- /** {@hide} */
- public static final String SCAN_FILE_CALL = "scan_file";
- /** {@hide} */
- public static final String SCAN_VOLUME_CALL = "scan_volume";
- /** {@hide} */
- public static final String SUICIDE_CALL = "suicide";
-
- /**
- * Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that
- * the file path originated from shell.
- *
- * {@hide}
- */
- public static final String EXTRA_ORIGINATED_FROM_SHELL =
- "android.intent.extra.originated_from_shell";
-
- /**
- * The method name used by the media scanner and mtp to tell the media provider to
- * rescan and reclassify that have become unhidden because of renaming folders or
- * removing nomedia files
- * @hide
- */
- @Deprecated
- public static final String UNHIDE_CALL = "unhide";
-
- /**
- * The method name used by the media scanner service to reload all localized ringtone titles due
- * to a locale change.
- * @hide
- */
- public static final String RETRANSLATE_CALL = "update_titles";
-
- /** {@hide} */
- public static final String GET_VERSION_CALL = "get_version";
- /** {@hide} */
- public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
- /** {@hide} */
- public static final String GET_MEDIA_URI_CALL = "get_media_uri";
-
- /** {@hide} */
- public static final String GET_CONTRIBUTED_MEDIA_CALL = "get_contributed_media";
- /** {@hide} */
- public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media";
-
- /**
- * This is for internal use by the media scanner only.
- * Name of the (optional) Uri parameter that determines whether to skip deleting
- * the file pointed to by the _data column, when deleting the database entry.
- * The only appropriate value for this parameter is "false", in which case the
- * delete will be skipped. Note especially that setting this to true, or omitting
- * the parameter altogether, will perform the default action, which is different
- * for different types of media.
- * @hide
- */
- public static final String PARAM_DELETE_DATA = "deletedata";
-
- /** {@hide} */
- @Deprecated
- public static final String PARAM_INCLUDE_PENDING = "includePending";
- /** {@hide} */
- @Deprecated
- public static final String PARAM_INCLUDE_TRASHED = "includeTrashed";
- /** {@hide} */
- public static final String PARAM_PROGRESS = "progress";
- /** {@hide} */
- public static final String PARAM_REQUIRE_ORIGINAL = "requireOriginal";
- /** {@hide} */
- public static final String PARAM_LIMIT = "limit";
-
- /**
- * Activity Action: Launch a music player.
- * The activity should be able to play, browse, or manipulate music files stored on the device.
- *
- * @deprecated Use {@link android.content.Intent#CATEGORY_APP_MUSIC} instead.
- */
- @Deprecated
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER";
-
- /**
- * Activity Action: Perform a search for media.
- * Contains at least the {@link android.app.SearchManager#QUERY} extra.
- * May also contain any combination of the following extras:
- * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS
- *
- * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST
- * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM
- * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE
- * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
-
- /**
- * An intent to perform a search for music media and automatically play content from the
- * result when possible. This can be fired, for example, by the result of a voice recognition
- * command to listen to music.
- * <p>This intent always includes the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS}
- * and {@link android.app.SearchManager#QUERY} extras. The
- * {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra determines the search mode, and
- * the value of the {@link android.app.SearchManager#QUERY} extra depends on the search mode.
- * For more information about the search modes for this intent, see
- * <a href="{@docRoot}guide/components/intents-common.html#PlaySearch">Play music based
- * on a search query</a> in <a href="{@docRoot}guide/components/intents-common.html">Common
- * Intents</a>.</p>
- *
- * <p>This intent makes the most sense for apps that can support large-scale search of music,
- * such as services connected to an online database of music which can be streamed and played
- * on the device.</p>
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
- "android.media.action.MEDIA_PLAY_FROM_SEARCH";
-
- /**
- * An intent to perform a search for readable media and automatically play content from the
- * result when possible. This can be fired, for example, by the result of a voice recognition
- * command to read a book or magazine.
- * <p>
- * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
- * contain any type of unstructured text search, like the name of a book or magazine, an author
- * a genre, a publisher, or any combination of these.
- * <p>
- * Because this intent includes an open-ended unstructured search string, it makes the most
- * sense for apps that can support large-scale search of text media, such as services connected
- * to an online database of books and/or magazines which can be read on the device.
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH =
- "android.media.action.TEXT_OPEN_FROM_SEARCH";
-
- /**
- * An intent to perform a search for video media and automatically play content from the
- * result when possible. This can be fired, for example, by the result of a voice recognition
- * command to play movies.
- * <p>
- * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
- * contain any type of unstructured video search, like the name of a movie, one or more actors,
- * a genre, or any combination of these.
- * <p>
- * Because this intent includes an open-ended unstructured search string, it makes the most
- * sense for apps that can support large-scale search of video, such as services connected to an
- * online database of videos which can be streamed and played on the device.
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH =
- "android.media.action.VIDEO_PLAY_FROM_SEARCH";
-
- /**
- * The name of the Intent-extra used to define the artist
- */
- public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
- /**
- * The name of the Intent-extra used to define the album
- */
- public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album";
- /**
- * The name of the Intent-extra used to define the song title
- */
- public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
- /**
- * The name of the Intent-extra used to define the genre.
- */
- public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre";
- /**
- * The name of the Intent-extra used to define the playlist.
- */
- public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist";
- /**
- * The name of the Intent-extra used to define the radio channel.
- */
- public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel";
- /**
- * The name of the Intent-extra used to define the search focus. The search focus
- * indicates whether the search should be for things related to the artist, album
- * or song that is identified by the other extras.
- */
- public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus";
-
- /**
- * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView.
- * This is an int property that overrides the activity's requestedOrientation.
- * @see android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED
- */
- public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
-
- /**
- * The name of an Intent-extra used to control the UI of a ViewImage.
- * This is a boolean property that overrides the activity's default fullscreen state.
- */
- public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen";
-
- /**
- * The name of an Intent-extra used to control the UI of a ViewImage.
- * This is a boolean property that specifies whether or not to show action icons.
- */
- public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
-
- /**
- * The name of the Intent-extra used to control the onCompletion behavior of a MovieView.
- * This is a boolean property that specifies whether or not to finish the MovieView activity
- * when the movie completes playing. The default value is true, which means to automatically
- * exit the movie player activity when the movie completes playing.
- */
- public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion";
-
- /**
- * The name of the Intent action used to launch a camera in still image mode.
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
-
- /**
- * Name under which an activity handling {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or
- * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE} publishes the service name for its prewarm
- * service.
- * <p>
- * This meta-data should reference the fully qualified class name of the prewarm service
- * extending {@link CameraPrewarmService}.
- * <p>
- * The prewarm service will get bound and receive a prewarm signal
- * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
- * An application implementing a prewarm service should do the absolute minimum amount of work
- * to initialize the camera in order to reduce startup time in likely case that shortly after a
- * camera launch intent would be sent.
- */
- public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE =
- "android.media.still_image_camera_preview_service";
-
- /**
- * The name of the Intent action used to launch a camera in still image mode
- * for use when the device is secured (e.g. with a pin, password, pattern,
- * or face unlock). Applications responding to this intent must not expose
- * any personal content like existing photos or videos on the device. The
- * applications should be careful not to share any photo or video with other
- * applications or internet. The activity should use {@link
- * Activity#setShowWhenLocked} to display
- * on top of the lock screen while secured. There is no activity stack when
- * this flag is used, so launching more than one activity is strongly
- * discouraged.
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
- "android.media.action.STILL_IMAGE_CAMERA_SECURE";
-
- /**
- * The name of the Intent action used to launch a camera in video mode.
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA";
-
- /**
- * Standard Intent action that can be sent to have the camera application
- * capture an image and return it.
- * <p>
- * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
- * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
- * object in the extra field. This is useful for applications that only need a small image.
- * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
- * value of EXTRA_OUTPUT.
- * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
- * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
- * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
- * If you don't set a ClipData, it will be copied there for you when calling
- * {@link Context#startActivity(Intent)}.
- *
- * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above
- * and declares as using the {@link android.Manifest.permission#CAMERA} permission which
- * is not granted, then attempting to use this action will result in a {@link
- * java.lang.SecurityException}.
- *
- * @see #EXTRA_OUTPUT
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
-
- /**
- * Intent action that can be sent to have the camera application capture an image and return
- * it when the device is secured (e.g. with a pin, password, pattern, or face unlock).
- * Applications responding to this intent must not expose any personal content like existing
- * photos or videos on the device. The applications should be careful not to share any photo
- * or video with other applications or Internet. The activity should use {@link
- * Activity#setShowWhenLocked} to display on top of the
- * lock screen while secured. There is no activity stack when this flag is used, so
- * launching more than one activity is strongly discouraged.
- * <p>
- * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
- * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
- * object in the extra field. This is useful for applications that only need a small image.
- * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
- * value of EXTRA_OUTPUT.
- * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
- * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
- * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
- * If you don't set a ClipData, it will be copied there for you when calling
- * {@link Context#startActivity(Intent)}.
- *
- * @see #ACTION_IMAGE_CAPTURE
- * @see #EXTRA_OUTPUT
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_IMAGE_CAPTURE_SECURE =
- "android.media.action.IMAGE_CAPTURE_SECURE";
-
- /**
- * Standard Intent action that can be sent to have the camera application
- * capture a video and return it.
- * <p>
- * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality.
- * <p>
- * The caller may pass in an extra EXTRA_OUTPUT to control
- * where the video is written. If EXTRA_OUTPUT is not present the video will be
- * written to the standard location for videos, and the Uri of that location will be
- * returned in the data field of the Uri.
- * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
- * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
- * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
- * If you don't set a ClipData, it will be copied there for you when calling
- * {@link Context#startActivity(Intent)}.
- *
- * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above
- * and declares as using the {@link android.Manifest.permission#CAMERA} permission which
- * is not granted, then atempting to use this action will result in a {@link
- * java.lang.SecurityException}.
- *
- * @see #EXTRA_OUTPUT
- * @see #EXTRA_VIDEO_QUALITY
- * @see #EXTRA_SIZE_LIMIT
- * @see #EXTRA_DURATION_LIMIT
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
-
- /**
- * Standard action that can be sent to review the given media file.
- * <p>
- * The launched application is expected to provide a large-scale view of the
- * given media file, while allowing the user to quickly access other
- * recently captured media files.
- * <p>
- * Input: {@link Intent#getData} is URI of the primary media item to
- * initially display.
- *
- * @see #ACTION_REVIEW_SECURE
- * @see #EXTRA_BRIGHTNESS
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public final static String ACTION_REVIEW = "android.provider.action.REVIEW";
-
- /**
- * Standard action that can be sent to review the given media file when the
- * device is secured (e.g. with a pin, password, pattern, or face unlock).
- * The applications should be careful not to share any media with other
- * applications or Internet. The activity should use
- * {@link Activity#setShowWhenLocked} to display on top of the lock screen
- * while secured. There is no activity stack when this flag is used, so
- * launching more than one activity is strongly discouraged.
- * <p>
- * The launched application is expected to provide a large-scale view of the
- * given primary media file, while only allowing the user to quickly access
- * other media from an explicit secondary list.
- * <p>
- * Input: {@link Intent#getData} is URI of the primary media item to
- * initially display. {@link Intent#getClipData} is the limited list of
- * secondary media items that the user is allowed to review. If
- * {@link Intent#getClipData} is undefined, then no other media access
- * should be allowed.
- *
- * @see #EXTRA_BRIGHTNESS
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public final static String ACTION_REVIEW_SECURE = "android.provider.action.REVIEW_SECURE";
-
- /**
- * When defined, the launched application is requested to set the given
- * brightness value via
- * {@link android.view.WindowManager.LayoutParams#screenBrightness} to help
- * ensure a smooth transition when launching {@link #ACTION_REVIEW} or
- * {@link #ACTION_REVIEW_SECURE} intents.
- */
- public final static String EXTRA_BRIGHTNESS = "android.provider.extra.BRIGHTNESS";
-
- /**
- * The name of the Intent-extra used to control the quality of a recorded video. This is an
- * integer property. Currently value 0 means low quality, suitable for MMS messages, and
- * value 1 means high quality. In the future other quality levels may be added.
- */
- public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality";
-
- /**
- * Specify the maximum allowed size.
- */
- public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
-
- /**
- * Specify the maximum allowed recording duration in seconds.
- */
- public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
-
- /**
- * The name of the Intent-extra used to indicate a content resolver Uri to be used to
- * store the requested image or video.
- */
- public final static String EXTRA_OUTPUT = "output";
-
- /**
- * The string that is used when a media attribute is not known. For example,
- * if an audio file does not have any meta data, the artist and album columns
- * will be set to this value.
- */
- public static final String UNKNOWN_STRING = "<unknown>";
-
- /**
- * Specify a {@link Uri} that is "related" to the current operation being
- * performed.
- * <p>
- * This is typically used to allow an operation that may normally be
- * rejected, such as making a copy of a pre-existing image located under a
- * {@link MediaColumns#RELATIVE_PATH} where new images are not allowed.
- * <p>
- * It's strongly recommended that when making a copy of pre-existing content
- * that you define the "original document ID" GUID as defined by the <em>XMP
- * Media Management</em> standard.
- * <p>
- * This key can be placed in a {@link Bundle} of extras and passed to
- * {@link ContentResolver#insert}.
- */
- public static final String QUERY_ARG_RELATED_URI = "android:query-arg-related-uri";
-
- /**
- * Specify how {@link MediaColumns#IS_PENDING} items should be filtered when
- * performing a {@link MediaStore} operation.
- * <p>
- * This key can be placed in a {@link Bundle} of extras and passed to
- * {@link ContentResolver#query}, {@link ContentResolver#update}, or
- * {@link ContentResolver#delete}.
- * <p>
- * By default, pending items are filtered away from operations.
- */
- @Match
- public static final String QUERY_ARG_MATCH_PENDING = "android:query-arg-match-pending";
-
- /**
- * Specify how {@link MediaColumns#IS_TRASHED} items should be filtered when
- * performing a {@link MediaStore} operation.
- * <p>
- * This key can be placed in a {@link Bundle} of extras and passed to
- * {@link ContentResolver#query}, {@link ContentResolver#update}, or
- * {@link ContentResolver#delete}.
- * <p>
- * By default, trashed items are filtered away from operations.
- */
- @Match
- public static final String QUERY_ARG_MATCH_TRASHED = "android:query-arg-match-trashed";
-
- /**
- * Specify how {@link MediaColumns#IS_FAVORITE} items should be filtered
- * when performing a {@link MediaStore} operation.
- * <p>
- * This key can be placed in a {@link Bundle} of extras and passed to
- * {@link ContentResolver#query}, {@link ContentResolver#update}, or
- * {@link ContentResolver#delete}.
- * <p>
- * By default, favorite items are <em>not</em> filtered away from
- * operations.
- */
- @Match
- public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
-
- /** @hide */
- @IntDef(flag = true, prefix = { "MATCH_" }, value = {
- MATCH_DEFAULT,
- MATCH_INCLUDE,
- MATCH_EXCLUDE,
- MATCH_ONLY,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Match {}
-
- /**
- * Value indicating that the default matching behavior should be used, as
- * defined by the key documentation.
- */
- public static final int MATCH_DEFAULT = 0;
-
- /**
- * Value indicating that operations should include items matching the
- * criteria defined by this key.
- * <p>
- * Note that items <em>not</em> matching the criteria <em>may</em> also be
- * included depending on the default behavior documented by the key. If you
- * want to operate exclusively on matching items, use {@link #MATCH_ONLY}.
- */
- public static final int MATCH_INCLUDE = 1;
-
- /**
- * Value indicating that operations should exclude items matching the
- * criteria defined by this key.
- */
- public static final int MATCH_EXCLUDE = 2;
-
- /**
- * Value indicating that operations should only operate on items explicitly
- * matching the criteria defined by this key.
- */
- public static final int MATCH_ONLY = 3;
-
- /**
- * Update the given {@link Uri} to also include any pending media items from
- * calls such as
- * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
- * By default no pending items are returned.
- *
- * @see MediaColumns#IS_PENDING
- * @see MediaStore#getIncludePending(Uri)
- * @deprecated consider migrating to {@link #QUERY_ARG_MATCH_PENDING} which
- * is more expressive.
- */
- @Deprecated
- public static @NonNull Uri setIncludePending(@NonNull Uri uri) {
- return setIncludePending(uri.buildUpon()).build();
- }
-
- /** @hide */
- @Deprecated
- public static @NonNull Uri.Builder setIncludePending(@NonNull Uri.Builder uriBuilder) {
- return uriBuilder.appendQueryParameter(PARAM_INCLUDE_PENDING, "1");
- }
-
- /**
- * Return if any pending media items should be included in calls such as
- * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
- *
- * @see MediaColumns#IS_PENDING
- * @see MediaStore#setIncludePending(Uri)
- * @deprecated consider migrating to {@link #QUERY_ARG_MATCH_PENDING} which
- * is more expressive.
- * @removed
- */
- @Deprecated
- public static boolean getIncludePending(@NonNull Uri uri) {
- return parseBoolean(uri.getQueryParameter(MediaStore.PARAM_INCLUDE_PENDING));
- }
-
- /**
- * Update the given {@link Uri} to also include any trashed media items from
- * calls such as
- * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
- * By default no trashed items are returned.
- *
- * @see MediaColumns#IS_TRASHED
- * @see MediaStore#setIncludeTrashed(Uri)
- * @see MediaStore#trash(Context, Uri)
- * @see MediaStore#untrash(Context, Uri)
- * @deprecated consider migrating to {@link #QUERY_ARG_MATCH_TRASHED} which
- * is more expressive.
- * @removed
- */
- @Deprecated
- public static @NonNull Uri setIncludeTrashed(@NonNull Uri uri) {
- return uri.buildUpon().appendQueryParameter(PARAM_INCLUDE_TRASHED, "1").build();
- }
-
- /**
- * Update the given {@link Uri} to indicate that the caller requires the
- * original file contents when calling
- * {@link ContentResolver#openFileDescriptor(Uri, String)}.
- * <p>
- * This can be useful when the caller wants to ensure they're backing up the
- * exact bytes of the underlying media, without any Exif redaction being
- * performed.
- * <p>
- * If the original file contents cannot be provided, a
- * {@link UnsupportedOperationException} will be thrown when the returned
- * {@link Uri} is used, such as when the caller doesn't hold
- * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}.
- *
- * @see MediaStore#getRequireOriginal(Uri)
- */
- public static @NonNull Uri setRequireOriginal(@NonNull Uri uri) {
- return uri.buildUpon().appendQueryParameter(PARAM_REQUIRE_ORIGINAL, "1").build();
- }
-
- /**
- * Return if the caller requires the original file contents when calling
- * {@link ContentResolver#openFileDescriptor(Uri, String)}.
- *
- * @see MediaStore#setRequireOriginal(Uri)
- */
- public static boolean getRequireOriginal(@NonNull Uri uri) {
- return parseBoolean(uri.getQueryParameter(MediaStore.PARAM_REQUIRE_ORIGINAL));
- }
-
- /**
- * Create a new pending media item using the given parameters. Pending items
- * are expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @return token which can be passed to {@link #openPending(Context, Uri)}
- * to work with this pending item.
- * @see MediaColumns#IS_PENDING
- * @see MediaStore#setIncludePending(Uri)
- * @see MediaStore#createPending(Context, PendingParams)
- * @removed
- */
- @Deprecated
- public static @NonNull Uri createPending(@NonNull Context context,
- @NonNull PendingParams params) {
- return context.getContentResolver().insert(params.insertUri, params.insertValues);
- }
-
- /**
- * Open a pending media item to make progress on it. You can open a pending
- * item multiple times before finally calling either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
- *
- * @param uri token which was previously returned from
- * {@link #createPending(Context, PendingParams)}.
- * @removed
- */
- @Deprecated
- public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
- return new PendingSession(context, uri);
- }
-
- /**
- * Parameters that describe a pending media item.
- *
- * @removed
- */
- @Deprecated
- public static class PendingParams {
- /** {@hide} */
- public final Uri insertUri;
- /** {@hide} */
- public final ContentValues insertValues;
-
- /**
- * Create parameters that describe a pending media item.
- *
- * @param insertUri the {@code content://} Uri where this pending item
- * should be inserted when finally published. For example, to
- * publish an image, use
- * {@link MediaStore.Images.Media#getContentUri(String)}.
- */
- public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
- @NonNull String mimeType) {
- this.insertUri = Objects.requireNonNull(insertUri);
- final long now = System.currentTimeMillis() / 1000;
- this.insertValues = new ContentValues();
- this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
- this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
- this.insertValues.put(MediaColumns.DATE_ADDED, now);
- this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
- this.insertValues.put(MediaColumns.IS_PENDING, 1);
- this.insertValues.put(MediaColumns.DATE_EXPIRES,
- (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
- }
-
- public void setRelativePath(@Nullable String relativePath) {
- if (relativePath == null) {
- this.insertValues.remove(MediaColumns.RELATIVE_PATH);
- } else {
- this.insertValues.put(MediaColumns.RELATIVE_PATH, relativePath);
- }
- }
-
- /**
- * Optionally set the Uri from where the file has been downloaded. This is used
- * for files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#DOWNLOAD_URI
- */
- public void setDownloadUri(@Nullable Uri downloadUri) {
- if (downloadUri == null) {
- this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
- } else {
- this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
- }
- }
-
- /**
- * Optionally set the Uri indicating HTTP referer of the file. This is used for
- * files being added to {@link Downloads} table.
- *
- * @see DownloadColumns#REFERER_URI
- */
- public void setRefererUri(@Nullable Uri refererUri) {
- if (refererUri == null) {
- this.insertValues.remove(DownloadColumns.REFERER_URI);
- } else {
- this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
- }
- }
- }
-
- /**
- * Session actively working on a pending media item. Pending items are
- * expected to have a short lifetime, and owners should either
- * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
- * pending item within a few hours after first creating it.
- *
- * @removed
- */
- @Deprecated
- public static class PendingSession implements AutoCloseable {
- /** {@hide} */
- private final Context mContext;
- /** {@hide} */
- private final Uri mUri;
-
- /** {@hide} */
- public PendingSession(Context context, Uri uri) {
- mContext = Objects.requireNonNull(context);
- mUri = Objects.requireNonNull(uri);
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
- return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
- }
-
- /**
- * Open the underlying file representing this media item. When a media
- * item is successfully completed, you should
- * {@link OutputStream#close()} and then {@link #publish()} it.
- *
- * @see #notifyProgress(int)
- */
- public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
- return mContext.getContentResolver().openOutputStream(mUri);
- }
-
- /**
- * Notify of current progress on this pending media item. Gallery
- * applications may choose to surface progress information of this
- * pending item.
- *
- * @param progress a percentage between 0 and 100.
- */
- public void notifyProgress(@IntRange(from = 0, to = 100) int progress) {
- final Uri withProgress = mUri.buildUpon()
- .appendQueryParameter(PARAM_PROGRESS, Integer.toString(progress)).build();
- mContext.getContentResolver().notifyChange(withProgress, null, 0);
- }
-
- /**
- * When this media item is successfully completed, call this method to
- * publish and make the final item visible to the user.
- *
- * @return the final {@code content://} Uri representing the newly
- * published media.
- */
- public @NonNull Uri publish() {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.IS_PENDING, 0);
- values.putNull(MediaColumns.DATE_EXPIRES);
- mContext.getContentResolver().update(mUri, values, null, null);
- return mUri;
- }
-
- /**
- * When this media item has failed to be completed, call this method to
- * destroy the pending item record and any data related to it.
- */
- public void abandon() {
- mContext.getContentResolver().delete(mUri, null, null);
- }
-
- @Override
- public void close() {
- // No resources to close, but at least we can inform people that no
- // progress is being actively made.
- notifyProgress(-1);
- }
- }
-
- /**
- * Mark the given item as being "trashed", meaning it should be deleted at
- * some point in the future. This is a more gentle operation than simply
- * calling {@link ContentResolver#delete(Uri, String, String[])}, which
- * would take effect immediately.
- * <p>
- * This method preserves trashed items for at least 48 hours before erasing
- * them, giving the user a chance to untrash the item.
- *
- * @see MediaColumns#IS_TRASHED
- * @see MediaStore#setIncludeTrashed(Uri)
- * @see MediaStore#trash(Context, Uri)
- * @see MediaStore#untrash(Context, Uri)
- */
- public static void trash(@NonNull Context context, @NonNull Uri uri) {
- trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS);
- }
-
- /**
- * Mark the given item as being "trashed", meaning it should be deleted at
- * some point in the future. This is a more gentle operation than simply
- * calling {@link ContentResolver#delete(Uri, String, String[])}, which
- * would take effect immediately.
- * <p>
- * This method preserves trashed items for at least the given timeout before
- * erasing them, giving the user a chance to untrash the item.
- *
- * @see MediaColumns#IS_TRASHED
- * @see MediaStore#setIncludeTrashed(Uri)
- * @see MediaStore#trash(Context, Uri)
- * @see MediaStore#untrash(Context, Uri)
- */
- public static void trash(@NonNull Context context, @NonNull Uri uri,
- @DurationMillisLong long timeoutMillis) {
- if (timeoutMillis < 0) {
- throw new IllegalArgumentException();
- }
-
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.IS_TRASHED, 1);
- values.put(MediaColumns.DATE_EXPIRES,
- (System.currentTimeMillis() + timeoutMillis) / 1000);
- context.getContentResolver().update(uri, values, null, null);
- }
-
- /**
- * Mark the given item as being "untrashed", meaning it should no longer be
- * deleted as previously requested through {@link #trash(Context, Uri)}.
- *
- * @see MediaColumns#IS_TRASHED
- * @see MediaStore#setIncludeTrashed(Uri)
- * @see MediaStore#trash(Context, Uri)
- * @see MediaStore#untrash(Context, Uri)
- */
- public static void untrash(@NonNull Context context, @NonNull Uri uri) {
- final ContentValues values = new ContentValues();
- values.put(MediaColumns.IS_TRASHED, 0);
- values.putNull(MediaColumns.DATE_EXPIRES);
- context.getContentResolver().update(uri, values, null, null);
- }
-
- /**
- * Rewrite the given {@link Uri} to point at
- * {@link MediaStore#AUTHORITY_LEGACY}.
- *
- * @hide
- */
- public static @NonNull Uri rewriteToLegacy(@NonNull Uri uri) {
- return uri.buildUpon().authority(MediaStore.AUTHORITY_LEGACY).build();
- }
-
- /**
- * Common media metadata columns.
- */
- public interface MediaColumns extends BaseColumns {
- /**
- * Absolute filesystem path to the media item on disk.
- * <p>
- * Note that apps may not have filesystem permissions to directly access
- * this path. Instead of trying to open this path directly, apps should
- * use {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
- * access.
- *
- * @deprecated Apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path
- * directly, apps should use
- * {@link ContentResolver#openFileDescriptor(Uri, String)}
- * to gain access.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String DATA = "_data";
-
- /**
- * Indexed value of {@link File#length()} extracted from this media
- * item.
- */
- @BytesLong
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String SIZE = "_size";
-
- /**
- * The display name of the media item.
- * <p>
- * For example, an item stored at
- * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a
- * display name of {@code IMG1024.JPG}.
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String DISPLAY_NAME = "_display_name";
-
- /**
- * The time the media item was first added.
- */
- @CurrentTimeSecondsLong
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String DATE_ADDED = "date_added";
-
- /**
- * Indexed value of {@link File#lastModified()} extracted from this
- * media item.
- */
- @CurrentTimeSecondsLong
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String DATE_MODIFIED = "date_modified";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DATE} or
- * {@link ExifInterface#TAG_DATETIME_ORIGINAL} extracted from this media
- * item.
- * <p>
- * Note that images must define both
- * {@link ExifInterface#TAG_DATETIME_ORIGINAL} and
- * {@code ExifInterface#TAG_OFFSET_TIME_ORIGINAL} to reliably determine
- * this value in relation to the epoch.
- */
- @CurrentTimeMillisLong
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String DATE_TAKEN = "datetaken";
-
- /**
- * The MIME type of the media item.
- * <p>
- * This is typically defined based on the file extension of the media
- * item. However, it may be the value of the {@code format} attribute
- * defined by the <em>Dublin Core Media Initiative</em> standard,
- * extracted from any XMP metadata contained within this media item.
- * <p class="note">
- * Note: the {@code format} attribute may be ignored if the top-level
- * MIME type disagrees with the file extension. For example, it's
- * reasonable for an {@code image/jpeg} file to declare a {@code format}
- * of {@code image/vnd.google.panorama360+jpg}, but declaring a
- * {@code format} of {@code audio/ogg} would be ignored.
- * <p>
- * This is a read-only column that is automatically computed.
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String MIME_TYPE = "mime_type";
-
- /**
- * Non-zero if the media file is drm-protected
- * @hide
- */
- @UnsupportedAppUsage
- @Deprecated
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String IS_DRM = "is_drm";
-
- /**
- * Flag indicating if a media item is pending, and still being inserted
- * by its owner. While this flag is set, only the owner of the item can
- * open the underlying file; requests from other apps will be rejected.
- * <p>
- * Pending items are retained either until they are published by setting
- * the field to {@code 0}, or until they expire as defined by
- * {@link #DATE_EXPIRES}.
- *
- * @see MediaStore#QUERY_ARG_MATCH_PENDING
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String IS_PENDING = "is_pending";
-
- /**
- * Flag indicating if a media item is trashed.
- * <p>
- * Trashed items are retained until they expire as defined by
- * {@link #DATE_EXPIRES}.
- *
- * @see MediaStore#QUERY_ARG_MATCH_TRASHED
- * @see MediaStore#trash(Context, Uri)
- * @see MediaStore#untrash(Context, Uri)
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String IS_TRASHED = "is_trashed";
-
- /**
- * The time the media item should be considered expired. Typically only
- * meaningful in the context of {@link #IS_PENDING} or
- * {@link #IS_TRASHED}.
- */
- @CurrentTimeSecondsLong
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String DATE_EXPIRES = "date_expires";
-
- /**
- * Indexed value of
- * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_WIDTH},
- * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_WIDTH} or
- * {@link ExifInterface#TAG_IMAGE_WIDTH} extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String WIDTH = "width";
-
- /**
- * Indexed value of
- * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_HEIGHT},
- * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_HEIGHT} or
- * {@link ExifInterface#TAG_IMAGE_LENGTH} extracted from this media
- * item.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String HEIGHT = "height";
-
- /**
- * Calculated value that combines {@link #WIDTH} and {@link #HEIGHT}
- * into a user-presentable string.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String RESOLUTION = "resolution";
-
- /**
- * Package name that contributed this media. The value may be
- * {@code NULL} if ownership cannot be reliably determined.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String OWNER_PACKAGE_NAME = "owner_package_name";
-
- /**
- * Volume name of the specific storage device where this media item is
- * persisted. The value is typically one of the volume names returned
- * from {@link MediaStore#getExternalVolumeNames(Context)}.
- * <p>
- * This is a read-only column that is automatically computed.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String VOLUME_NAME = "volume_name";
-
- /**
- * Relative path of this media item within the storage device where it
- * is persisted. For example, an item stored at
- * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a
- * path of {@code DCIM/Vacation/}.
- * <p>
- * This value should only be used for organizational purposes, and you
- * should not attempt to construct or access a raw filesystem path using
- * this value. If you need to open a media item, use an API like
- * {@link ContentResolver#openFileDescriptor(Uri, String)}.
- * <p>
- * When this value is set to {@code NULL} during an
- * {@link ContentResolver#insert} operation, the newly created item will
- * be placed in a relevant default location based on the type of media
- * being inserted. For example, a {@code image/jpeg} item will be placed
- * under {@link Environment#DIRECTORY_PICTURES}.
- * <p>
- * You can modify this column during an {@link ContentResolver#update}
- * call, which will move the underlying file on disk.
- * <p>
- * In both cases above, content must be placed under a top-level
- * directory that is relevant to the media type. For example, attempting
- * to place a {@code audio/mpeg} file under
- * {@link Environment#DIRECTORY_PICTURES} will be rejected.
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String RELATIVE_PATH = "relative_path";
-
- /**
- * The primary bucket ID of this media item. This can be useful to
- * present the user a first-level clustering of related media items.
- * This is a read-only column that is automatically computed.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String BUCKET_ID = "bucket_id";
-
- /**
- * The primary bucket display name of this media item. This can be
- * useful to present the user a first-level clustering of related
- * media items. This is a read-only column that is automatically
- * computed.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
-
- /**
- * The group ID of this media item. This can be useful to present
- * the user a grouping of related media items, such a burst of
- * images, or a {@code JPG} and {@code DNG} version of the same
- * image.
- * <p>
- * This is a read-only column that is automatically computed based
- * on the first portion of the filename. For example,
- * {@code IMG1024.BURST001.JPG} and {@code IMG1024.BURST002.JPG}
- * will have the same {@link #GROUP_ID} because the first portion of
- * their filenames is identical.
- *
- * @removed
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- @Deprecated
- public static final String GROUP_ID = "group_id";
-
- /**
- * The "document ID" GUID as defined by the <em>XMP Media
- * Management</em> standard, extracted from any XMP metadata contained
- * within this media item. The value is {@code null} when no metadata
- * was found.
- * <p>
- * Each "document ID" is created once for each new resource. Different
- * renditions of that resource are expected to have different IDs.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String DOCUMENT_ID = "document_id";
-
- /**
- * The "instance ID" GUID as defined by the <em>XMP Media
- * Management</em> standard, extracted from any XMP metadata contained
- * within this media item. The value is {@code null} when no metadata
- * was found.
- * <p>
- * This "instance ID" changes with each save operation of a specific
- * "document ID".
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String INSTANCE_ID = "instance_id";
-
- /**
- * The "original document ID" GUID as defined by the <em>XMP Media
- * Management</em> standard, extracted from any XMP metadata contained
- * within this media item.
- * <p>
- * This "original document ID" links a resource to its original source.
- * For example, when you save a PSD document as a JPEG, then convert the
- * JPEG to GIF format, the "original document ID" of both the JPEG and
- * GIF files is the "document ID" of the original PSD file.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ORIGINAL_DOCUMENT_ID = "original_document_id";
-
- /**
- * Indexed value of
- * {@link MediaMetadataRetriever#METADATA_KEY_VIDEO_ROTATION},
- * {@link MediaMetadataRetriever#METADATA_KEY_IMAGE_ROTATION}, or
- * {@link ExifInterface#TAG_ORIENTATION} extracted from this media item.
- * <p>
- * For consistency the indexed value is expressed in degrees, such as 0,
- * 90, 180, or 270.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String ORIENTATION = "orientation";
-
- /**
- * Flag indicating if the media item has been marked as being a
- * "favorite" by the user.
- *
- * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String IS_FAVORITE = "is_favorite";
-
- // =======================================
- // ==== MediaMetadataRetriever values ====
- // =======================================
-
- /**
- * Indexed value of
- * {@link MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER} extracted
- * from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String CD_TRACK_NUMBER = "cd_track_number";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ALBUM}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ALBUM = "album";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ARTIST}
- * or {@link ExifInterface#TAG_ARTIST} extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ARTIST = "artist";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_AUTHOR}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String AUTHOR = "author";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_COMPOSER}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String COMPOSER = "composer";
-
- // METADATA_KEY_DATE is DATE_TAKEN
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_GENRE}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String GENRE = "genre";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_TITLE}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String TITLE = "title";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_YEAR}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String YEAR = "year";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DURATION}
- * extracted from this media item.
- */
- @DurationMillisLong
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String DURATION = "duration";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_NUM_TRACKS}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String NUM_TRACKS = "num_tracks";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_WRITER}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String WRITER = "writer";
-
- // METADATA_KEY_MIMETYPE is MIME_TYPE
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ALBUM_ARTIST = "album_artist";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String DISC_NUMBER = "disc_number";
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_COMPILATION}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String COMPILATION = "compilation";
-
- // HAS_AUDIO is ignored
- // HAS_VIDEO is ignored
- // VIDEO_WIDTH is WIDTH
- // VIDEO_HEIGHT is HEIGHT
-
- /**
- * Indexed value of {@link MediaMetadataRetriever#METADATA_KEY_BITRATE}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String BITRATE = "bitrate";
-
- // TIMED_TEXT_LANGUAGES is ignored
- // IS_DRM is ignored
- // LOCATION is LATITUDE and LONGITUDE
- // VIDEO_ROTATION is ORIENTATION
-
- /**
- * Indexed value of
- * {@link MediaMetadataRetriever#METADATA_KEY_CAPTURE_FRAMERATE}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
- public static final String CAPTURE_FRAMERATE = "capture_framerate";
-
- // HAS_IMAGE is ignored
- // IMAGE_COUNT is ignored
- // IMAGE_PRIMARY is ignored
- // IMAGE_WIDTH is WIDTH
- // IMAGE_HEIGHT is HEIGHT
- // IMAGE_ROTATION is ORIENTATION
- // VIDEO_FRAME_COUNT is ignored
- // EXIF_OFFSET is ignored
- // EXIF_LENGTH is ignored
- // COLOR_STANDARD is ignored
- // COLOR_TRANSFER is ignored
- // COLOR_RANGE is ignored
- // SAMPLERATE is ignored
- // BITS_PER_SAMPLE is ignored
- }
-
- /**
- * Media provider table containing an index of all files in the media storage,
- * including non-media files. This should be used by applications that work with
- * non-media file types (text, HTML, PDF, etc) as well as applications that need to
- * work with multiple media file types in a single query.
- */
- public static final class Files {
- /** @hide */
- public static final String TABLE = "files";
-
- /** @hide */
- public static final Uri EXTERNAL_CONTENT_URI = getContentUri(VOLUME_EXTERNAL);
-
- /**
- * Get the content:// style URI for the files table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the files table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("file").build();
- }
-
- /**
- * Get the content:// style URI for a single row in the files table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @param rowId the file to get the URI for
- * @return the URI to the files table on the given volume
- */
- public static final Uri getContentUri(String volumeName,
- long rowId) {
- return ContentUris.withAppendedId(getContentUri(volumeName), rowId);
- }
-
- /**
- * For use only by the MTP implementation.
- * @hide
- */
- @UnsupportedAppUsage
- public static Uri getMtpObjectsUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("object").build();
- }
-
- /**
- * For use only by the MTP implementation.
- * @hide
- */
- @UnsupportedAppUsage
- public static final Uri getMtpObjectsUri(String volumeName,
- long fileId) {
- return ContentUris.withAppendedId(getMtpObjectsUri(volumeName), fileId);
- }
-
- /**
- * Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
- * @hide
- */
- @UnsupportedAppUsage
- public static final Uri getMtpReferencesUri(String volumeName,
- long fileId) {
- return getMtpObjectsUri(volumeName, fileId).buildUpon().appendPath("references")
- .build();
- }
-
- /**
- * Used to trigger special logic for directories.
- * @hide
- */
- public static final Uri getDirectoryUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("dir").build();
- }
-
- /** @hide */
- public static final Uri getContentUriForPath(String path) {
- return getContentUri(getVolumeName(new File(path)));
- }
-
- /**
- * File metadata columns.
- */
- public interface FileColumns extends MediaColumns {
- /**
- * The MTP storage ID of the file
- * @hide
- */
- @UnsupportedAppUsage
- @Deprecated
- // @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String STORAGE_ID = "storage_id";
-
- /**
- * The MTP format code of the file
- * @hide
- */
- @UnsupportedAppUsage
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String FORMAT = "format";
-
- /**
- * The index of the parent directory of the file
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String PARENT = "parent";
-
- /**
- * The MIME type of the media item.
- * <p>
- * This is typically defined based on the file extension of the media
- * item. However, it may be the value of the {@code format} attribute
- * defined by the <em>Dublin Core Media Initiative</em> standard,
- * extracted from any XMP metadata contained within this media item.
- * <p class="note">
- * Note: the {@code format} attribute may be ignored if the top-level
- * MIME type disagrees with the file extension. For example, it's
- * reasonable for an {@code image/jpeg} file to declare a {@code format}
- * of {@code image/vnd.google.panorama360+jpg}, but declaring a
- * {@code format} of {@code audio/ogg} would be ignored.
- * <p>
- * This is a read-only column that is automatically computed.
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String MIME_TYPE = "mime_type";
-
- /** @removed promoted to parent interface */
- public static final String TITLE = "title";
-
- /**
- * The media type (audio, video, image or playlist)
- * of the file, or 0 for not a media file
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String MEDIA_TYPE = "media_type";
-
- /**
- * Constant for the {@link #MEDIA_TYPE} column indicating that file
- * is not an audio, image, video, playlist, or subtitles file.
- */
- public static final int MEDIA_TYPE_NONE = 0;
-
- /**
- * Constant for the {@link #MEDIA_TYPE} column indicating that file
- * is an image file.
- */
- public static final int MEDIA_TYPE_IMAGE = 1;
-
- /**
- * Constant for the {@link #MEDIA_TYPE} column indicating that file
- * is an audio file.
- */
- public static final int MEDIA_TYPE_AUDIO = 2;
-
- /**
- * Constant for the {@link #MEDIA_TYPE} column indicating that file
- * is a video file.
- */
- public static final int MEDIA_TYPE_VIDEO = 3;
-
- /**
- * Constant for the {@link #MEDIA_TYPE} column indicating that file
- * is a playlist file.
- */
- public static final int MEDIA_TYPE_PLAYLIST = 4;
-
- /**
- * Constant for the {@link #MEDIA_TYPE} column indicating that file
- * is a subtitles or lyrics file.
- */
- public static final int MEDIA_TYPE_SUBTITLE = 5;
-
- /**
- * Column indicating if the file is part of Downloads collection.
- * @hide
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String IS_DOWNLOAD = "is_download";
- }
- }
-
- /** @hide */
- public static class ThumbnailConstants {
- public static final int MINI_KIND = 1;
- public static final int FULL_SCREEN_KIND = 2;
- public static final int MICRO_KIND = 3;
-
- public static final Point MINI_SIZE = new Point(512, 384);
- public static final Point FULL_SCREEN_SIZE = new Point(1024, 786);
- public static final Point MICRO_SIZE = new Point(96, 96);
- }
-
- /**
- * Download metadata columns.
- */
- public interface DownloadColumns extends MediaColumns {
- /**
- * Uri indicating where the item has been downloaded from.
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- String DOWNLOAD_URI = "download_uri";
-
- /**
- * Uri indicating HTTP referer of {@link #DOWNLOAD_URI}.
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- String REFERER_URI = "referer_uri";
-
- /**
- * The description of the download.
- *
- * @removed
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_STRING)
- String DESCRIPTION = "description";
- }
-
- /**
- * Collection of downloaded items.
- */
- public static final class Downloads implements DownloadColumns {
- private Downloads() {}
-
- /**
- * The content:// style URI for the internal storage.
- */
- @NonNull
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- @NonNull
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The MIME type for this table.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download";
-
- /**
- * Regex that matches paths that needs to be considered part of downloads collection.
- * @hide
- */
- public static final Pattern PATTERN_DOWNLOADS_FILE = Pattern.compile(
- "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/.+");
- private static final Pattern PATTERN_DOWNLOADS_DIRECTORY = Pattern.compile(
- "(?i)^/storage/[^/]+/(?:[0-9]+/)?(?:Android/sandbox/[^/]+/)?Download/?");
-
- /**
- * Get the content:// style URI for the downloads table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the image media table on the given volume
- */
- public static @NonNull Uri getContentUri(@NonNull String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName)
- .appendPath("downloads").build();
- }
-
- /**
- * Get the content:// style URI for a single row in the downloads table
- * on the given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @param id the download to get the URI for
- * @return the URI to the downloads table on the given volume
- */
- public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
- return ContentUris.withAppendedId(getContentUri(volumeName), id);
- }
-
- /** @hide */
- public static @NonNull Uri getContentUriForPath(@NonNull String path) {
- return getContentUri(getVolumeName(new File(path)));
- }
-
- /** @hide */
- public static boolean isDownload(@NonNull String path) {
- return PATTERN_DOWNLOADS_FILE.matcher(path).matches();
- }
-
- /** @hide */
- public static boolean isDownloadDir(@NonNull String path) {
- return PATTERN_DOWNLOADS_DIRECTORY.matcher(path).matches();
- }
- }
-
- /** {@hide} */
- public static @NonNull String getVolumeName(@NonNull File path) {
- if (FileUtils.contains(Environment.getStorageDirectory(), path)) {
- final StorageManager sm = AppGlobals.getInitialApplication()
- .getSystemService(StorageManager.class);
- final StorageVolume sv = sm.getStorageVolume(path);
- if (sv != null) {
- if (sv.isPrimary()) {
- return VOLUME_EXTERNAL_PRIMARY;
- } else {
- return checkArgumentVolumeName(sv.getNormalizedUuid());
- }
- }
- throw new IllegalStateException("Unknown volume at " + path);
- } else {
- return VOLUME_INTERNAL;
- }
- }
-
- /**
- * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
- * to be accessed elsewhere.
- */
- @Deprecated
- private static class InternalThumbnails implements BaseColumns {
- /**
- * Currently outstanding thumbnail requests that can be cancelled.
- */
- @GuardedBy("sPending")
- private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>();
-
- /**
- * Make a blocking request to obtain the given thumbnail, generating it
- * if needed.
- *
- * @see #cancelThumbnail(ContentResolver, Uri)
- */
- @Deprecated
- static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri,
- int kind, @Nullable BitmapFactory.Options opts) {
- final Point size;
- if (kind == ThumbnailConstants.MICRO_KIND) {
- size = ThumbnailConstants.MICRO_SIZE;
- } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
- size = ThumbnailConstants.FULL_SCREEN_SIZE;
- } else if (kind == ThumbnailConstants.MINI_KIND) {
- size = ThumbnailConstants.MINI_SIZE;
- } else {
- throw new IllegalArgumentException("Unsupported kind: " + kind);
- }
-
- CancellationSignal signal = null;
- synchronized (sPending) {
- signal = sPending.get(uri);
- if (signal == null) {
- signal = new CancellationSignal();
- sPending.put(uri, signal);
- }
- }
-
- try {
- return cr.loadThumbnail(uri, Point.convert(size), signal);
- } catch (IOException e) {
- Log.w(TAG, "Failed to obtain thumbnail for " + uri, e);
- return null;
- } finally {
- synchronized (sPending) {
- sPending.remove(uri);
- }
- }
- }
-
- /**
- * This method cancels the thumbnail request so clients waiting for
- * {@link #getThumbnail} will be interrupted and return immediately.
- * Only the original process which made the request can cancel their own
- * requests.
- */
- @Deprecated
- static void cancelThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri) {
- synchronized (sPending) {
- final CancellationSignal signal = sPending.get(uri);
- if (signal != null) {
- signal.cancel();
- }
- }
- }
- }
-
- /**
- * Collection of all media with MIME type of {@code image/*}.
- */
- public static final class Images {
- /**
- * Image metadata columns.
- */
- public interface ImageColumns extends MediaColumns {
- /**
- * The picasa id of the image
- *
- * @deprecated this value was only relevant for images hosted on
- * Picasa, which are no longer supported.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String PICASA_ID = "picasa_id";
-
- /**
- * Whether the video should be published as public or private
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String IS_PRIVATE = "isprivate";
-
- /**
- * The latitude where the image was captured.
- *
- * @deprecated location details are no longer indexed for privacy
- * reasons, and this value is now always {@code null}.
- * You can still manually obtain location metadata using
- * {@link ExifInterface#getLatLong(float[])}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
- public static final String LATITUDE = "latitude";
-
- /**
- * The longitude where the image was captured.
- *
- * @deprecated location details are no longer indexed for privacy
- * reasons, and this value is now always {@code null}.
- * You can still manually obtain location metadata using
- * {@link ExifInterface#getLatLong(float[])}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
- public static final String LONGITUDE = "longitude";
-
- /** @removed promoted to parent interface */
- public static final String DATE_TAKEN = "datetaken";
- /** @removed promoted to parent interface */
- public static final String ORIENTATION = "orientation";
-
- /**
- * The mini thumb id.
- *
- * @deprecated all thumbnails should be obtained via
- * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this
- * value is no longer supported.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
-
- /** @removed promoted to parent interface */
- public static final String BUCKET_ID = "bucket_id";
- /** @removed promoted to parent interface */
- public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
- /** @removed promoted to parent interface */
- public static final String GROUP_ID = "group_id";
-
- /**
- * Indexed value of {@link ExifInterface#TAG_IMAGE_DESCRIPTION}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String DESCRIPTION = "description";
-
- /**
- * Indexed value of {@link ExifInterface#TAG_EXPOSURE_TIME}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String EXPOSURE_TIME = "exposure_time";
-
- /**
- * Indexed value of {@link ExifInterface#TAG_F_NUMBER}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String F_NUMBER = "f_number";
-
- /**
- * Indexed value of {@link ExifInterface#TAG_ISO_SPEED_RATINGS}
- * extracted from this media item.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String ISO = "iso";
- }
-
- public static final class Media implements ImageColumns {
- /**
- * @deprecated all queries should be performed through
- * {@link ContentResolver} directly, which offers modern
- * features like {@link CancellationSignal}.
- */
- @Deprecated
- public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
- return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
- }
-
- /**
- * @deprecated all queries should be performed through
- * {@link ContentResolver} directly, which offers modern
- * features like {@link CancellationSignal}.
- */
- @Deprecated
- public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
- String where, String orderBy) {
- return cr.query(uri, projection, where,
- null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- /**
- * @deprecated all queries should be performed through
- * {@link ContentResolver} directly, which offers modern
- * features like {@link CancellationSignal}.
- */
- @Deprecated
- public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
- String selection, String [] selectionArgs, String orderBy) {
- return cr.query(uri, projection, selection,
- selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- /**
- * Retrieves an image for the given url as a {@link Bitmap}.
- *
- * @param cr The content resolver to use
- * @param url The url of the image
- * @deprecated loading of images should be performed through
- * {@link ImageDecoder#createSource(ContentResolver, Uri)},
- * which offers modern features like
- * {@link PostProcessor}.
- */
- @Deprecated
- public static final Bitmap getBitmap(ContentResolver cr, Uri url)
- throws FileNotFoundException, IOException {
- InputStream input = cr.openInputStream(url);
- Bitmap bitmap = BitmapFactory.decodeStream(input);
- input.close();
- return bitmap;
- }
-
- /**
- * Insert an image and create a thumbnail for it.
- *
- * @param cr The content resolver to use
- * @param imagePath The path to the image to insert
- * @param name The name of the image
- * @param description The description of the image
- * @return The URL to the newly created image
- * @deprecated inserting of images should be performed using
- * {@link MediaColumns#IS_PENDING}, which offers richer
- * control over lifecycle.
- */
- @Deprecated
- public static final String insertImage(ContentResolver cr, String imagePath,
- String name, String description) throws FileNotFoundException {
- final File file = new File(imagePath);
- final String mimeType = MediaFile.getMimeTypeForFile(imagePath);
-
- if (TextUtils.isEmpty(name)) name = "Image";
- final PendingParams params = new PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, name, mimeType);
-
- final Context context = AppGlobals.getInitialApplication();
- final Uri pendingUri = createPending(context, params);
- try (PendingSession session = openPending(context, pendingUri)) {
- try (InputStream in = new FileInputStream(file);
- OutputStream out = session.openOutputStream()) {
- FileUtils.copy(in, out);
- }
- return session.publish().toString();
- } catch (Exception e) {
- Log.w(TAG, "Failed to insert image", e);
- context.getContentResolver().delete(pendingUri, null, null);
- return null;
- }
- }
-
- /**
- * Insert an image and create a thumbnail for it.
- *
- * @param cr The content resolver to use
- * @param source The stream to use for the image
- * @param title The name of the image
- * @param description The description of the image
- * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored
- * for any reason.
- * @deprecated inserting of images should be performed using
- * {@link MediaColumns#IS_PENDING}, which offers richer
- * control over lifecycle.
- */
- @Deprecated
- public static final String insertImage(ContentResolver cr, Bitmap source,
- String title, String description) {
- if (TextUtils.isEmpty(title)) title = "Image";
- final PendingParams params = new PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, title, "image/jpeg");
-
- final Context context = AppGlobals.getInitialApplication();
- final Uri pendingUri = createPending(context, params);
- try (PendingSession session = openPending(context, pendingUri)) {
- try (OutputStream out = session.openOutputStream()) {
- source.compress(Bitmap.CompressFormat.JPEG, 90, out);
- }
- return session.publish().toString();
- } catch (Exception e) {
- Log.w(TAG, "Failed to insert image", e);
- context.getContentResolver().delete(pendingUri, null, null);
- return null;
- }
- }
-
- /**
- * Get the content:// style URI for the image media table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the image media table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images")
- .appendPath("media").build();
- }
-
- /**
- * Get the content:// style URI for a single row in the images table
- * on the given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @param id the image to get the URI for
- * @return the URI to the images table on the given volume
- */
- public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
- return ContentUris.withAppendedId(getContentUri(volumeName), id);
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The MIME type of of this directory of
- * images. Note that each entry in this directory will have a standard
- * image MIME type as appropriate -- for example, image/jpeg.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
- }
-
- /**
- * This class provides utility methods to obtain thumbnails for various
- * {@link Images} items.
- *
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it offers
- * richer control over requested thumbnail sizes and
- * cancellation behavior.
- */
- @Deprecated
- public static class Thumbnails implements BaseColumns {
- /**
- * @deprecated all queries should be performed through
- * {@link ContentResolver} directly, which offers modern
- * features like {@link CancellationSignal}.
- */
- @Deprecated
- public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
- return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
- }
-
- /**
- * @deprecated all queries should be performed through
- * {@link ContentResolver} directly, which offers modern
- * features like {@link CancellationSignal}.
- */
- @Deprecated
- public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind,
- String[] projection) {
- return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
- }
-
- /**
- * @deprecated all queries should be performed through
- * {@link ContentResolver} directly, which offers modern
- * features like {@link CancellationSignal}.
- */
- @Deprecated
- public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind,
- String[] projection) {
- return cr.query(EXTERNAL_CONTENT_URI, projection,
- IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
- kind, null, null);
- }
-
- /**
- * Cancel any outstanding {@link #getThumbnail} requests, causing
- * them to return by throwing a {@link OperationCanceledException}.
- * <p>
- * This method has no effect on
- * {@link ContentResolver#loadThumbnail} calls, since they provide
- * their own {@link CancellationSignal}.
- *
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it
- * offers richer control over requested thumbnail sizes
- * and cancellation behavior.
- */
- @Deprecated
- public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- final Uri uri = ContentUris.withAppendedId(
- Images.Media.EXTERNAL_CONTENT_URI, origId);
- InternalThumbnails.cancelThumbnail(cr, uri);
- }
-
- /**
- * Return thumbnail representing a specific image item. If a
- * thumbnail doesn't exist, this method will block until it's
- * generated. Callers are responsible for their own in-memory
- * caching of returned values.
- *
- * As of {@link android.os.Build.VERSION_CODES#Q}, this output
- * of the thumbnail has correct rotation, don't need to rotate
- * it again.
- *
- * @param imageId the image item to obtain a thumbnail for.
- * @param kind optimal thumbnail size desired.
- * @return decoded thumbnail, or {@code null} if problem was
- * encountered.
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it
- * offers richer control over requested thumbnail sizes
- * and cancellation behavior.
- */
- @Deprecated
- public static Bitmap getThumbnail(ContentResolver cr, long imageId, int kind,
- BitmapFactory.Options options) {
- final Uri uri = ContentUris.withAppendedId(
- Images.Media.EXTERNAL_CONTENT_URI, imageId);
- return InternalThumbnails.getThumbnail(cr, uri, kind, options);
- }
-
- /**
- * Cancel any outstanding {@link #getThumbnail} requests, causing
- * them to return by throwing a {@link OperationCanceledException}.
- * <p>
- * This method has no effect on
- * {@link ContentResolver#loadThumbnail} calls, since they provide
- * their own {@link CancellationSignal}.
- *
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it
- * offers richer control over requested thumbnail sizes
- * and cancellation behavior.
- */
- @Deprecated
- public static void cancelThumbnailRequest(ContentResolver cr, long origId,
- long groupId) {
- cancelThumbnailRequest(cr, origId);
- }
-
- /**
- * Return thumbnail representing a specific image item. If a
- * thumbnail doesn't exist, this method will block until it's
- * generated. Callers are responsible for their own in-memory
- * caching of returned values.
- *
- * As of {@link android.os.Build.VERSION_CODES#Q}, this output
- * of the thumbnail has correct rotation, don't need to rotate
- * it again.
- *
- * @param imageId the image item to obtain a thumbnail for.
- * @param kind optimal thumbnail size desired.
- * @return decoded thumbnail, or {@code null} if problem was
- * encountered.
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it
- * offers richer control over requested thumbnail sizes
- * and cancellation behavior.
- */
- @Deprecated
- public static Bitmap getThumbnail(ContentResolver cr, long imageId, long groupId,
- int kind, BitmapFactory.Options options) {
- return getThumbnail(cr, imageId, kind, options);
- }
-
- /**
- * Get the content:// style URI for the image media table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the image media table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("images")
- .appendPath("thumbnails").build();
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "image_id ASC";
-
- /**
- * Path to the thumbnail file on disk.
- * <p>
- * Note that apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path directly,
- * apps should use
- * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
- * access.
- *
- * As of {@link android.os.Build.VERSION_CODES#Q}, this thumbnail
- * has correct rotation, don't need to rotate it again.
- *
- * @deprecated Apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path
- * directly, apps should use
- * {@link ContentResolver#loadThumbnail}
- * to gain access.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String DATA = "_data";
-
- /**
- * The original image for the thumbnal
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String IMAGE_ID = "image_id";
-
- /**
- * The kind of the thumbnail
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String KIND = "kind";
-
- public static final int MINI_KIND = ThumbnailConstants.MINI_KIND;
- public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND;
- public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
-
- /**
- * The blob raw data of thumbnail
- *
- * @deprecated this column never existed internally, and could never
- * have returned valid data.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_BLOB)
- public static final String THUMB_DATA = "thumb_data";
-
- /**
- * The width of the thumbnal
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String WIDTH = "width";
-
- /**
- * The height of the thumbnail
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String HEIGHT = "height";
- }
- }
-
- /**
- * Collection of all media with MIME type of {@code audio/*}.
- */
- public static final class Audio {
- /**
- * Audio metadata columns.
- */
- public interface AudioColumns extends MediaColumns {
-
- /**
- * A non human readable key calculated from the TITLE, used for
- * searching, sorting and grouping
- *
- * @see Audio#keyFor(String)
- * @deprecated These keys are generated using
- * {@link java.util.Locale#ROOT}, which means they don't
- * reflect locale-specific sorting preferences. To apply
- * locale-specific sorting preferences, use
- * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
- * {@code COLLATE LOCALIZED}, or
- * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String TITLE_KEY = "title_key";
-
- /** @removed promoted to parent interface */
- public static final String DURATION = "duration";
-
- /**
- * The position within the audio item at which playback should be
- * resumed.
- */
- @DurationMillisLong
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String BOOKMARK = "bookmark";
-
- /**
- * The id of the artist who created the audio file, if any
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String ARTIST_ID = "artist_id";
-
- /** @removed promoted to parent interface */
- public static final String ARTIST = "artist";
-
- /**
- * The artist credited for the album that contains the audio file
- * @hide
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ALBUM_ARTIST = "album_artist";
-
- /**
- * A non human readable key calculated from the ARTIST, used for
- * searching, sorting and grouping
- *
- * @see Audio#keyFor(String)
- * @deprecated These keys are generated using
- * {@link java.util.Locale#ROOT}, which means they don't
- * reflect locale-specific sorting preferences. To apply
- * locale-specific sorting preferences, use
- * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
- * {@code COLLATE LOCALIZED}, or
- * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ARTIST_KEY = "artist_key";
-
- /** @removed promoted to parent interface */
- public static final String COMPOSER = "composer";
-
- /**
- * The id of the album the audio file is from, if any
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String ALBUM_ID = "album_id";
-
- /** @removed promoted to parent interface */
- public static final String ALBUM = "album";
-
- /**
- * A non human readable key calculated from the ALBUM, used for
- * searching, sorting and grouping
- *
- * @see Audio#keyFor(String)
- * @deprecated These keys are generated using
- * {@link java.util.Locale#ROOT}, which means they don't
- * reflect locale-specific sorting preferences. To apply
- * locale-specific sorting preferences, use
- * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
- * {@code COLLATE LOCALIZED}, or
- * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ALBUM_KEY = "album_key";
-
- /**
- * The track number of this song on the album, if any.
- * This number encodes both the track number and the
- * disc number. For multi-disc sets, this number will
- * be 1xxx for tracks on the first disc, 2xxx for tracks
- * on the second disc, etc.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String TRACK = "track";
-
- /**
- * The year the audio file was recorded, if any
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String YEAR = "year";
-
- /**
- * Non-zero if the audio file is music
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String IS_MUSIC = "is_music";
-
- /**
- * Non-zero if the audio file is a podcast
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String IS_PODCAST = "is_podcast";
-
- /**
- * Non-zero if the audio file may be a ringtone
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String IS_RINGTONE = "is_ringtone";
-
- /**
- * Non-zero if the audio file may be an alarm
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String IS_ALARM = "is_alarm";
-
- /**
- * Non-zero if the audio file may be a notification sound
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String IS_NOTIFICATION = "is_notification";
-
- /**
- * Non-zero if the audio file is an audiobook
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String IS_AUDIOBOOK = "is_audiobook";
-
- /**
- * The id of the genre the audio file is from, if any
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String GENRE_ID = "genre_id";
-
- /**
- * The genre of the audio file, if any.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String GENRE = "genre";
-
- /**
- * A non human readable key calculated from the GENRE, used for
- * searching, sorting and grouping
- *
- * @see Audio#keyFor(String)
- * @deprecated These keys are generated using
- * {@link java.util.Locale#ROOT}, which means they don't
- * reflect locale-specific sorting preferences. To apply
- * locale-specific sorting preferences, use
- * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
- * {@code COLLATE LOCALIZED}, or
- * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String GENRE_KEY = "genre_key";
-
- /**
- * The resource URI of a localized title, if any.
- * <p>
- * Conforms to this pattern:
- * <ul>
- * <li>Scheme: {@link ContentResolver#SCHEME_ANDROID_RESOURCE}
- * <li>Authority: Package Name of ringtone title provider
- * <li>First Path Segment: Type of resource (must be "string")
- * <li>Second Path Segment: Resource ID of title
- * </ul>
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String TITLE_RESOURCE_URI = "title_resource_uri";
- }
-
- private static final Pattern PATTERN_TRIM_BEFORE = Pattern.compile(
- "(?i)(^(the|an|a) |,\\s*(the|an|a)$|[^\\w\\s]|^\\s+|\\s+$)");
- private static final Pattern PATTERN_TRIM_AFTER = Pattern.compile(
- "(^(00)+|(00)+$)");
-
- /**
- * Converts a user-visible string into a "key" that can be used for
- * grouping, sorting, and searching.
- *
- * @return Opaque token that should not be parsed or displayed to users.
- * @deprecated These keys are generated using
- * {@link java.util.Locale#ROOT}, which means they don't
- * reflect locale-specific sorting preferences. To apply
- * locale-specific sorting preferences, use
- * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
- * {@code COLLATE LOCALIZED}, or
- * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
- */
- @Deprecated
- public static @Nullable String keyFor(@Nullable String name) {
- if (TextUtils.isEmpty(name)) return null;
-
- if (UNKNOWN_STRING.equals(name)) {
- return "01";
- }
-
- final boolean sortFirst = name.startsWith("\001");
-
- name = PATTERN_TRIM_BEFORE.matcher(name).replaceAll("");
- if (TextUtils.isEmpty(name)) return null;
-
- final Collator c = Collator.getInstance(Locale.ROOT);
- c.setStrength(Collator.PRIMARY);
- name = HexEncoding.encodeToString(c.getCollationKey(name).toByteArray(), false);
-
- name = PATTERN_TRIM_AFTER.matcher(name).replaceAll("");
- if (sortFirst) {
- name = "01" + name;
- }
- return name;
- }
-
- public static final class Media implements AudioColumns {
- /**
- * Get the content:// style URI for the audio media table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the audio media table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
- .appendPath("media").build();
- }
-
- /**
- * Get the content:// style URI for a single row in the audio table
- * on the given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @param id the audio to get the URI for
- * @return the URI to the audio table on the given volume
- */
- public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
- return ContentUris.withAppendedId(getContentUri(volumeName), id);
- }
-
- /**
- * Get the content:// style URI for the given audio media file.
- *
- * @deprecated Apps may not have filesystem permissions to directly
- * access this path.
- */
- @Deprecated
- public static @Nullable Uri getContentUriForPath(@NonNull String path) {
- return getContentUri(getVolumeName(new File(path)));
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The MIME type for this table.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
-
- /**
- * The MIME type for an audio track.
- */
- public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
-
- /**
- * Activity Action: Start SoundRecorder application.
- * <p>Input: nothing.
- * <p>Output: An uri to the recorded sound stored in the Media Library
- * if the recording was successful.
- * May also contain the extra EXTRA_MAX_BYTES.
- * @see #EXTRA_MAX_BYTES
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String RECORD_SOUND_ACTION =
- "android.provider.MediaStore.RECORD_SOUND";
-
- /**
- * The name of the Intent-extra used to define a maximum file size for
- * a recording made by the SoundRecorder application.
- *
- * @see #RECORD_SOUND_ACTION
- */
- public static final String EXTRA_MAX_BYTES =
- "android.provider.MediaStore.extra.MAX_BYTES";
- }
-
- /**
- * Audio genre metadata columns.
- */
- public interface GenresColumns {
- /**
- * The name of the genre
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String NAME = "name";
- }
-
- /**
- * Contains all genres for audio files
- */
- public static final class Genres implements BaseColumns, GenresColumns {
- /**
- * Get the content:// style URI for the audio genres table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the audio genres table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
- .appendPath("genres").build();
- }
-
- /**
- * Get the content:// style URI for querying the genres of an audio file.
- *
- * @param volumeName the name of the volume to get the URI for
- * @param audioId the ID of the audio file for which to retrieve the genres
- * @return the URI to for querying the genres for the audio file
- * with the given the volume and audioID
- */
- public static Uri getContentUriForAudioId(String volumeName, int audioId) {
- return ContentUris.withAppendedId(Audio.Media.getContentUri(volumeName), audioId)
- .buildUpon().appendPath("genres").build();
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The MIME type for this table.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
-
- /**
- * The MIME type for entries in this table.
- */
- public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = NAME;
-
- /**
- * Sub-directory of each genre containing all members.
- */
- public static final class Members implements AudioColumns {
-
- public static final Uri getContentUri(String volumeName, long genreId) {
- return ContentUris
- .withAppendedId(Audio.Genres.getContentUri(volumeName), genreId)
- .buildUpon().appendPath("members").build();
- }
-
- /**
- * A subdirectory of each genre containing all member audio files.
- */
- public static final String CONTENT_DIRECTORY = "members";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
-
- /**
- * The ID of the audio file
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String AUDIO_ID = "audio_id";
-
- /**
- * The ID of the genre
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String GENRE_ID = "genre_id";
- }
- }
-
- /**
- * Audio playlist metadata columns.
- */
- public interface PlaylistsColumns {
- /**
- * The name of the playlist
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String NAME = "name";
-
- /**
- * Path to the playlist file on disk.
- * <p>
- * Note that apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path directly,
- * apps should use
- * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
- * access.
- *
- * @deprecated Apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path
- * directly, apps should use
- * {@link ContentResolver#openFileDescriptor(Uri, String)}
- * to gain access.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String DATA = "_data";
-
- /**
- * The time the media item was first added.
- */
- @CurrentTimeSecondsLong
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String DATE_ADDED = "date_added";
-
- /**
- * The time the media item was last modified.
- */
- @CurrentTimeSecondsLong
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String DATE_MODIFIED = "date_modified";
- }
-
- /**
- * Contains playlists for audio files
- */
- public static final class Playlists implements BaseColumns,
- PlaylistsColumns {
- /**
- * Get the content:// style URI for the audio playlists table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the audio playlists table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
- .appendPath("playlists").build();
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The MIME type for this table.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
-
- /**
- * The MIME type for entries in this table.
- */
- public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = NAME;
-
- /**
- * Sub-directory of each playlist containing all members.
- */
- public static final class Members implements AudioColumns {
- public static final Uri getContentUri(String volumeName, long playlistId) {
- return ContentUris
- .withAppendedId(Audio.Playlists.getContentUri(volumeName), playlistId)
- .buildUpon().appendPath("members").build();
- }
-
- /**
- * Convenience method to move a playlist item to a new location
- * @param res The content resolver to use
- * @param playlistId The numeric id of the playlist
- * @param from The position of the item to move
- * @param to The position to move the item to
- * @return true on success
- */
- public static final boolean moveItem(ContentResolver res,
- long playlistId, int from, int to) {
- Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
- playlistId)
- .buildUpon()
- .appendEncodedPath(String.valueOf(from))
- .appendQueryParameter("move", "true")
- .build();
- ContentValues values = new ContentValues();
- values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
- return res.update(uri, values, null, null) != 0;
- }
-
- /**
- * The ID within the playlist.
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String _ID = "_id";
-
- /**
- * A subdirectory of each playlist containing all member audio
- * files.
- */
- public static final String CONTENT_DIRECTORY = "members";
-
- /**
- * The ID of the audio file
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String AUDIO_ID = "audio_id";
-
- /**
- * The ID of the playlist
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String PLAYLIST_ID = "playlist_id";
-
- /**
- * The order of the songs in the playlist
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String PLAY_ORDER = "play_order";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = PLAY_ORDER;
- }
- }
-
- /**
- * Audio artist metadata columns.
- */
- public interface ArtistColumns {
- /**
- * The artist who created the audio file, if any
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ARTIST = "artist";
-
- /**
- * A non human readable key calculated from the ARTIST, used for
- * searching, sorting and grouping
- *
- * @see Audio#keyFor(String)
- * @deprecated These keys are generated using
- * {@link java.util.Locale#ROOT}, which means they don't
- * reflect locale-specific sorting preferences. To apply
- * locale-specific sorting preferences, use
- * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
- * {@code COLLATE LOCALIZED}, or
- * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ARTIST_KEY = "artist_key";
-
- /**
- * The number of albums in the database for this artist
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String NUMBER_OF_ALBUMS = "number_of_albums";
-
- /**
- * The number of albums in the database for this artist
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String NUMBER_OF_TRACKS = "number_of_tracks";
- }
-
- /**
- * Contains artists for audio files
- */
- public static final class Artists implements BaseColumns, ArtistColumns {
- /**
- * Get the content:// style URI for the artists table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the audio artists table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
- .appendPath("artists").build();
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The MIME type for this table.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
-
- /**
- * The MIME type for entries in this table.
- */
- public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = ARTIST_KEY;
-
- /**
- * Sub-directory of each artist containing all albums on which
- * a song by the artist appears.
- */
- public static final class Albums implements AlbumColumns {
- public static final Uri getContentUri(String volumeName,long artistId) {
- return ContentUris
- .withAppendedId(Audio.Artists.getContentUri(volumeName), artistId)
- .buildUpon().appendPath("albums").build();
- }
- }
- }
-
- /**
- * Audio album metadata columns.
- */
- public interface AlbumColumns {
-
- /**
- * The id for the album
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String ALBUM_ID = "album_id";
-
- /**
- * The album on which the audio file appears, if any
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ALBUM = "album";
-
- /**
- * The ID of the artist whose songs appear on this album.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String ARTIST_ID = "artist_id";
-
- /**
- * The name of the artist whose songs appear on this album.
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ARTIST = "artist";
-
- /**
- * A non human readable key calculated from the ARTIST, used for
- * searching, sorting and grouping
- *
- * @see Audio#keyFor(String)
- * @deprecated These keys are generated using
- * {@link java.util.Locale#ROOT}, which means they don't
- * reflect locale-specific sorting preferences. To apply
- * locale-specific sorting preferences, use
- * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
- * {@code COLLATE LOCALIZED}, or
- * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ARTIST_KEY = "artist_key";
-
- /**
- * The number of songs on this album
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String NUMBER_OF_SONGS = "numsongs";
-
- /**
- * This column is available when getting album info via artist,
- * and indicates the number of songs on the album by the given
- * artist.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
-
- /**
- * The year in which the earliest songs
- * on this album were released. This will often
- * be the same as {@link #LAST_YEAR}, but for compilation albums
- * they might differ.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String FIRST_YEAR = "minyear";
-
- /**
- * The year in which the latest songs
- * on this album were released. This will often
- * be the same as {@link #FIRST_YEAR}, but for compilation albums
- * they might differ.
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String LAST_YEAR = "maxyear";
-
- /**
- * A non human readable key calculated from the ALBUM, used for
- * searching, sorting and grouping
- *
- * @see Audio#keyFor(String)
- * @deprecated These keys are generated using
- * {@link java.util.Locale#ROOT}, which means they don't
- * reflect locale-specific sorting preferences. To apply
- * locale-specific sorting preferences, use
- * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
- * {@code COLLATE LOCALIZED}, or
- * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String ALBUM_KEY = "album_key";
-
- /**
- * Cached album art.
- *
- * @deprecated Apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path
- * directly, apps should use
- * {@link ContentResolver#loadThumbnail}
- * to gain access.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String ALBUM_ART = "album_art";
- }
-
- /**
- * Contains artists for audio files
- */
- public static final class Albums implements BaseColumns, AlbumColumns {
- /**
- * Get the content:// style URI for the albums table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the audio albums table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("audio")
- .appendPath("albums").build();
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The MIME type for this table.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
-
- /**
- * The MIME type for entries in this table.
- */
- public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = ALBUM_KEY;
- }
-
- public static final class Radio {
- /**
- * The MIME type for entries in this table.
- */
- public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
-
- // Not instantiable.
- private Radio() { }
- }
-
- /**
- * This class provides utility methods to obtain thumbnails for various
- * {@link Audio} items.
- *
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it offers
- * richer control over requested thumbnail sizes and
- * cancellation behavior.
- * @hide
- */
- @Deprecated
- public static class Thumbnails implements BaseColumns {
- /**
- * Path to the thumbnail file on disk.
- * <p>
- * Note that apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path directly,
- * apps should use
- * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
- * access.
- *
- * @deprecated Apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path
- * directly, apps should use
- * {@link ContentResolver#loadThumbnail}
- * to gain access.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String DATA = "_data";
-
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String ALBUM_ID = "album_id";
- }
- }
-
- /**
- * Collection of all media with MIME type of {@code video/*}.
- */
- public static final class Video {
-
- /**
- * The default sort order for this table.
- */
- public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
-
- /**
- * @deprecated all queries should be performed through
- * {@link ContentResolver} directly, which offers modern
- * features like {@link CancellationSignal}.
- */
- @Deprecated
- public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
- return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
- }
-
- /**
- * Video metadata columns.
- */
- public interface VideoColumns extends MediaColumns {
- /** @removed promoted to parent interface */
- public static final String DURATION = "duration";
- /** @removed promoted to parent interface */
- public static final String ARTIST = "artist";
- /** @removed promoted to parent interface */
- public static final String ALBUM = "album";
- /** @removed promoted to parent interface */
- public static final String RESOLUTION = "resolution";
-
- /**
- * The description of the video recording
- */
- @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
- public static final String DESCRIPTION = "description";
-
- /**
- * Whether the video should be published as public or private
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String IS_PRIVATE = "isprivate";
-
- /**
- * The user-added tags associated with a video
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String TAGS = "tags";
-
- /**
- * The YouTube category of the video
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String CATEGORY = "category";
-
- /**
- * The language of the video
- */
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String LANGUAGE = "language";
-
- /**
- * The latitude where the video was captured.
- *
- * @deprecated location details are no longer indexed for privacy
- * reasons, and this value is now always {@code null}.
- * You can still manually obtain location metadata using
- * {@link ExifInterface#getLatLong(float[])}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
- public static final String LATITUDE = "latitude";
-
- /**
- * The longitude where the video was captured.
- *
- * @deprecated location details are no longer indexed for privacy
- * reasons, and this value is now always {@code null}.
- * You can still manually obtain location metadata using
- * {@link ExifInterface#getLatLong(float[])}.
- */
- @Deprecated
- @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true)
- public static final String LONGITUDE = "longitude";
-
- /** @removed promoted to parent interface */
- public static final String DATE_TAKEN = "datetaken";
-
- /**
- * The mini thumb id.
- *
- * @deprecated all thumbnails should be obtained via
- * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this
- * value is no longer supported.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
-
- /** @removed promoted to parent interface */
- public static final String BUCKET_ID = "bucket_id";
- /** @removed promoted to parent interface */
- public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
- /** @removed promoted to parent interface */
- public static final String GROUP_ID = "group_id";
-
- /**
- * The position within the video item at which playback should be
- * resumed.
- */
- @DurationMillisLong
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String BOOKMARK = "bookmark";
-
- /**
- * The color standard of this media file, if available.
- *
- * @see MediaFormat#COLOR_STANDARD_BT709
- * @see MediaFormat#COLOR_STANDARD_BT601_PAL
- * @see MediaFormat#COLOR_STANDARD_BT601_NTSC
- * @see MediaFormat#COLOR_STANDARD_BT2020
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String COLOR_STANDARD = "color_standard";
-
- /**
- * The color transfer of this media file, if available.
- *
- * @see MediaFormat#COLOR_TRANSFER_LINEAR
- * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
- * @see MediaFormat#COLOR_TRANSFER_ST2084
- * @see MediaFormat#COLOR_TRANSFER_HLG
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String COLOR_TRANSFER = "color_transfer";
-
- /**
- * The color range of this media file, if available.
- *
- * @see MediaFormat#COLOR_RANGE_LIMITED
- * @see MediaFormat#COLOR_RANGE_FULL
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String COLOR_RANGE = "color_range";
- }
-
- public static final class Media implements VideoColumns {
- /**
- * Get the content:// style URI for the video media table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the video media table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video")
- .appendPath("media").build();
- }
-
- /**
- * Get the content:// style URI for a single row in the videos table
- * on the given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @param id the video to get the URI for
- * @return the URI to the videos table on the given volume
- */
- public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
- return ContentUris.withAppendedId(getContentUri(volumeName), id);
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The MIME type for this table.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = TITLE;
- }
-
- /**
- * This class provides utility methods to obtain thumbnails for various
- * {@link Video} items.
- *
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it offers
- * richer control over requested thumbnail sizes and
- * cancellation behavior.
- */
- @Deprecated
- public static class Thumbnails implements BaseColumns {
- /**
- * Cancel any outstanding {@link #getThumbnail} requests, causing
- * them to return by throwing a {@link OperationCanceledException}.
- * <p>
- * This method has no effect on
- * {@link ContentResolver#loadThumbnail} calls, since they provide
- * their own {@link CancellationSignal}.
- *
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it
- * offers richer control over requested thumbnail sizes
- * and cancellation behavior.
- */
- @Deprecated
- public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- final Uri uri = ContentUris.withAppendedId(
- Video.Media.EXTERNAL_CONTENT_URI, origId);
- InternalThumbnails.cancelThumbnail(cr, uri);
- }
-
- /**
- * Return thumbnail representing a specific video item. If a
- * thumbnail doesn't exist, this method will block until it's
- * generated. Callers are responsible for their own in-memory
- * caching of returned values.
- *
- * @param videoId the video item to obtain a thumbnail for.
- * @param kind optimal thumbnail size desired.
- * @return decoded thumbnail, or {@code null} if problem was
- * encountered.
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it
- * offers richer control over requested thumbnail sizes
- * and cancellation behavior.
- */
- @Deprecated
- public static Bitmap getThumbnail(ContentResolver cr, long videoId, int kind,
- BitmapFactory.Options options) {
- final Uri uri = ContentUris.withAppendedId(
- Video.Media.EXTERNAL_CONTENT_URI, videoId);
- return InternalThumbnails.getThumbnail(cr, uri, kind, options);
- }
-
- /**
- * Cancel any outstanding {@link #getThumbnail} requests, causing
- * them to return by throwing a {@link OperationCanceledException}.
- * <p>
- * This method has no effect on
- * {@link ContentResolver#loadThumbnail} calls, since they provide
- * their own {@link CancellationSignal}.
- *
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it
- * offers richer control over requested thumbnail sizes
- * and cancellation behavior.
- */
- @Deprecated
- public static void cancelThumbnailRequest(ContentResolver cr, long videoId,
- long groupId) {
- cancelThumbnailRequest(cr, videoId);
- }
-
- /**
- * Return thumbnail representing a specific video item. If a
- * thumbnail doesn't exist, this method will block until it's
- * generated. Callers are responsible for their own in-memory
- * caching of returned values.
- *
- * @param videoId the video item to obtain a thumbnail for.
- * @param kind optimal thumbnail size desired.
- * @return decoded thumbnail, or {@code null} if problem was
- * encountered.
- * @deprecated Callers should migrate to using
- * {@link ContentResolver#loadThumbnail}, since it
- * offers richer control over requested thumbnail sizes
- * and cancellation behavior.
- */
- @Deprecated
- public static Bitmap getThumbnail(ContentResolver cr, long videoId, long groupId,
- int kind, BitmapFactory.Options options) {
- return getThumbnail(cr, videoId, kind, options);
- }
-
- /**
- * Get the content:// style URI for the image media table on the
- * given volume.
- *
- * @param volumeName the name of the volume to get the URI for
- * @return the URI to the image media table on the given volume
- */
- public static Uri getContentUri(String volumeName) {
- return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("video")
- .appendPath("thumbnails").build();
- }
-
- /**
- * The content:// style URI for the internal storage.
- */
- public static final Uri INTERNAL_CONTENT_URI =
- getContentUri("internal");
-
- /**
- * The content:// style URI for the "primary" external storage
- * volume.
- */
- public static final Uri EXTERNAL_CONTENT_URI =
- getContentUri("external");
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "video_id ASC";
-
- /**
- * Path to the thumbnail file on disk.
- *
- * @deprecated Apps may not have filesystem permissions to directly
- * access this path. Instead of trying to open this path
- * directly, apps should use
- * {@link ContentResolver#openFileDescriptor(Uri, String)}
- * to gain access.
- */
- @Deprecated
- @Column(Cursor.FIELD_TYPE_STRING)
- public static final String DATA = "_data";
-
- /**
- * The original image for the thumbnal
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String VIDEO_ID = "video_id";
-
- /**
- * The kind of the thumbnail
- */
- @Column(Cursor.FIELD_TYPE_INTEGER)
- public static final String KIND = "kind";
-
- public static final int MINI_KIND = ThumbnailConstants.MINI_KIND;
- public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND;
- public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
-
- /**
- * The width of the thumbnal
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String WIDTH = "width";
-
- /**
- * The height of the thumbnail
- */
- @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
- public static final String HEIGHT = "height";
- }
- }
-
- /** @removed */
- @Deprecated
- public static @NonNull Set<String> getAllVolumeNames(@NonNull Context context) {
- return getExternalVolumeNames(context);
- }
-
- /**
- * Return list of all specific volume names that make up
- * {@link #VOLUME_EXTERNAL}. This includes a unique volume name for each
- * shared storage device that is currently attached, which typically
- * includes {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}.
- * <p>
- * Each specific volume name can be passed to APIs like
- * {@link MediaStore.Images.Media#getContentUri(String)} to interact with
- * media on that storage device.
- */
- public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) {
- final StorageManager sm = context.getSystemService(StorageManager.class);
- final Set<String> volumeNames = new ArraySet<>();
- for (VolumeInfo vi : sm.getVolumes()) {
- if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) {
- if (vi.isPrimary()) {
- volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
- } else {
- volumeNames.add(vi.getNormalizedFsUuid());
- }
- }
- }
- return volumeNames;
- }
-
- /**
- * Return list of all specific volume names that have recently been part of
- * {@link #VOLUME_EXTERNAL}.
- * <p>
- * This includes both currently mounted volumes <em>and</em> recently
- * mounted (but currently unmounted) volumes. Any indexed metadata for these
- * volumes is preserved to optimize the speed of remounting at a later time.
- *
- * @hide
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
- public static @NonNull Set<String> getRecentExternalVolumeNames(@NonNull Context context) {
- final StorageManager sm = context.getSystemService(StorageManager.class);
-
- // We always have primary storage
- final Set<String> volumeNames = new ArraySet<>();
- volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
-
- final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
- for (VolumeRecord rec : sm.getVolumeRecords()) {
- // Skip volumes without valid UUIDs
- if (TextUtils.isEmpty(rec.fsUuid)) continue;
-
- final VolumeInfo vi = sm.findVolumeByUuid(rec.fsUuid);
- if (vi != null && vi.isVisibleForUser(UserHandle.myUserId())
- && vi.isMountedReadable()) {
- // We're mounted right now
- volumeNames.add(rec.getNormalizedFsUuid());
- } else if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
- // We're not mounted right now, but we've been seen recently
- volumeNames.add(rec.getNormalizedFsUuid());
- }
- }
- return volumeNames;
- }
-
- /**
- * Return the volume name that the given {@link Uri} references.
- */
- public static @NonNull String getVolumeName(@NonNull Uri uri) {
- final List<String> segments = uri.getPathSegments();
- switch (uri.getAuthority()) {
- case AUTHORITY:
- case AUTHORITY_LEGACY: {
- if (segments != null && segments.size() > 0) {
- return segments.get(0);
- }
- }
- }
- throw new IllegalArgumentException("Missing volume name: " + uri);
- }
-
- /** {@hide} */
- public static @NonNull String checkArgumentVolumeName(@NonNull String volumeName) {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- if (VOLUME_INTERNAL.equals(volumeName)) {
- return volumeName;
- } else if (VOLUME_EXTERNAL.equals(volumeName)) {
- return volumeName;
- } else if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) {
- return volumeName;
- }
-
- // When not one of the well-known values above, it must be a hex UUID
- for (int i = 0; i < volumeName.length(); i++) {
- final char c = volumeName.charAt(i);
- if (('a' <= c && c <= 'f') || ('0' <= c && c <= '9') || (c == '-')) {
- continue;
- } else {
- throw new IllegalArgumentException("Invalid volume name: " + volumeName);
- }
- }
- return volumeName;
- }
-
- private static boolean parseBoolean(@Nullable String value) {
- if (value == null) return false;
- if ("1".equals(value)) return true;
- if ("true".equalsIgnoreCase(value)) return true;
- return false;
- }
-
- /**
- * Return path where the given specific volume is mounted. Not valid for
- * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are
- * broad collections that cover many paths.
- *
- * @hide
- */
- @TestApi
- public static @NonNull File getVolumePath(@NonNull String volumeName)
- throws FileNotFoundException {
- final StorageManager sm = AppGlobals.getInitialApplication()
- .getSystemService(StorageManager.class);
- return getVolumePath(sm.getVolumes(), volumeName);
- }
-
- /** {@hide} */
- public static @NonNull File getVolumePath(@NonNull List<VolumeInfo> volumes,
- @NonNull String volumeName) throws FileNotFoundException {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- switch (volumeName) {
- case VOLUME_INTERNAL:
- case VOLUME_EXTERNAL:
- throw new FileNotFoundException(volumeName + " has no associated path");
- }
-
- final boolean wantPrimary = VOLUME_EXTERNAL_PRIMARY.equals(volumeName);
- for (VolumeInfo volume : volumes) {
- final boolean matchPrimary = wantPrimary
- && volume.isPrimary();
- final boolean matchSecondary = !wantPrimary
- && Objects.equals(volume.getNormalizedFsUuid(), volumeName);
- if (matchPrimary || matchSecondary) {
- final File path = volume.getPathForUser(UserHandle.myUserId());
- if (path != null) {
- return path;
- }
- }
- }
- throw new FileNotFoundException("Failed to find path for " + volumeName);
- }
-
- /**
- * Return paths that should be scanned for the given volume.
- *
- * @hide
- */
- @TestApi
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
- public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
- throws FileNotFoundException {
- if (TextUtils.isEmpty(volumeName)) {
- throw new IllegalArgumentException();
- }
-
- final Context context = AppGlobals.getInitialApplication();
- final UserManager um = context.getSystemService(UserManager.class);
-
- final ArrayList<File> res = new ArrayList<>();
- if (VOLUME_INTERNAL.equals(volumeName)) {
- addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
- addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
- addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
- } else if (VOLUME_EXTERNAL.equals(volumeName)) {
- for (String exactVolume : getExternalVolumeNames(context)) {
- addCanonicalFile(res, getVolumePath(exactVolume));
- }
- if (um.isDemoUser()) {
- addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
- }
- } else {
- addCanonicalFile(res, getVolumePath(volumeName));
- if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) {
- addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
- }
- }
- return res;
- }
-
- private static void addCanonicalFile(List<File> list, File file) {
- try {
- list.add(file.getCanonicalFile());
- } catch (IOException e) {
- Log.w(TAG, "Failed to resolve " + file + ": " + e);
- list.add(file);
- }
- }
-
- /**
- * Uri for querying the state of the media scanner.
- */
- public static Uri getMediaScannerUri() {
- return AUTHORITY_URI.buildUpon().appendPath("none").appendPath("media_scanner").build();
- }
-
- /**
- * Name of current volume being scanned by the media scanner.
- */
- public static final String MEDIA_SCANNER_VOLUME = "volume";
-
- /**
- * Name of the file signaling the media scanner to ignore media in the containing directory
- * and its subdirectories. Developers should use this to avoid application graphics showing
- * up in the Gallery and likewise prevent application sounds and music from showing up in
- * the Music app.
- */
- public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
-
- /**
- * Return an opaque version string describing the {@link MediaStore} state.
- * <p>
- * Applications that import data from {@link MediaStore} into their own
- * caches can use this to detect that {@link MediaStore} has undergone
- * substantial changes, and that data should be rescanned.
- * <p>
- * No other assumptions should be made about the meaning of the version.
- * <p>
- * This method returns the version for
- * {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}; to obtain a version for a
- * different volume, use {@link #getVersion(Context, String)}.
- */
- public static @NonNull String getVersion(@NonNull Context context) {
- return getVersion(context, VOLUME_EXTERNAL_PRIMARY);
- }
-
- /**
- * Return an opaque version string describing the {@link MediaStore} state.
- * <p>
- * Applications that import data from {@link MediaStore} into their own
- * caches can use this to detect that {@link MediaStore} has undergone
- * substantial changes, and that data should be rescanned.
- * <p>
- * No other assumptions should be made about the meaning of the version.
- *
- * @param volumeName specific volume to obtain an opaque version string for.
- * Must be one of the values returned from
- * {@link #getExternalVolumeNames(Context)}.
- */
- public static @NonNull String getVersion(@NonNull Context context, @NonNull String volumeName) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_TEXT, volumeName);
- final Bundle out = client.call(GET_VERSION_CALL, null, in);
- return out.getString(Intent.EXTRA_TEXT);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Return a {@link DocumentsProvider} Uri that is an equivalent to the given
- * {@link MediaStore} Uri.
- * <p>
- * This allows apps with Storage Access Framework permissions to convert
- * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer
- * to the same underlying item. Note that this method doesn't grant any new
- * permissions; callers must already hold permissions obtained with
- * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs.
- *
- * @param mediaUri The {@link MediaStore} Uri to convert.
- * @return An equivalent {@link DocumentsProvider} Uri. Returns {@code null}
- * if no equivalent was found.
- * @see #getMediaUri(Context, Uri)
- */
- public static @Nullable Uri getDocumentUri(@NonNull Context context, @NonNull Uri mediaUri) {
- final ContentResolver resolver = context.getContentResolver();
- final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
-
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- final Bundle in = new Bundle();
- in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri);
- in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
- final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in);
- return out.getParcelable(DocumentsContract.EXTRA_URI);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Return a {@link MediaStore} Uri that is an equivalent to the given
- * {@link DocumentsProvider} Uri.
- * <p>
- * This allows apps with Storage Access Framework permissions to convert
- * between {@link MediaStore} and {@link DocumentsProvider} Uris that refer
- * to the same underlying item. Note that this method doesn't grant any new
- * permissions; callers must already hold permissions obtained with
- * {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs.
- *
- * @param documentUri The {@link DocumentsProvider} Uri to convert.
- * @return An equivalent {@link MediaStore} Uri. Returns {@code null} if no
- * equivalent was found.
- * @see #getDocumentUri(Context, Uri)
- */
- public static @Nullable Uri getMediaUri(@NonNull Context context, @NonNull Uri documentUri) {
- final ContentResolver resolver = context.getContentResolver();
- final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
-
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- final Bundle in = new Bundle();
- in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
- in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
- final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in);
- return out.getParcelable(DocumentsContract.EXTRA_URI);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Calculate size of media contributed by given package under the calling
- * user. The meaning of "contributed" means it won't automatically be
- * deleted when the app is uninstalled.
- *
- * @hide
- */
- @TestApi
- @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
- public static @BytesLong long getContributedMediaSize(Context context, String packageName,
- UserHandle user) throws IOException {
- final UserManager um = context.getSystemService(UserManager.class);
- if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
- try {
- final ContentResolver resolver = context
- .createPackageContextAsUser(packageName, 0, user).getContentResolver();
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in);
- return out.getLong(Intent.EXTRA_INDEX);
- } catch (Exception e) {
- throw new IOException(e);
- }
- } else {
- throw new IOException("User " + user + " must be unlocked and running");
- }
- }
-
- /**
- * Delete all media contributed by given package under the calling user. The
- * meaning of "contributed" means it won't automatically be deleted when the
- * app is uninstalled.
- *
- * @hide
- */
- @TestApi
- @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
- public static void deleteContributedMedia(Context context, String packageName,
- UserHandle user) throws IOException {
- final UserManager um = context.getSystemService(UserManager.class);
- if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
- try {
- final ContentResolver resolver = context
- .createPackageContextAsUser(packageName, 0, user).getContentResolver();
- final Bundle in = new Bundle();
- in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in);
- } catch (Exception e) {
- throw new IOException(e);
- }
- } else {
- throw new IOException("User " + user + " must be unlocked and running");
- }
- }
-
- /** @hide */
- @TestApi
- public static void waitForIdle(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- client.call(WAIT_FOR_IDLE_CALL, null, null);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-
- /** @hide */
- public static void suicide(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver
- .acquireUnstableContentProviderClient(AUTHORITY)) {
- client.call(SUICIDE_CALL, null, null);
- } catch (Exception ignored) {
- }
- }
-
- /** @hide */
- @TestApi
- public static Uri scanFile(Context context, File file) {
- return scan(context, SCAN_FILE_CALL, file, false);
- }
-
- /** @hide */
- @TestApi
- public static Uri scanFileFromShell(Context context, File file) {
- return scan(context, SCAN_FILE_CALL, file, true);
- }
-
- /** @hide */
- @TestApi
- public static void scanVolume(Context context, File file) {
- scan(context, SCAN_VOLUME_CALL, file, false);
- }
-
- /** @hide */
- public static Uri scanFile(ContentProviderClient client, File file) {
- return scan(client, SCAN_FILE_CALL, file, false);
- }
-
- /** @hide */
- private static Uri scan(Context context, String method, File file,
- boolean originatedFromShell) {
- final ContentResolver resolver = context.getContentResolver();
- try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
- return scan(client, method, file, originatedFromShell);
- }
- }
-
- /** @hide */
- private static Uri scan(ContentProviderClient client, String method, File file,
- boolean originatedFromShell) {
- try {
- final Bundle in = new Bundle();
- in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
- in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell);
- final Bundle out = client.call(method, null, in);
- return out.getParcelable(Intent.EXTRA_STREAM);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
-}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ad8d553..462627e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -568,16 +568,47 @@
/**
* Activity Action: Show settings to enroll fingerprints, and setup PIN/Pattern/Pass if
* necessary.
+ * @deprecated See {@link #ACTION_BIOMETRIC_ENROLL}.
* <p>
* Input: Nothing.
* <p>
* Output: Nothing.
*/
+ @Deprecated
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_FINGERPRINT_ENROLL =
"android.settings.FINGERPRINT_ENROLL";
/**
+ * Activity Action: Show settings to enroll biometrics, and setup PIN/Pattern/Pass if
+ * necessary. By default, this prompts the user to enroll biometrics with strength
+ * Weak or above, as defined by the CDD. Only biometrics that meet or exceed Strong, as defined
+ * in the CDD are allowed to participate in Keystore operations.
+ * <p>
+ * Input: extras {@link #EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED} as an integer, with
+ * constants defined in {@link android.hardware.biometrics.BiometricManager.Authenticators},
+ * e.g. {@link android.hardware.biometrics.BiometricManager.Authenticators#BIOMETRIC_STRONG}.
+ * If not specified, the default behavior is
+ * {@link android.hardware.biometrics.BiometricManager.Authenticators#BIOMETRIC_WEAK}.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_BIOMETRIC_ENROLL =
+ "android.settings.BIOMETRIC_ENROLL";
+
+ /**
+ * Activity Extra: The minimum strength to request enrollment for.
+ * <p>
+ * This can be passed as an extra field to the {@link #ACTION_BIOMETRIC_ENROLL} intent to
+ * indicate that only enrollment for sensors that meet this strength should be shown. The
+ * value should be one of the biometric strength constants defined in
+ * {@link android.hardware.biometrics.BiometricManager.Authenticators}.
+ */
+ public static final String EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED =
+ "android.provider.extra.BIOMETRIC_MINIMUM_STRENGTH_REQUIRED";
+
+ /**
* Activity Action: Show settings to allow configuration of cast endpoints.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -648,6 +679,22 @@
"android.settings.NIGHT_DISPLAY_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of Dark theme.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DARK_THEME_SETTINGS =
+ "android.settings.DARK_THEME_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of locale.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -3853,6 +3900,12 @@
public static final String VOLUME_ACCESSIBILITY = "volume_a11y";
/**
+ * @hide
+ * Volume index for virtual assistant.
+ */
+ public static final String VOLUME_ASSISTANT = "volume_assistant";
+
+ /**
* Master volume (float in the range 0.0f to 1.0f).
*
* @hide
@@ -3930,7 +3983,7 @@
"" /*STREAM_SYSTEM_ENFORCED, no setting for this stream*/,
"" /*STREAM_DTMF, no setting for this stream*/,
"" /*STREAM_TTS, no setting for this stream*/,
- VOLUME_ACCESSIBILITY
+ VOLUME_ACCESSIBILITY, VOLUME_ASSISTANT
};
/**
@@ -4477,6 +4530,7 @@
PUBLIC_SETTINGS.add(VOLUME_ALARM);
PUBLIC_SETTINGS.add(VOLUME_NOTIFICATION);
PUBLIC_SETTINGS.add(VOLUME_BLUETOOTH_SCO);
+ PUBLIC_SETTINGS.add(VOLUME_ASSISTANT);
PUBLIC_SETTINGS.add(RINGTONE);
PUBLIC_SETTINGS.add(NOTIFICATION_SOUND);
PUBLIC_SETTINGS.add(ALARM_ALERT);
@@ -8376,6 +8430,20 @@
"navigation_mode";
/**
+ * Scale factor for the back gesture inset size on the left side of the screen.
+ * @hide
+ */
+ public static final String BACK_GESTURE_INSET_SCALE_LEFT =
+ "back_gesture_inset_scale_left";
+
+ /**
+ * Scale factor for the back gesture inset size on the right side of the screen.
+ * @hide
+ */
+ public static final String BACK_GESTURE_INSET_SCALE_RIGHT =
+ "back_gesture_inset_scale_right";
+
+ /**
* Controls whether aware is enabled.
* @hide
*/
@@ -8394,6 +8462,18 @@
public static final String TAP_GESTURE = "tap_gesture";
/**
+ * Controls whether the people strip is enabled.
+ * @hide
+ */
+ public static final String PEOPLE_STRIP = "people_strip";
+
+ /**
+ * Controls if window magnification is enabled.
+ * @hide
+ */
+ public static final String WINDOW_MAGNIFICATION = "window_magnification";
+
+ /**
* Keys we no longer back up under the current schema, but want to continue to
* process when restoring historical backup datasets.
*
@@ -9483,35 +9563,37 @@
*/
public static final String SMS_SHORT_CODE_RULE = "sms_short_code_rule";
- /**
- * Used to select TCP's default initial receiver window size in segments - defaults to a build config value
- * @hide
- */
- public static final String TCP_DEFAULT_INIT_RWND = "tcp_default_init_rwnd";
+ /**
+ * Used to select TCP's default initial receiver window size in segments - defaults to a
+ * build config value.
+ * @hide
+ */
+ public static final String TCP_DEFAULT_INIT_RWND = "tcp_default_init_rwnd";
- /**
- * Used to disable Tethering on a device - defaults to true
- * @hide
- */
- public static final String TETHER_SUPPORTED = "tether_supported";
+ /**
+ * Used to disable Tethering on a device - defaults to true.
+ * @hide
+ */
+ @SystemApi
+ public static final String TETHER_SUPPORTED = "tether_supported";
- /**
- * Used to require DUN APN on the device or not - defaults to a build config value
- * which defaults to false
- * @hide
- */
- public static final String TETHER_DUN_REQUIRED = "tether_dun_required";
+ /**
+ * Used to require DUN APN on the device or not - defaults to a build config value
+ * which defaults to false.
+ * @hide
+ */
+ public static final String TETHER_DUN_REQUIRED = "tether_dun_required";
- /**
- * Used to hold a gservices-provisioned apn value for DUN. If set, or the
- * corresponding build config values are set it will override the APN DB
- * values.
- * Consists of a comma seperated list of strings:
- * "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
- * note that empty fields can be omitted: "name,apn,,,,,,,,,310,260,,DUN"
- * @hide
- */
- public static final String TETHER_DUN_APN = "tether_dun_apn";
+ /**
+ * Used to hold a gservices-provisioned apn value for DUN. If set, or the
+ * corresponding build config values are set it will override the APN DB
+ * values.
+ * Consists of a comma separated list of strings:
+ * "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
+ * note that empty fields can be omitted: "name,apn,,,,,,,,,310,260,,DUN"
+ * @hide
+ */
+ public static final String TETHER_DUN_APN = "tether_dun_apn";
/**
* Used to disable trying to talk to any available tethering offload HAL.
@@ -10296,6 +10378,19 @@
public static final String ERROR_LOGCAT_PREFIX = "logcat_for_";
/**
+ * Maximum number of bytes of a system crash/ANR/etc. report that
+ * ActivityManagerService should send to DropBox, as a prefix of the
+ * dropbox tag of the report type. For example,
+ * "max_error_bytes_for_system_server_anr" controls the maximum
+ * number of bytes captured with system server ANR reports.
+ * <p>
+ * Type: int (max size in bytes)
+ *
+ * @hide
+ */
+ public static final String MAX_ERROR_BYTES_PREFIX = "max_error_bytes_for_";
+
+ /**
* The interval in minutes after which the amount of free storage left
* on the device is logged to the event log
*
@@ -13598,13 +13693,6 @@
public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader";
/**
- * Default user id to boot into. They map to user ids, for example, 10, 11, 12.
- *
- * @hide
- */
- public static final String DEFAULT_USER_ID_TO_BOOT_INTO = "default_boot_into_user_id";
-
- /**
* Persistent user id that is last logged in to.
*
* They map to user ids, for example, 10, 11, 12.
@@ -14194,6 +14282,20 @@
*/
public static final int ADD_WIFI_RESULT_ALREADY_EXISTS = 2;
+ /**
+ * Activity Action: Allows user to select current bug report handler.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_BUGREPORT_HANDLER_SETTINGS =
+ "android.settings.BUGREPORT_HANDLER_SETTINGS";
+
private static final String[] PM_WRITE_SETTINGS = {
android.Manifest.permission.WRITE_SETTINGS
};
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 70c8e5d..fed3d7ba 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -25,6 +25,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.ChangeId;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -1307,8 +1308,7 @@
* Broadcast action: When SMS-MMS db is being created. If file-based encryption is
* supported, this broadcast indicates creation of the db in credential-encrypted
* storage. A boolean is specified in {@link #EXTRA_IS_INITIAL_CREATE} to indicate if
- * this is the initial create of the db. Requires
- * {@link android.Manifest.permission#READ_SMS} to receive.
+ * this is the initial create of the db.
*
* @see #EXTRA_IS_INITIAL_CREATE
*
@@ -1389,7 +1389,6 @@
for (int i = 0; i < pduCount; i++) {
byte[] pdu = (byte[]) messages[i];
msgs[i] = SmsMessage.createFromPdu(pdu, format);
- if (msgs[i] != null) msgs[i].setSubId(subId);
}
return msgs;
}
@@ -4032,6 +4031,16 @@
@Retention(RetentionPolicy.SOURCE)
public @interface Skip464XlatStatus {}
+ /**
+ * Compat framework change ID for the APN db read permission change.
+ *
+ * In API level 30 and beyond, accessing the APN database will require the
+ * {@link android.Manifest.permission#WRITE_APN_SETTINGS} permission. This change ID tracks
+ * apps that are affected because they don't hold this permission.
+ * @hide
+ */
+ @ChangeId
+ public static final long APN_READING_PERMISSION_CHANGE_ID = 124107808L;
}
/**
diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java
index 00060ab..d646e23 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/core/java/android/se/omapi/SEService.java
@@ -22,11 +22,14 @@
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;
@@ -140,6 +143,10 @@
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;
@@ -270,4 +277,23 @@
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/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
new file mode 100644
index 0000000..cdd6584
--- /dev/null
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -0,0 +1,77 @@
+/*
+ * 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.security;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * This class provides access to file integrity related operations.
+ */
+@SystemService(Context.FILE_INTEGRITY_SERVICE)
+public final class FileIntegrityManager {
+ @NonNull private final IFileIntegrityService mService;
+
+ /** @hide */
+ public FileIntegrityManager(@NonNull IFileIntegrityService service) {
+ mService = service;
+ }
+
+ /**
+ * Returns true if APK Verity is supported on the device. When supported, an APK can be
+ * installed with a fs-verity signature (if verified with trusted App Source Certificate) for
+ * continuous on-access verification.
+ */
+ public boolean isApkVeritySupported() {
+ try {
+ // Go through the service just to avoid exposing the vendor controlled system property
+ // to all apps.
+ return mService.isApkVeritySupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the given certificate can be used to prove app's install source. Always
+ * return false if the feature is not supported.
+ *
+ * <p>A store can use this API to decide if a signature file needs to be downloaded. Also, if a
+ * store has shipped different certificates before (e.g. with stronger and weaker key), it can
+ * also use this API to download the best signature on the running device.
+ *
+ * @return whether the certificate is trusted in the system
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INSTALL_PACKAGES,
+ android.Manifest.permission.REQUEST_INSTALL_PACKAGES
+ })
+ public boolean isAppSourceCertificateTrusted(@NonNull X509Certificate certificate)
+ throws CertificateEncodingException {
+ try {
+ return mService.isAppSourceCertificateTrusted(certificate.getEncoded());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/net/ITetheringEventCallback.aidl b/core/java/android/security/IFileIntegrityService.aidl
similarity index 67%
copy from core/java/android/net/ITetheringEventCallback.aidl
copy to core/java/android/security/IFileIntegrityService.aidl
index d502088..ebb8bcb 100644
--- a/core/java/android/net/ITetheringEventCallback.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package android.net;
-
-import android.net.Network;
+package android.security;
/**
- * Callback class for receiving tethering changed events
+ * Binder interface to communicate with FileIntegrityService.
* @hide
*/
-oneway interface ITetheringEventCallback
-{
- void onUpstreamChanged(in Network network);
+interface IFileIntegrityService {
+ boolean isApkVeritySupported();
+ boolean isAppSourceCertificateTrusted(in byte[] certificateBytes);
}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 15ded8d..e4ba87c 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -94,6 +94,7 @@
public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
public static final int KM_TAG_ATTESTATION_ID_MANUFACTURER = KM_BYTES | 716;
public static final int KM_TAG_ATTESTATION_ID_MODEL = KM_BYTES | 717;
+ public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = KM_BOOL | 720;
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
diff --git a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
index 8801217..2b70c71 100644
--- a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
+++ b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
@@ -22,11 +22,10 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Objects;
/**
* A {@link KeyChainSnapshot} is protected with a key derived from the user's lock screen. This
@@ -220,8 +219,8 @@
if (mInstance.mUserSecretType == null) {
mInstance.mUserSecretType = TYPE_LOCKSCREEN;
}
- Preconditions.checkNotNull(mInstance.mLockScreenUiFormat);
- Preconditions.checkNotNull(mInstance.mKeyDerivationParams);
+ Objects.requireNonNull(mInstance.mLockScreenUiFormat);
+ Objects.requireNonNull(mInstance.mKeyDerivationParams);
if (mInstance.mSecret == null) {
mInstance.mSecret = new byte[]{};
}
diff --git a/core/java/android/security/keystore/recovery/KeyChainSnapshot.java b/core/java/android/security/keystore/recovery/KeyChainSnapshot.java
index 2f58471..fdc00f4 100644
--- a/core/java/android/security/keystore/recovery/KeyChainSnapshot.java
+++ b/core/java/android/security/keystore/recovery/KeyChainSnapshot.java
@@ -27,6 +27,7 @@
import java.security.cert.CertPath;
import java.security.cert.CertificateException;
import java.util.List;
+import java.util.Objects;
/**
* A snapshot of a version of the keystore. Two events can trigger the generation of a new snapshot:
@@ -272,9 +273,9 @@
"keyChainProtectionParams");
Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
"entryRecoveryData");
- Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob);
- Preconditions.checkNotNull(mInstance.mServerParams);
- Preconditions.checkNotNull(mInstance.mCertPath);
+ Objects.requireNonNull(mInstance.mEncryptedRecoveryKeyBlob);
+ Objects.requireNonNull(mInstance.mServerParams);
+ Objects.requireNonNull(mInstance.mCertPath);
return mInstance;
}
}
diff --git a/core/java/android/security/keystore/recovery/KeyDerivationParams.java b/core/java/android/security/keystore/recovery/KeyDerivationParams.java
index d036d14..6c541da 100644
--- a/core/java/android/security/keystore/recovery/KeyDerivationParams.java
+++ b/core/java/android/security/keystore/recovery/KeyDerivationParams.java
@@ -22,10 +22,9 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* Collection of parameters which define a key derivation function.
@@ -109,7 +108,7 @@
private KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt,
int memoryDifficulty) {
mAlgorithm = algorithm;
- mSalt = Preconditions.checkNotNull(salt);
+ mSalt = Objects.requireNonNull(salt);
mMemoryDifficulty = memoryDifficulty;
}
diff --git a/core/java/android/security/keystore/recovery/RecoveryCertPath.java b/core/java/android/security/keystore/recovery/RecoveryCertPath.java
index 51bd2ae..ff10b29 100644
--- a/core/java/android/security/keystore/recovery/RecoveryCertPath.java
+++ b/core/java/android/security/keystore/recovery/RecoveryCertPath.java
@@ -20,13 +20,12 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
-
import java.io.ByteArrayInputStream;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
+import java.util.Objects;
/**
* The certificate path of the recovery service.
@@ -67,7 +66,7 @@
}
private RecoveryCertPath(@NonNull byte[] encodedCertPath) {
- mEncodedCertPath = Preconditions.checkNotNull(encodedCertPath);
+ mEncodedCertPath = Objects.requireNonNull(encodedCertPath);
}
private RecoveryCertPath(Parcel in) {
@@ -98,13 +97,13 @@
@NonNull
private static byte[] encodeCertPath(@NonNull CertPath certPath)
throws CertificateEncodingException {
- Preconditions.checkNotNull(certPath);
+ Objects.requireNonNull(certPath);
return certPath.getEncoded(CERT_PATH_ENCODING);
}
@NonNull
private static CertPath decodeCertPath(@NonNull byte[] bytes) throws CertificateException {
- Preconditions.checkNotNull(bytes);
+ Objects.requireNonNull(bytes);
CertificateFactory certFactory;
try {
certFactory = CertificateFactory.getInstance("X.509");
diff --git a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java
index 665c937..0cb69a4 100644
--- a/core/java/android/security/keystore/recovery/WrappedApplicationKey.java
+++ b/core/java/android/security/keystore/recovery/WrappedApplicationKey.java
@@ -22,7 +22,7 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
* Helper class with data necessary recover a single application key, given a recovery key.
@@ -106,8 +106,8 @@
* @throws NullPointerException if some required fields were not set.
*/
public @NonNull WrappedApplicationKey build() {
- Preconditions.checkNotNull(mInstance.mAlias);
- Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial);
+ Objects.requireNonNull(mInstance.mAlias);
+ Objects.requireNonNull(mInstance.mEncryptedKeyMaterial);
return mInstance;
}
}
@@ -120,8 +120,8 @@
*/
@Deprecated
public WrappedApplicationKey(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) {
- mAlias = Preconditions.checkNotNull(alias);
- mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
+ mAlias = Objects.requireNonNull(alias);
+ mEncryptedKeyMaterial = Objects.requireNonNull(encryptedKeyMaterial);
}
/**
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index e53ebad..72e9ad0 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -23,6 +23,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
+import android.view.inputmethod.InlineSuggestionsRequest;
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
@@ -116,20 +117,34 @@
*/
private final @RequestFlags int mFlags;
+ /**
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
+ *
+ * @return the suggestionspec
+ */
+ private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
+
private void onConstructed() {
Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
}
- // Code below generated by codegen v1.0.0.
+ // 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/FillRequest.java
//
- // CHECKSTYLE:OFF Generated code
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
/** @hide */
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -184,6 +199,11 @@
*
* @return any combination of {@link #FLAG_MANUAL_REQUEST} and
* {@link #FLAG_COMPATIBILITY_MODE_REQUEST}.
+ * @param inlineSuggestionsRequest
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
* @hide
*/
@DataClass.Generated.Member
@@ -191,7 +211,8 @@
int id,
@NonNull List<FillContext> fillContexts,
@Nullable Bundle clientState,
- @RequestFlags int flags) {
+ @RequestFlags int flags,
+ @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
this.mId = id;
this.mFillContexts = fillContexts;
com.android.internal.util.AnnotationValidations.validate(
@@ -203,6 +224,7 @@
mFlags,
FLAG_MANUAL_REQUEST
| FLAG_COMPATIBILITY_MODE_REQUEST);
+ this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
onConstructed();
}
@@ -256,6 +278,19 @@
return mFlags;
}
+ /**
+ * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+ * with this request.
+ *
+ * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
+ *
+ * @return the suggestionspec
+ */
+ @DataClass.Generated.Member
+ public @Nullable InlineSuggestionsRequest getInlineSuggestionsRequest() {
+ return mInlineSuggestionsRequest;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -266,29 +301,63 @@
"id = " + mId + ", " +
"fillContexts = " + mFillContexts + ", " +
"clientState = " + mClientState + ", " +
- "flags = " + requestFlagsToString(mFlags) +
+ "flags = " + requestFlagsToString(mFlags) + ", " +
+ "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
" }";
}
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ 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 (mClientState != null) flg |= 0x4;
+ if (mInlineSuggestionsRequest != null) flg |= 0x10;
dest.writeByte(flg);
dest.writeInt(mId);
dest.writeParcelableList(mFillContexts, flags);
if (mClientState != null) dest.writeBundle(mClientState);
dest.writeInt(mFlags);
+ if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ FillRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int id = in.readInt();
+ List<FillContext> fillContexts = new ArrayList<>();
+ in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
+ Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
+ int flags = in.readInt();
+ InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+
+ this.mId = id;
+ this.mFillContexts = fillContexts;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFillContexts);
+ this.mClientState = clientState;
+ this.mFlags = flags;
+
+ Preconditions.checkFlagsArgument(
+ mFlags,
+ FLAG_MANUAL_REQUEST
+ | FLAG_COMPATIBILITY_MODE_REQUEST);
+ this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+
+ onConstructed();
+ }
+
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<FillRequest> CREATOR
= new Parcelable.Creator<FillRequest>() {
@@ -298,31 +367,21 @@
}
@Override
- @SuppressWarnings({"unchecked", "RedundantCast"})
- public FillRequest createFromParcel(Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- int id = in.readInt();
- List<FillContext> fillContexts = new ArrayList<>();
- in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
- Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
- int flags = in.readInt();
- return new FillRequest(
- id,
- fillContexts,
- clientState,
- flags);
+ public FillRequest createFromParcel(@NonNull Parcel in) {
+ return new FillRequest(in);
}
};
@DataClass.Generated(
- time = 1565152134349L,
- codegenVersion = "1.0.0",
+ time = 1575928271155L,
+ codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
- inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}
+
+ //@formatter:on
+ // End of generated code
+
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index c99fe61..02a6390 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -25,6 +25,7 @@
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;
@@ -86,6 +87,7 @@
private int mRequestId;
private final @Nullable UserData mUserData;
private final @Nullable int[] mCancelIds;
+ private final @Nullable ParceledListSlice<Slice> mInlineSuggestionSlices;
private FillResponse(@NonNull Builder builder) {
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
@@ -103,6 +105,8 @@
mRequestId = INVALID_REQUEST_ID;
mUserData = builder.mUserData;
mCancelIds = builder.mCancelIds;
+ mInlineSuggestionSlices = (builder.mInlineSuggestionSlices != null)
+ ? new ParceledListSlice<>(builder.mInlineSuggestionSlices) : null;
}
/** @hide */
@@ -195,6 +199,11 @@
return mCancelIds;
}
+ /** @hide */
+ public List<Slice> getInlineSuggestionSlices() {
+ return (mInlineSuggestionSlices != null) ? mInlineSuggestionSlices.getList() : null;
+ }
+
/**
* Builder for {@link FillResponse} objects. You must to provide at least
* one dataset or set an authentication intent with a presentation view.
@@ -215,6 +224,7 @@
private boolean mDestroyed;
private UserData mUserData;
private int[] mCancelIds;
+ private ArrayList<Slice> mInlineSuggestionSlices;
/**
* Triggers a custom UI before before autofilling the screen with any data set in this
@@ -570,6 +580,20 @@
}
/**
+ * 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:
@@ -670,7 +694,9 @@
if (mCancelIds != null) {
builder.append(", mCancelIds=").append(mCancelIds.length);
}
-
+ if (mInlineSuggestionSlices != null) {
+ builder.append(", inlinedSuggestions=").append(mInlineSuggestionSlices.getList());
+ }
return builder.append("]").toString();
}
@@ -699,7 +725,7 @@
parcel.writeParcelableArray(mFieldClassificationIds, flags);
parcel.writeInt(mFlags);
parcel.writeIntArray(mCancelIds);
-
+ parcel.writeParcelable(mInlineSuggestionSlices, flags);
parcel.writeInt(mRequestId);
}
@@ -755,6 +781,16 @@
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/service/controls/BooleanAction.java b/core/java/android/service/controls/BooleanAction.java
index 8508c63..877f82e 100644
--- a/core/java/android/service/controls/BooleanAction.java
+++ b/core/java/android/service/controls/BooleanAction.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
import android.os.Parcel;
/**
@@ -26,6 +27,8 @@
*/
public final class BooleanAction extends ControlAction {
+ private static final String KEY_NEW_STATE = "key_new_state";
+
private final boolean mNewState;
/**
@@ -49,9 +52,9 @@
mNewState = newValue;
}
- BooleanAction(Parcel in) {
- super(in);
- mNewState = in.readByte() == 1;
+ BooleanAction(Bundle b) {
+ super(b);
+ mNewState = b.getBoolean(KEY_NEW_STATE);
}
/**
@@ -72,17 +75,17 @@
return ControlAction.TYPE_BOOLEAN;
}
-
@Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeByte(mNewState ? (byte) 1 : (byte) 0);
+ protected Bundle getDataBundle() {
+ Bundle b = super.getDataBundle();
+ b.putBoolean(KEY_NEW_STATE, mNewState);
+ return b;
}
public static final @NonNull Creator<BooleanAction> CREATOR = new Creator<BooleanAction>() {
@Override
public BooleanAction createFromParcel(Parcel source) {
- return new BooleanAction(source);
+ return new BooleanAction(source.readBundle());
}
@Override
diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java
index a69408c..2c17e89 100644
--- a/core/java/android/service/controls/Control.java
+++ b/core/java/android/service/controls/Control.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
@@ -55,7 +56,7 @@
private final @NonNull Icon mIcon;
private final @NonNull CharSequence mTitle;
private final @Nullable ColorStateList mTintColor;
- private final @NonNull Intent mAppIntent;
+ private final @NonNull PendingIntent mAppIntent;
private final @ControlTemplate.TemplateType int mPrimaryType;
/**
@@ -64,14 +65,15 @@
* @param title the user facing name of this control (e.g. "Bedroom thermostat").
* @param tintColor the color to tint parts of the element UI. If {@code null} is passed, the
* system accent color will be used.
- * @param appIntent an intent linking to a page to interact with the corresponding device.
+ * @param appIntent a {@link PendingIntent} linking to a page to interact with the
+ * corresponding device.
* @param primaryType the primary template for this type.
*/
public Control(@NonNull String controlId,
@NonNull Icon icon,
@NonNull CharSequence title,
@Nullable ColorStateList tintColor,
- @NonNull Intent appIntent,
+ @NonNull PendingIntent appIntent,
int primaryType) {
Preconditions.checkNotNull(controlId);
Preconditions.checkNotNull(icon);
@@ -94,7 +96,7 @@
} else {
mTintColor = null;
}
- mAppIntent = Intent.CREATOR.createFromParcel(in);
+ mAppIntent = PendingIntent.CREATOR.createFromParcel(in);
mPrimaryType = in.readInt();
}
@@ -119,7 +121,7 @@
}
@NonNull
- public Intent getAppIntent() {
+ public PendingIntent getAppIntent() {
return mAppIntent;
}
@@ -175,17 +177,17 @@
private Icon mIcon;
private CharSequence mTitle = "";
private ColorStateList mTintColor;
- private @Nullable Intent mAppIntent;
+ private @Nullable PendingIntent mAppIntent;
private @ControlTemplate.TemplateType int mPrimaryType = ControlTemplate.TYPE_NONE;
/**
* @param controlId the identifier for the {@link Control}.
* @param icon the icon for the {@link Control}.
- * @param appIntent the intent linking to the device Activity.
+ * @param appIntent the pending intent linking to the device Activity.
*/
public Builder(@NonNull String controlId,
@NonNull Icon icon,
- @NonNull Intent appIntent) {
+ @NonNull PendingIntent appIntent) {
Preconditions.checkNotNull(controlId);
Preconditions.checkNotNull(icon);
Preconditions.checkNotNull(appIntent);
@@ -256,7 +258,7 @@
* @return {@code this}
*/
@NonNull
- public Builder setAppIntent(@NonNull Intent appIntent) {
+ public Builder setAppIntent(@NonNull PendingIntent appIntent) {
Preconditions.checkNotNull(appIntent);
mAppIntent = appIntent;
return this;
diff --git a/core/java/android/service/controls/ControlAction.java b/core/java/android/service/controls/ControlAction.java
index 8b75955..0a7c97c 100644
--- a/core/java/android/service/controls/ControlAction.java
+++ b/core/java/android/service/controls/ControlAction.java
@@ -16,9 +16,11 @@
package android.service.controls;
+import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -36,25 +38,38 @@
*/
public abstract class ControlAction implements Parcelable {
+ private static final String KEY_TEMPLATE_ID = "key_template_id";
+ private static final String KEY_CHALLENGE_VALUE = "key_challenge_value";
+
+ public static final ControlAction UNKNOWN_ACTION = new ControlAction() {
+
+ @Override
+ public int getActionType() {
+ return TYPE_UNKNOWN;
+ }
+ };
+
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
+ TYPE_UNKNOWN,
TYPE_BOOLEAN,
TYPE_FLOAT
})
public @interface ActionType {};
+ public static final @ActionType int TYPE_UNKNOWN = 0;
/**
* The identifier of {@link BooleanAction}.
*/
- public static final @ActionType int TYPE_BOOLEAN = 0;
+ public static final @ActionType int TYPE_BOOLEAN = 1;
/**
* The identifier of {@link FloatAction}.
*/
- public static final @ActionType int TYPE_FLOAT = 1;
+ public static final @ActionType int TYPE_FLOAT = 2;
/**
* @hide
@@ -120,13 +135,9 @@
/**
* @hide
*/
- ControlAction(Parcel in) {
- mTemplateId = in.readString();
- if (in.readByte() == 1) {
- mChallengeValue = in.readString();
- } else {
- mChallengeValue = null;
- }
+ ControlAction(Bundle b) {
+ mTemplateId = b.getString(KEY_TEMPLATE_ID);
+ mChallengeValue = b.getString(KEY_CHALLENGE_VALUE);
}
/**
@@ -151,15 +162,24 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public final void writeToParcel(Parcel dest, int flags) {
dest.writeInt(getActionType());
- dest.writeString(mTemplateId);
- if (mChallengeValue != null) {
- dest.writeByte((byte) 1);
- dest.writeString(mChallengeValue);
- } else {
- dest.writeByte((byte) 0);
- }
+ dest.writeBundle(getDataBundle());
+ }
+
+ /**
+ * Obtain a {@link Bundle} describing this object populated with data.
+ *
+ * Implementations in subclasses should populate the {@link Bundle} returned by
+ * {@link ControlAction}.
+ * @return a {@link Bundle} containing the data that represents this object.
+ */
+ @CallSuper
+ protected Bundle getDataBundle() {
+ Bundle b = new Bundle();
+ b.putString(KEY_TEMPLATE_ID, mTemplateId);
+ b.putString(KEY_CHALLENGE_VALUE, mChallengeValue);
+ return b;
}
public static final @NonNull Creator<ControlAction> CREATOR = new Creator<ControlAction>() {
@@ -175,6 +195,7 @@
}
};
+
private static ControlAction createActionFromType(@ActionType int type, Parcel source) {
switch(type) {
case TYPE_BOOLEAN:
@@ -182,7 +203,8 @@
case TYPE_FLOAT:
return FloatAction.CREATOR.createFromParcel(source);
default:
- return null;
+ source.readBundle();
+ return UNKNOWN_ACTION;
}
}
diff --git a/core/java/android/service/controls/ControlButton.java b/core/java/android/service/controls/ControlButton.java
index fed3115..969c0a7 100644
--- a/core/java/android/service/controls/ControlButton.java
+++ b/core/java/android/service/controls/ControlButton.java
@@ -29,29 +29,29 @@
*/
public class ControlButton implements Parcelable {
- private final boolean mActive;
+ private final boolean mChecked;
private final @NonNull Icon mIcon;
private final @NonNull CharSequence mContentDescription;
/**
- * @param active true if the button should be rendered as active.
+ * @param checked true if the button should be rendered as active.
* @param icon icon to display in the button.
* @param contentDescription content description for the button.
*/
- public ControlButton(boolean active, @NonNull Icon icon,
+ public ControlButton(boolean checked, @NonNull Icon icon,
@NonNull CharSequence contentDescription) {
Preconditions.checkNotNull(icon);
Preconditions.checkNotNull(contentDescription);
- mActive = active;
+ mChecked = checked;
mIcon = icon;
mContentDescription = contentDescription;
}
/**
- * Whether the button should be rendered in its active state.
+ * Whether the button should be rendered in a checked state.
*/
- public boolean isActive() {
- return mActive;
+ public boolean isChecked() {
+ return mChecked;
}
/**
@@ -78,13 +78,13 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeByte(mActive ? (byte) 1 : (byte) 0);
+ dest.writeByte(mChecked ? (byte) 1 : (byte) 0);
mIcon.writeToParcel(dest, flags);
dest.writeCharSequence(mContentDescription);
}
ControlButton(Parcel in) {
- mActive = in.readByte() != 0;
+ mChecked = in.readByte() != 0;
mIcon = Icon.CREATOR.createFromParcel(in);
mContentDescription = in.readCharSequence();
}
diff --git a/core/java/android/service/controls/ControlState.java b/core/java/android/service/controls/ControlState.java
index 804aef7..1477f9f4 100644
--- a/core/java/android/service/controls/ControlState.java
+++ b/core/java/android/service/controls/ControlState.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
import android.os.Parcel;
@@ -33,11 +34,11 @@
* Current state for a {@link Control}.
*
* Collects information to render the current state of a {@link Control} as well as possible action
- * that can be performed on it. Some of the information may temporarily override the defaults
- * provided by the corresponding {@link Control}, while this state is being displayed.
- *
- * Additionally, this can be used to modify information related to the corresponding
- * {@link Control}.
+ * that can be performed on it.
+ * <p>
+ * Additionally, this object is used to modify elements from the {@link Control} such as icons,
+ * colors, names and intents. This information will last until it is again modified by a
+ * {@link ControlState}.
* @hide
*/
public final class ControlState implements Parcelable {
@@ -74,58 +75,81 @@
*/
public static final int STATUS_DISABLED = 3;
- private final @NonNull Control mControl;
+ private final @NonNull String mControlId;
private final @Status int mStatus;
private final @NonNull ControlTemplate mControlTemplate;
private final @NonNull CharSequence mStatusText;
- private final @Nullable Icon mOverrideIcon;
- private final @Nullable ColorStateList mOverrideTint;
+ private final @Nullable CharSequence mTitle;
+ private final @Nullable PendingIntent mAppIntent;
+ private final @Nullable Icon mIcon;
+ private final @Nullable ColorStateList mTint;
/**
- * @param control the {@link Control} this state should be applied to. Can be used to
- * update information about the {@link Control}
+ * @param controlId the identifier of the {@link Control} this object refers to.
* @param status the current status of the {@link Control}.
- * @param controlTemplate the template to be used to render the {@link Control}.
- * @param statusText the text describing the current status.
- * @param overrideIcon the icon to temporarily override the one provided in
- * {@link Control#getIcon()}. Pass {@code null} to use the icon in
- * {@link Control#getIcon()}.
- * @param overrideTint the colors to temporarily override those provided in
- * {@link Control#getTint()}. Pass {@code null} to use the colors in
- * {@link Control#getTint()}.
+ * @param controlTemplate the template to be used to render the {@link Control}. This can be
+ * of a different
+ * {@link android.service.controls.ControlTemplate.TemplateType} than the
+ * one defined in {@link Control#getPrimaryType}
+ * @param statusText the user facing text describing the current status.
+ * @param title the title to replace the one set in the {@link Control} or set in the
+ * last {@link ControlState}. Pass {@code null} to use the last value set for this
+ * {@link Control}
+ * @param appIntent the {@link PendingIntent} to replace the one set in the {@link Control} or
+ * set in the last {@link ControlState}. Pass {@code null} to use the last
+ * value set for this {@link Control}.
+ * @param icon the icon to replace the one set in the {@link Control} or set in the last
+ * {@link ControlState}. Pass {@code null} to use the last value set for this
+ * {@link Control}.
+ * @param tint the colors to replace those set in the {@link Control} or set in the last
+ * {@link ControlState}. Pass {@code null} to use the last value set for this
+ * {@link Control}.
*/
- public ControlState(@NonNull Control control,
+ public ControlState(@NonNull String controlId,
int status,
@NonNull ControlTemplate controlTemplate,
@NonNull CharSequence statusText,
- @Nullable Icon overrideIcon,
- @Nullable ColorStateList overrideTint) {
- Preconditions.checkNotNull(control);
+ @Nullable CharSequence title,
+ @Nullable PendingIntent appIntent,
+ @Nullable Icon icon,
+ @Nullable ColorStateList tint) {
+ Preconditions.checkNotNull(controlId);
Preconditions.checkNotNull(controlTemplate);
Preconditions.checkNotNull(statusText);
-
- mControl = control;
+ mControlId = controlId;
mStatus = status;
mControlTemplate = controlTemplate;
- mOverrideIcon = overrideIcon;
mStatusText = statusText;
- mOverrideTint = overrideTint;
+ mTitle = title;
+ mAppIntent = appIntent;
+ mIcon = icon;
+ mTint = tint;
}
ControlState(Parcel in) {
- mControl = Control.CREATOR.createFromParcel(in);
+ mControlId = in.readString();
mStatus = in.readInt();
mControlTemplate = ControlTemplate.CREATOR.createFromParcel(in);
mStatusText = in.readCharSequence();
if (in.readByte() == 1) {
- mOverrideIcon = Icon.CREATOR.createFromParcel(in);
+ mTitle = in.readCharSequence();
} else {
- mOverrideIcon = null;
+ mTitle = null;
}
if (in.readByte() == 1) {
- mOverrideTint = ColorStateList.CREATOR.createFromParcel(in);
+ mAppIntent = PendingIntent.CREATOR.createFromParcel(in);
} else {
- mOverrideTint = null;
+ mAppIntent = null;
+ }
+ if (in.readByte() == 1) {
+ mIcon = Icon.CREATOR.createFromParcel(in);
+ } else {
+ mIcon = null;
+ }
+ if (in.readByte() == 1) {
+ mTint = ColorStateList.CREATOR.createFromParcel(in);
+ } else {
+ mTint = null;
}
}
@@ -134,6 +158,21 @@
return 0;
}
+ @NonNull
+ public String getControlId() {
+ return mControlId;
+ }
+
+ @Nullable
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ @Nullable
+ public PendingIntent getAppIntent() {
+ return mAppIntent;
+ }
+
@Status
public int getStatus() {
return mStatus;
@@ -145,8 +184,8 @@
}
@Nullable
- public Icon getOverrideIcon() {
- return mOverrideIcon;
+ public Icon getIcon() {
+ return mIcon;
}
@NonNull
@@ -155,35 +194,37 @@
}
@Nullable
- public ColorStateList getOverrideTint() {
- return mOverrideTint;
- }
-
- @NonNull
- public Control getControl() {
- return mControl;
- }
-
- @NonNull
- public String getControlId() {
- return mControl.getControlId();
+ public ColorStateList getTint() {
+ return mTint;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- mControl.writeToParcel(dest, flags);
+ dest.writeString(mControlId);
dest.writeInt(mStatus);
mControlTemplate.writeToParcel(dest, flags);
dest.writeCharSequence(mStatusText);
- if (mOverrideIcon != null) {
+ if (mTitle != null) {
dest.writeByte((byte) 1);
- mOverrideIcon.writeToParcel(dest, flags);
+ dest.writeCharSequence(mTitle);
} else {
dest.writeByte((byte) 0);
}
- if (mOverrideTint != null) {
+ if (mAppIntent != null) {
dest.writeByte((byte) 1);
- mOverrideTint.writeToParcel(dest, flags);
+ mAppIntent.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ if (mIcon != null) {
+ dest.writeByte((byte) 1);
+ mIcon.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ if (mTint != null) {
+ dest.writeByte((byte) 1);
+ mTint.writeToParcel(dest, flags);
} else {
dest.writeByte((byte) 0);
}
@@ -213,19 +254,22 @@
* </ul>
*/
public static class Builder {
- private @NonNull Control mControl;
+ private @NonNull String mControlId;
private @Status int mStatus = STATUS_OK;
private @NonNull ControlTemplate mControlTemplate = ControlTemplate.NO_TEMPLATE;
private @NonNull CharSequence mStatusText = "";
- private @Nullable Icon mOverrideIcon;
- private @Nullable ColorStateList mOverrideTint;
+ private @Nullable CharSequence mTitle;
+ private @Nullable PendingIntent mAppIntent;
+ private @Nullable Icon mIcon;
+ private @Nullable ColorStateList mTint;
/**
- * @param control the {@link Control} that the resulting {@link ControlState} refers to.
+ * @param controlId the identifier of the {@link Control} that the resulting
+ * {@link ControlState} refers to.
*/
- public Builder(@NonNull Control control) {
- Preconditions.checkNotNull(control);
- mControl = control;
+ public Builder(@NonNull String controlId) {
+ Preconditions.checkNotNull(controlId);
+ mControlId = controlId;
}
/**
@@ -234,21 +278,24 @@
*/
public Builder(@NonNull ControlState controlState) {
Preconditions.checkNotNull(controlState);
- mControl = controlState.mControl;
+ mControlId = controlState.mControlId;
+ mStatus = controlState.mStatus;
mControlTemplate = controlState.mControlTemplate;
- mOverrideIcon = controlState.mOverrideIcon;
mStatusText = controlState.mStatusText;
- mOverrideTint = controlState.mOverrideTint;
+ mTitle = controlState.mTitle;
+ mAppIntent = controlState.mAppIntent;
+ mIcon = controlState.mIcon;
+ mTint = controlState.mTint;
}
/**
- * @param control the updated {@link Control} information.
+ * @param controlId the identifier of the {@link Control} for the resulting object.
* @return {@code this}
*/
@NonNull
- public Builder setControl(@NonNull Control control) {
- mControl = control;
+ public Builder setControlId(@NonNull String controlId) {
+ mControlId = controlId;
return this;
}
@@ -285,24 +332,50 @@
}
/**
- * @param overrideIcon the icon to override the one defined in the corresponding
- * {@code Control}. Pass {@code null} to remove the override.
+ * @param title the title to replace the one defined in the corresponding {@link Control} or
+ * set by the last {@link ControlState}. Pass {@code null} to keep the last
+ * value.
* @return {@code this}
*/
@NonNull
- public Builder setOverrideIcon(@Nullable Icon overrideIcon) {
- mOverrideIcon = overrideIcon;
+ public Builder setTitle(@Nullable CharSequence title) {
+ mTitle = title;
return this;
}
/**
- * @param overrideTint the colors to override the ones defined in the corresponding
- * {@code Control}. Pass {@code null} to remove the override.
+ * @param appIntent the Pending Intent to replace the one defined in the corresponding
+ * {@link Control} or set by the last {@link ControlState}. Pass
+ * {@code null} to keep the last value.
* @return {@code this}
*/
@NonNull
- public Builder setOverrideTint(@Nullable ColorStateList overrideTint) {
- mOverrideTint = overrideTint;
+ public Builder setAppIntent(@Nullable PendingIntent appIntent) {
+ mAppIntent = appIntent;
+ return this;
+ }
+
+ /**
+ * @param icon the title to replace the one defined in the corresponding {@link Control} or
+ * set by the last {@link ControlState}. Pass {@code null} to keep the last
+ * value.
+ * @return {@code this}
+ */
+ @NonNull
+ public Builder setIcon(@Nullable Icon icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * @param tint the title to replace the one defined in the corresponding {@link Control} or
+ * set by the last {@link ControlState}. Pass {@code null} to keep the last
+ * value.
+ * @return {@code this}
+ */
+ @NonNull
+ public Builder setTint(@Nullable ColorStateList tint) {
+ mTint = tint;
return this;
}
@@ -310,8 +383,8 @@
* @return a new {@link ControlState}
*/
public ControlState build() {
- return new ControlState(mControl, mStatus, mControlTemplate, mStatusText,
- mOverrideIcon, mOverrideTint);
+ return new ControlState(mControlId, mStatus, mControlTemplate, mStatusText,
+ mTitle, mAppIntent, mIcon, mTint);
}
}
}
diff --git a/core/java/android/service/controls/ControlTemplate.java b/core/java/android/service/controls/ControlTemplate.java
index e559862..8bcabd6 100644
--- a/core/java/android/service/controls/ControlTemplate.java
+++ b/core/java/android/service/controls/ControlTemplate.java
@@ -16,8 +16,10 @@
package android.service.controls;
+import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -40,6 +42,8 @@
*/
public abstract class ControlTemplate implements Parcelable {
+ private static final String KEY_TEMPLATE_ID = "key_template_id";
+
/**
* Singleton representing a {@link Control} with no input.
*/
@@ -114,17 +118,28 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public final void writeToParcel(Parcel dest, int flags) {
dest.writeInt(getTemplateType());
- dest.writeString(mTemplateId);
+ dest.writeBundle(getDataBundle());
+ }
+
+ /**
+ * Obtain a {@link Bundle} describing this object populated with data.
+ * @return a {@link Bundle} containing the data that represents this object.
+ */
+ @CallSuper
+ protected Bundle getDataBundle() {
+ Bundle b = new Bundle();
+ b.putString(KEY_TEMPLATE_ID, mTemplateId);
+ return b;
}
private ControlTemplate() {
mTemplateId = "";
}
- ControlTemplate(Parcel in) {
- mTemplateId = in.readString();
+ ControlTemplate(@NonNull Bundle b) {
+ mTemplateId = b.getString(KEY_TEMPLATE_ID);
}
/**
@@ -148,6 +163,7 @@
}
};
+
private static ControlTemplate createTemplateFromType(@TemplateType int type, Parcel source) {
switch(type) {
case TYPE_TOGGLE:
@@ -159,9 +175,9 @@
case TYPE_DISCRETE_TOGGLE:
return DiscreteToggleTemplate.CREATOR.createFromParcel(source);
case TYPE_NONE:
- return NO_TEMPLATE;
default:
- return null;
+ source.readBundle();
+ return NO_TEMPLATE;
}
}
}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
new file mode 100644
index 0000000..193b2bc
--- /dev/null
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -0,0 +1,147 @@
+/*
+ * 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.controls;
+
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+
+import java.util.List;
+
+/**
+ * Service implementation allowing applications to contribute controls to the
+ * System UI.
+ * @hide
+ */
+public abstract class ControlsProviderService extends Service {
+
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String CONTROLS_ACTION = "android.service.controls.ControlsProviderService";
+
+ private IControlsProviderCallback mCallback;
+ private RequestHandler mHandler;
+
+ /**
+ * Signal to retrieve all Controls. When complete, call
+ * {@link IControlsProviderCallback#onLoad} to inform the caller.
+ */
+ public abstract void load();
+
+ /**
+ * Informs the service that the caller is listening for updates to the given controlIds.
+ * {@link IControlsProviderCallback#onRefreshState} should be called any time
+ * there are Control updates to render.
+ */
+ public abstract void subscribe(@NonNull List<String> controlIds);
+
+ /**
+ * Informs the service that the caller is done listening for updates,
+ * and any calls to {@link IControlsProviderCallback#onRefreshState} will be ignored.
+ */
+ public abstract void unsubscribe();
+
+ /**
+ * The user has interacted with a Control. The action is dictated by the type of
+ * {@link ControlAction} that was sent.
+ */
+ public abstract void onAction(@NonNull String controlId, @NonNull ControlAction action);
+
+ protected IControlsProviderCallback getControlsProviderCallback() {
+ return mCallback;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ mHandler = new RequestHandler(Looper.getMainLooper());
+
+ Bundle bundle = intent.getBundleExtra("CALLBACK_BUNDLE");
+ IBinder callbackBinder = bundle.getBinder("CALLBACK_BINDER");
+ mCallback = IControlsProviderCallback.Stub.asInterface(callbackBinder);
+
+ return new IControlsProvider.Stub() {
+ public void load() {
+ mHandler.sendEmptyMessage(RequestHandler.MSG_LOAD);
+ }
+
+ public void subscribe(List<String> ids) {
+ mHandler.obtainMessage(RequestHandler.MSG_SUBSCRIBE, ids).sendToTarget();
+ }
+
+ public void unsubscribe() {
+ mHandler.sendEmptyMessage(RequestHandler.MSG_UNSUBSCRIBE);
+ }
+
+ public void onAction(String id, ControlAction action) {
+ ActionMessage msg = new ActionMessage(id, action);
+ mHandler.obtainMessage(RequestHandler.MSG_SUBSCRIBE, msg).sendToTarget();
+ }
+ };
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mCallback = null;
+ mHandler = null;
+ return true;
+ }
+
+ private class RequestHandler extends Handler {
+ private static final int MSG_LOAD = 1;
+ private static final int MSG_SUBSCRIBE = 2;
+ private static final int MSG_UNSUBSCRIBE = 3;
+ private static final int MSG_ON_ACTION = 4;
+
+ RequestHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_LOAD:
+ ControlsProviderService.this.load();
+ break;
+ case MSG_SUBSCRIBE:
+ List<String> ids = (List<String>) msg.obj;
+ ControlsProviderService.this.subscribe(ids);
+ break;
+ case MSG_UNSUBSCRIBE:
+ ControlsProviderService.this.unsubscribe();
+ break;
+ case MSG_ON_ACTION:
+ ActionMessage aMsg = (ActionMessage) msg.obj;
+ ControlsProviderService.this.onAction(aMsg.mId, aMsg.mAction);
+ break;
+ }
+ }
+ }
+
+ private class ActionMessage {
+ final String mId;
+ final ControlAction mAction;
+
+ ActionMessage(String id, ControlAction action) {
+ this.mId = id;
+ this.mAction = action;
+ }
+ }
+}
diff --git a/core/java/android/service/controls/DiscreteToggleTemplate.java b/core/java/android/service/controls/DiscreteToggleTemplate.java
index 5167af4..5718252 100644
--- a/core/java/android/service/controls/DiscreteToggleTemplate.java
+++ b/core/java/android/service/controls/DiscreteToggleTemplate.java
@@ -17,6 +17,7 @@
package android.service.controls;
import android.annotation.NonNull;
+import android.os.Bundle;
import android.os.Parcel;
import com.android.internal.util.Preconditions;
@@ -34,6 +35,9 @@
*/
public class DiscreteToggleTemplate extends ControlTemplate {
+ private static final String KEY_NEGATIVE_BUTTON = "key_negative_button";
+ private static final String KEY_POSITIVE_BUTTON = "key_positive_button";
+
private final @NonNull ControlButton mNegativeButton;
private final @NonNull ControlButton mPositiveButton;
@@ -52,10 +56,10 @@
mPositiveButton = positiveButton;
}
- DiscreteToggleTemplate(Parcel in) {
- super(in);
- this.mNegativeButton = ControlButton.CREATOR.createFromParcel(in);
- this.mPositiveButton = ControlButton.CREATOR.createFromParcel(in);
+ DiscreteToggleTemplate(Bundle b) {
+ super(b);
+ mNegativeButton = b.getParcelable(KEY_NEGATIVE_BUTTON);
+ mPositiveButton = b.getParcelable(KEY_POSITIVE_BUTTON);
}
/**
@@ -89,17 +93,18 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- mNegativeButton.writeToParcel(dest, flags);
- mPositiveButton.writeToParcel(dest, flags);
+ protected Bundle getDataBundle() {
+ Bundle b = super.getDataBundle();
+ b.putObject(KEY_NEGATIVE_BUTTON, mNegativeButton);
+ b.putObject(KEY_POSITIVE_BUTTON, mPositiveButton);
+ return b;
}
public static final Creator<DiscreteToggleTemplate> CREATOR =
new Creator<DiscreteToggleTemplate>() {
@Override
public DiscreteToggleTemplate createFromParcel(Parcel source) {
- return new DiscreteToggleTemplate(source);
+ return new DiscreteToggleTemplate(source.readBundle());
}
@Override
diff --git a/core/java/android/service/controls/FloatAction.java b/core/java/android/service/controls/FloatAction.java
index fe6db10..229435f 100644
--- a/core/java/android/service/controls/FloatAction.java
+++ b/core/java/android/service/controls/FloatAction.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
import android.os.Parcel;
/**
@@ -26,6 +27,8 @@
*/
public final class FloatAction extends ControlAction {
+ private static final String KEY_NEW_VALUE = "key_new_value";
+
private final float mNewValue;
/**
@@ -50,9 +53,9 @@
mNewValue = newValue;
}
- public FloatAction(Parcel in) {
- super(in);
- mNewValue = in.readFloat();
+ public FloatAction(Bundle b) {
+ super(b);
+ mNewValue = b.getFloat(KEY_NEW_VALUE);
}
/**
@@ -71,15 +74,16 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeFloat(mNewValue);
+ protected Bundle getDataBundle() {
+ Bundle b = super.getDataBundle();
+ b.putFloat(KEY_NEW_VALUE, mNewValue);
+ return b;
}
public static final @NonNull Creator<FloatAction> CREATOR = new Creator<FloatAction>() {
@Override
public FloatAction createFromParcel(Parcel source) {
- return new FloatAction(source);
+ return new FloatAction(source.readBundle());
}
@Override
diff --git a/core/java/android/service/controls/RangeTemplate.java b/core/java/android/service/controls/RangeTemplate.java
index 70bf2dd..f0bce30 100644
--- a/core/java/android/service/controls/RangeTemplate.java
+++ b/core/java/android/service/controls/RangeTemplate.java
@@ -18,10 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
import android.os.Parcel;
-import com.android.internal.util.Preconditions;
-
import java.security.InvalidParameterException;
/**
@@ -32,6 +31,12 @@
*/
public final class RangeTemplate extends ControlTemplate {
+ private static final String KEY_MIN_VALUE = "key_min_value";
+ private static final String KEY_MAX_VALUE = "key_max_value";
+ private static final String KEY_CURRENT_VALUE = "key_current_value";
+ private static final String KEY_STEP_VALUE = "key_step_value";
+ private static final String KEY_FORMAT_STRING = "key_format_string";
+
private final float mMinValue;
private final float mMaxValue;
private final float mCurrentValue;
@@ -67,7 +72,6 @@
float stepValue,
@Nullable CharSequence formatString) {
super(templateId);
- Preconditions.checkNotNull(formatString);
mMinValue = minValue;
mMaxValue = maxValue;
mCurrentValue = currentValue;
@@ -87,13 +91,13 @@
* @see RangeTemplate#RangeTemplate(String, float, float, float, float, CharSequence)
* @hide
*/
- RangeTemplate(Parcel in) {
- super(in);
- mMinValue = in.readFloat();
- mMaxValue = in.readFloat();
- mCurrentValue = in.readFloat();
- mStepValue = in.readFloat();
- mFormatString = in.readCharSequence();
+ RangeTemplate(Bundle b) {
+ super(b);
+ mMinValue = b.getFloat(KEY_MIN_VALUE);
+ mMaxValue = b.getFloat(KEY_MAX_VALUE);
+ mCurrentValue = b.getFloat(KEY_CURRENT_VALUE);
+ mStepValue = b.getFloat(KEY_STEP_VALUE);
+ mFormatString = b.getCharSequence(KEY_FORMAT_STRING, "%.1f");
validate();
}
@@ -144,13 +148,14 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeFloat(mMinValue);
- dest.writeFloat(mMaxValue);
- dest.writeFloat(mCurrentValue);
- dest.writeFloat(mStepValue);
- dest.writeCharSequence(mFormatString);
+ protected Bundle getDataBundle() {
+ Bundle b = super.getDataBundle();
+ b.putFloat(KEY_MIN_VALUE, mMinValue);
+ b.putFloat(KEY_MAX_VALUE, mMaxValue);
+ b.putFloat(KEY_CURRENT_VALUE, mCurrentValue);
+ b.putFloat(KEY_STEP_VALUE, mStepValue);
+ b.putCharSequence(KEY_FORMAT_STRING, mFormatString);
+ return b;
}
/**
@@ -179,7 +184,7 @@
public static final Creator<RangeTemplate> CREATOR = new Creator<RangeTemplate>() {
@Override
public RangeTemplate createFromParcel(Parcel source) {
- return new RangeTemplate(source);
+ return new RangeTemplate(source.readBundle());
}
@Override
diff --git a/core/java/android/service/controls/ThumbnailTemplate.java b/core/java/android/service/controls/ThumbnailTemplate.java
index 796d2de..6e729c01 100644
--- a/core/java/android/service/controls/ThumbnailTemplate.java
+++ b/core/java/android/service/controls/ThumbnailTemplate.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.graphics.drawable.Icon;
+import android.os.Bundle;
import android.os.Parcel;
import com.android.internal.util.Preconditions;
@@ -28,6 +29,9 @@
*/
public final class ThumbnailTemplate extends ControlTemplate {
+ private static final String KEY_ICON = "key_icon";
+ private static final String KEY_CONTENT_DESCRIPTION = "key_content_description";
+
private final @NonNull Icon mThumbnail;
private final @NonNull CharSequence mContentDescription;
@@ -45,10 +49,10 @@
mContentDescription = contentDescription;
}
- ThumbnailTemplate(Parcel in) {
- super(in);
- mThumbnail = Icon.CREATOR.createFromParcel(in);
- mContentDescription = in.readCharSequence();
+ ThumbnailTemplate(Bundle b) {
+ super(b);
+ mThumbnail = b.getParcelable(KEY_ICON);
+ mContentDescription = b.getCharSequence(KEY_CONTENT_DESCRIPTION, "");
}
/**
@@ -74,17 +78,19 @@
public int getTemplateType() {
return TYPE_THUMBNAIL;
}
+
@Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- mThumbnail.writeToParcel(dest, flags);
- dest.writeCharSequence(mContentDescription);
+ protected Bundle getDataBundle() {
+ Bundle b = super.getDataBundle();
+ b.putObject(KEY_ICON, mThumbnail);
+ b.putObject(KEY_CONTENT_DESCRIPTION, mContentDescription);
+ return b;
}
public static final Creator<ThumbnailTemplate> CREATOR = new Creator<ThumbnailTemplate>() {
@Override
public ThumbnailTemplate createFromParcel(Parcel source) {
- return new ThumbnailTemplate(source);
+ return new ThumbnailTemplate(source.readBundle());
}
@Override
diff --git a/core/java/android/service/controls/ToggleTemplate.java b/core/java/android/service/controls/ToggleTemplate.java
index 3766bd1..4c4fd5e 100644
--- a/core/java/android/service/controls/ToggleTemplate.java
+++ b/core/java/android/service/controls/ToggleTemplate.java
@@ -17,6 +17,7 @@
package android.service.controls;
import android.annotation.NonNull;
+import android.os.Bundle;
import android.os.Parcel;
import com.android.internal.util.Preconditions;
@@ -24,7 +25,7 @@
/**
* A template for a {@link Control} with a single button that can be toggled between two states.
*
- * The states for the toggle correspond to the states in {@link ControlButton#isActive()}.
+ * The states for the toggle correspond to the states in {@link ControlButton#isChecked()}.
* An action on this template will originate a {@link BooleanAction} to change that state.
*
* @see BooleanAction
@@ -32,6 +33,7 @@
*/
public final class ToggleTemplate extends ControlTemplate {
+ private static final String KEY_BUTTON = "key_button";
private final @NonNull ControlButton mButton;
/**
@@ -44,9 +46,9 @@
mButton = button;
}
- ToggleTemplate(Parcel in) {
- super(in);
- mButton = ControlButton.CREATOR.createFromParcel(in);
+ ToggleTemplate(Bundle b) {
+ super(b);
+ mButton = b.getParcelable(KEY_BUTTON);
}
/**
@@ -66,15 +68,16 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- mButton.writeToParcel(dest, flags);
+ protected Bundle getDataBundle() {
+ Bundle b = super.getDataBundle();
+ b.putObject(KEY_BUTTON, mButton);
+ return b;
}
public static final Creator<ToggleTemplate> CREATOR = new Creator<ToggleTemplate>() {
@Override
public ToggleTemplate createFromParcel(Parcel source) {
- return new ToggleTemplate(source);
+ return new ToggleTemplate(source.readBundle());
}
@Override
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
new file mode 100644
index 0000000..75f252e
--- /dev/null
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -0,0 +1,245 @@
+/*
+ * 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.dataloader;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.IDataLoader;
+import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.InstallationFile;
+import android.content.pm.NamedParcelFileDescriptor;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * The base class for implementing data loader service to control data loaders. Expecting
+ * Incremental Service to bind to a children class of this.
+ *
+ * WARNING: This is a system API to aid internal development.
+ * Use at your own risk. It will change or be removed without warning.
+ *
+ * TODO(b/136132412): update with latest API design
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class DataLoaderService extends Service {
+ private static final String TAG = "DataLoaderService";
+ private final DataLoaderBinderService mBinder = new DataLoaderBinderService();
+
+ /**
+ * Managed DataLoader interface. Each instance corresponds to a single installation session.
+ * @hide
+ */
+ public interface DataLoader {
+ /**
+ * A virtual constructor.
+ *
+ * @param dataLoaderParams parameters set in the installation session
+ * @param connector FS API wrapper
+ * @return True if initialization of a Data Loader was successful. False will be reported to
+ * PackageManager and fail the installation
+ */
+ boolean onCreate(@NonNull DataLoaderParams dataLoaderParams,
+ @NonNull FileSystemConnector connector);
+
+ /**
+ * Prepare installation image. After this method succeeds installer will validate the files
+ * and continue installation.
+ *
+ * @param addedFiles list of files created in this installation session.
+ * @param removedFiles list of files removed in this installation session.
+ * @return false if unable to create and populate all addedFiles.
+ */
+ boolean onPrepareImage(Collection<InstallationFile> addedFiles,
+ Collection<String> removedFiles);
+ }
+
+ /**
+ * DataLoader factory method.
+ *
+ * @return An instance of a DataLoader.
+ * @hide
+ */
+ public @Nullable DataLoader onCreateDataLoader() {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public final @NonNull IBinder onBind(@NonNull Intent intent) {
+ return (IBinder) mBinder;
+ }
+
+ private class DataLoaderBinderService extends IDataLoader.Stub {
+ private int mId;
+
+ @Override
+ public void create(int id, @NonNull Bundle options,
+ @NonNull IDataLoaderStatusListener listener)
+ throws IllegalArgumentException, RuntimeException {
+ mId = id;
+ final DataLoaderParamsParcel params = options.getParcelable("params");
+ if (params == null) {
+ throw new IllegalArgumentException("Must specify data loader params");
+ }
+ final FileSystemControlParcel control = options.getParcelable("control");
+ if (control == null) {
+ throw new IllegalArgumentException("Must specify control parcel");
+ }
+ try {
+ if (!nativeCreateDataLoader(id, control, params, listener)) {
+ Slog.e(TAG, "Failed to create native loader for " + mId);
+ }
+ } catch (Exception ex) {
+ Slog.e(TAG, "Failed to create native loader for " + mId, ex);
+ destroy();
+ throw new RuntimeException(ex);
+ } finally {
+ // Closing FDs.
+ if (control.incremental != null) {
+ if (control.incremental.cmd != null) {
+ try {
+ control.incremental.cmd.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
+ }
+ }
+ if (control.incremental.log != null) {
+ try {
+ control.incremental.log.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
+ }
+ }
+ }
+ if (params.dynamicArgs != null) {
+ NamedParcelFileDescriptor[] fds = params.dynamicArgs;
+ for (NamedParcelFileDescriptor nfd : fds) {
+ try {
+ nfd.fd.close();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close DynamicArgs parcel file descriptor " + e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void start() {
+ if (!nativeStartDataLoader(mId)) {
+ Slog.e(TAG, "Failed to start loader: " + mId);
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (!nativeStopDataLoader(mId)) {
+ Slog.w(TAG, "Failed to stop loader: " + mId);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ if (!nativeDestroyDataLoader(mId)) {
+ Slog.w(TAG, "Failed to destroy loader: " + mId);
+ }
+ }
+
+ @Override
+ public void prepareImage(List<InstallationFile> addedFiles, List<String> removedFiles) {
+ if (!nativePrepareImage(mId, addedFiles, removedFiles)) {
+ Slog.w(TAG, "Failed to destroy loader: " + mId);
+ }
+ }
+ }
+
+ /**
+ * Used by the DataLoaderService implementations.
+ *
+ * @hide
+ */
+ public static final class FileSystemConnector {
+ /**
+ * Create a wrapper for a native instance.
+ *
+ * @hide
+ */
+ FileSystemConnector(long nativeInstance) {
+ mNativeInstance = nativeInstance;
+ }
+
+ /**
+ * Write data to an installation file from an arbitrary FD.
+ *
+ * @param name name of file previously added to the installation session.
+ * @param offsetBytes offset into the file to begin writing at, or 0 to start at the
+ * beginning of the file.
+ * @param lengthBytes total size of the file being written, used to preallocate the
+ * underlying disk space, or -1 if unknown. The system may clear various
+ * caches as needed to allocate this space.
+ * @param incomingFd FD to read bytes from.
+ * @throws IOException if trouble opening the file for writing, such as lack of disk space
+ * or unavailable media.
+ */
+ public void writeData(String name, long offsetBytes, long lengthBytes,
+ ParcelFileDescriptor incomingFd) throws IOException {
+ try {
+ nativeWriteData(mNativeInstance, name, offsetBytes, lengthBytes, incomingFd);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
+ }
+ }
+
+ private final long mNativeInstance;
+ }
+
+ /* Native methods */
+ private native boolean nativeCreateDataLoader(int storageId,
+ @NonNull FileSystemControlParcel control,
+ @NonNull DataLoaderParamsParcel params,
+ IDataLoaderStatusListener listener);
+
+ private native boolean nativeStartDataLoader(int storageId);
+
+ private native boolean nativeStopDataLoader(int storageId);
+
+ private native boolean nativeDestroyDataLoader(int storageId);
+
+ private native boolean nativePrepareImage(int storageId,
+ Collection<InstallationFile> addedFiles, Collection<String> removedFiles);
+
+ private static native void nativeWriteData(long nativeInstance, String name, long offsetBytes,
+ long lengthBytes, ParcelFileDescriptor incomingFd);
+
+}
diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java
index efb8923..f2cedbc 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -36,12 +36,6 @@
public final class Sandman {
private static final String TAG = "Sandman";
- // The component name of a special dock app that merely launches a dream.
- // We don't want to launch this app when docked because it causes an unnecessary
- // activity transition. We just want to start the dream.
- private static final ComponentName SOMNAMBULATOR_COMPONENT =
- new ComponentName("com.android.systemui", "com.android.systemui.Somnambulator");
-
// The sandman is eternal. No one instantiates him.
private Sandman() {
@@ -52,8 +46,11 @@
* False if we should dream instead, if appropriate.
*/
public static boolean shouldStartDockApp(Context context, Intent intent) {
+ final ComponentName somnambulatorComponent = ComponentName.unflattenFromString(
+ context.getResources().getString(
+ com.android.internal.R.string.config_somnambulatorComponent));
ComponentName name = intent.resolveActivity(context.getPackageManager());
- return name != null && !name.equals(SOMNAMBULATOR_COMPONENT);
+ return name != null && !name.equals(somnambulatorComponent);
}
/**
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 8ab687f..c84fbc7 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -124,6 +124,13 @@
public static final String KEY_IMPORTANCE = "key_importance";
/**
+ * Data type: float, a ranking score from 0 (lowest) to 1 (highest).
+ * Used to rank notifications inside that fall under the same classification (i.e. alerting,
+ * silenced).
+ */
+ public static final String KEY_RANKING_SCORE = "key_ranking_score";
+
+ /**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 85f13d5..c04ac59 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1895,7 +1895,8 @@
&& ((mSmartActions == null ? 0 : mSmartActions.size())
== (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
&& Objects.equals(mSmartReplies, other.mSmartReplies)
- && Objects.equals(mCanBubble, other.mCanBubble);
+ && Objects.equals(mCanBubble, other.mCanBubble)
+ && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive);
}
}
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index dd2586c..d0675ed 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -481,9 +481,12 @@
* as true on their TileService Manifest declaration, and will do nothing otherwise.
*/
public static final void requestListeningState(Context context, ComponentName component) {
+ final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+ context.getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
Intent intent = new Intent(ACTION_REQUEST_LISTENING);
intent.putExtra(Intent.EXTRA_COMPONENT_NAME, component);
- intent.setPackage("com.android.systemui");
+ intent.setPackage(sysuiComponent.getPackageName());
context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
}
}
diff --git a/core/java/android/service/sms/FinancialSmsService.java b/core/java/android/service/sms/FinancialSmsService.java
deleted file mode 100644
index 5fb7249..0000000
--- a/core/java/android/service/sms/FinancialSmsService.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.service.sms;
-
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.app.Service;
-import android.content.Intent;
-import android.database.CursorWindow;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-
-/**
- * A service to support sms messages read for financial apps.
- *
- * {@hide}
- */
-@SystemApi
-public abstract class FinancialSmsService extends Service {
-
- private static final String TAG = "FinancialSmsService";
-
- /**
- * The {@link Intent} action that must be declared as handled by a service
- * in its manifest for the system to recognize it as a quota providing
- * service.
- */
- public static final String ACTION_FINANCIAL_SERVICE_INTENT =
- "android.service.sms.action.FINANCIAL_SERVICE_INTENT";
-
- /** {@hide} **/
- public static final String EXTRA_SMS_MSGS = "sms_messages";
-
- private FinancialSmsServiceWrapper mWrapper;
-
- private void getSmsMessages(RemoteCallback callback, Bundle params) {
- final Bundle data = new Bundle();
- CursorWindow smsMessages = onGetSmsMessages(params);
- if (smsMessages != null) {
- data.putParcelable(EXTRA_SMS_MSGS, smsMessages);
- }
- callback.sendResult(data);
- }
-
- private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
-
- /** @hide */
- public FinancialSmsService() {
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- mWrapper = new FinancialSmsServiceWrapper();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mWrapper;
- }
-
- /**
- * Get sms messages for financial apps.
- *
- * @param params parameters passed in by the calling app.
- * @return the {@code CursorWindow} with all sms messages for the app to read.
- *
- * {@hide}
- */
- @Nullable
- @SystemApi
- public abstract CursorWindow onGetSmsMessages(@NonNull Bundle params);
-
- private final class FinancialSmsServiceWrapper extends IFinancialSmsService.Stub {
- @Override
- public void getSmsMessages(RemoteCallback callback, Bundle params) throws RemoteException {
- mHandler.sendMessage(obtainMessage(
- FinancialSmsService::getSmsMessages,
- FinancialSmsService.this,
- callback, params));
- }
- }
-
-}
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 20dc2be..8dca69f 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -37,7 +37,6 @@
import android.os.Looper;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
import android.view.textclassifier.ConversationActions;
@@ -437,28 +436,16 @@
/**
* Returns the component name of the system default textclassifier service if it can be found
* on the system. Otherwise, returns null.
- * @hide
- */
- public static ComponentName getServiceComponentName(@NonNull Context context) {
- return getServiceComponentName(context, new TextClassificationConstants(
- () -> Settings.Global.getString(
- context.getContentResolver(),
- Settings.Global.TEXT_CLASSIFIER_CONSTANTS)));
- }
-
- /**
- * Returns the component name of the system default textclassifier service if it can be found
- * on the system. Otherwise, returns null.
- * @param context the text classification context
- * @param settings TextClassifier specific settings.
*
+ * @param context the text classification context
* @hide
*/
@Nullable
- public static ComponentName getServiceComponentName(@NonNull Context context,
- @NonNull TextClassificationConstants settings) {
+ public static ComponentName getServiceComponentName(@NonNull Context context) {
+ final TextClassificationConstants settings = TextClassificationManager.getSettings(context);
// get override TextClassifierService package name
- String packageName = settings.getTextClassifierServiceName();
+ String packageName = settings.getTextClassifierServicePackageOverride();
+
ComponentName serviceComponent = null;
final boolean isOverrideService = !TextUtils.isEmpty(packageName);
if (isOverrideService) {
@@ -468,7 +455,7 @@
if (serviceComponent != null) {
return serviceComponent;
}
- // If no TextClassifierService overrode or invalid override package name, read the first
+ // If no TextClassifierService override or invalid override package name, read the first
// package defined in the config
final String[] packages = context.getPackageManager().getSystemTextClassifierPackages();
if (packages.length == 0 || TextUtils.isEmpty(packages[0])) {
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index c9d37bf..52b7294 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1137,6 +1137,7 @@
mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
mCallbacks, this, mDispatcherState,
WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true);
+ mWindow.getWindow().setFitWindowInsetsTypes(0 /* types */);
mWindow.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d5c6766..e50d6c2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -811,6 +811,7 @@
// Add window
mLayout.type = mIWallpaperEngine.mWindowType;
mLayout.gravity = Gravity.START|Gravity.TOP;
+ mLayout.setFitWindowInsetsTypes(0 /* types */);
mLayout.setTitle(WallpaperService.this.getClass().getName());
mLayout.windowAnimations =
com.android.internal.R.style.Animation_Wallpaper;
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 44446ad0..172495f 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -1771,7 +1771,7 @@
*
* @param text The text that should be synthesized. No longer than
* {@link #getMaxSpeechInputLength()} characters.
- * @param params Parameters for the request. Can be null.
+ * @param params Parameters for the request.
* Engine specific parameters may be passed in but the parameter keys
* must be prefixed by the name of the engine they are intended for. For example
* the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the engine
@@ -1798,7 +1798,7 @@
*
* @param text The text that should be synthesized. No longer than
* {@link #getMaxSpeechInputLength()} characters.
- * @param params Parameters for the request. Can be null.
+ * @param params Parameters for the request. Cannot be null.
* Engine specific parameters may be passed in but the parameter keys
* must be prefixed by the name of the engine they are intended for. For example
* the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
@@ -1842,7 +1842,7 @@
*
* @param text The text that should be synthesized. No longer than
* {@link #getMaxSpeechInputLength()} characters.
- * @param params Parameters for the request. Can be null.
+ * @param params Parameters for the request. Cannot be null.
* Supported parameter names:
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
* Engine specific parameters may be passed in but the parameter keys
diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java
index 4474f3e..8446253 100644
--- a/core/java/android/telephony/CellBroadcastIntents.java
+++ b/core/java/android/telephony/CellBroadcastIntents.java
@@ -26,7 +26,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.util.Log;
/**
* A static helper class used to send Intents with prepopulated flags.
@@ -75,20 +74,20 @@
@Nullable String receiverAppOp, @Nullable BroadcastReceiver resultReceiver,
@Nullable Handler scheduler, int initialCode, @Nullable String initialData,
@Nullable Bundle initialExtras) {
- Log.d(LOG_TAG, "sendOrderedBroadcastForBackgroundReceivers intent=" + intent.getAction());
int status = context.checkCallingOrSelfPermission(
"android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS");
if (status == PackageManager.PERMISSION_DENIED) {
throw new SecurityException(
"Caller does not have permission to send broadcast for background receivers");
}
- intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ Intent backgroundIntent = new Intent(intent);
+ backgroundIntent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
if (user != null) {
- context.createContextAsUser(user, 0).sendOrderedBroadcast(intent, receiverPermission,
- receiverAppOp, resultReceiver, scheduler, initialCode, initialData,
- initialExtras);
+ context.createContextAsUser(user, 0).sendOrderedBroadcast(backgroundIntent,
+ receiverPermission, receiverAppOp, resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
} else {
- context.sendOrderedBroadcast(intent, receiverPermission,
+ context.sendOrderedBroadcast(backgroundIntent, receiverPermission,
receiverAppOp, resultReceiver, scheduler, initialCode, initialData,
initialExtras);
}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 716a522..7841f99 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -24,7 +24,6 @@
import android.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Build;
-import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -35,8 +34,8 @@
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
-import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IPhoneStateListener;
import dalvik.system.VMRuntime;
@@ -169,14 +168,6 @@
public static final int LISTEN_SIGNAL_STRENGTHS = 0x00000100;
/**
- * Listen for changes to OTASP mode.
- *
- * @see #onOtaspChanged
- * @hide
- */
- public static final int LISTEN_OTASP_CHANGED = 0x00000200;
-
- /**
* Listen for changes to observed cell info.
*
* @see #onCellInfoChanged
@@ -196,12 +187,13 @@
/**
* Listen for {@link PreciseDataConnectionState} on the data connection (cellular).
*
- * @see #onPreciseDataConnectionStateChanged
+ * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+ * or the calling app has carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * @hide
+ * @see #onPreciseDataConnectionStateChanged
*/
- @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
+ @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 0x00001000;
/**
@@ -326,10 +318,8 @@
* Listen for call disconnect causes which contains {@link DisconnectCause} and
* {@link PreciseDisconnectCause}.
*
- * @hide
*/
@RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
public static final int LISTEN_CALL_DISCONNECT_CAUSES = 0x02000000;
/**
@@ -349,10 +339,8 @@
* {@link android.telephony.ims.ImsReasonInfo}
*
* @see #onImsCallDisconnectCauseChanged(ImsReasonInfo)
- * @hide
*/
@RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 0x08000000;
/**
@@ -624,29 +612,6 @@
// default implementation empty
}
-
- /**
- * The Over The Air Service Provisioning (OTASP) has changed on the registered subscription.
- * Note, the registration subId comes from {@link TelephonyManager} object which registers
- * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
- * If this TelephonyManager object was created with
- * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subId. Otherwise, this callback applies to
- * {@link SubscriptionManager#getDefaultSubscriptionId()}.
- *
- * Requires the READ_PHONE_STATE permission.
- * @param otaspMode is integer <code>OTASP_UNKNOWN=1<code>
- * means the value is currently unknown and the system should wait until
- * <code>OTASP_NEEDED=2<code> or <code>OTASP_NOT_NEEDED=3<code> is received before
- * making the decision to perform OTASP or not.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public void onOtaspChanged(int otaspMode) {
- // default implementation empty
- }
-
/**
* Callback invoked when a observed cell info has changed or new cells have been added
* or removed on the registered subscription.
@@ -691,10 +656,8 @@
* @param disconnectCause {@link DisconnectCause}.
* @param preciseDisconnectCause {@link PreciseDisconnectCause}.
*
- * @hide
*/
@RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
public void onCallDisconnectCauseChanged(int disconnectCause, int preciseDisconnectCause) {
// default implementation empty
}
@@ -710,17 +673,16 @@
*
* @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed.
*
- * @hide
*/
@RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
public void onImsCallDisconnectCauseChanged(@NonNull ImsReasonInfo imsReasonInfo) {
// default implementation empty
}
/**
- * Callback invoked when data connection state changes with precise information
- * on the registered subscription.
+ * Callback providing update about the default/internet data connection on the registered
+ * subscription.
+ *
* Note, the registration subId comes from {@link TelephonyManager} object which registers
* PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
* If this TelephonyManager object was created with
@@ -728,12 +690,13 @@
* subId. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * @param dataConnectionState {@link PreciseDataConnectionState}
+ * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+ * or the calling app has carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}).
*
- * @hide
+ * @param dataConnectionState {@link PreciseDataConnectionState}
*/
- @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
- @SystemApi
+ @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
public void onPreciseDataConnectionStateChanged(
@NonNull PreciseDataConnectionState dataConnectionState) {
// default implementation empty
@@ -939,8 +902,7 @@
* subId. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * Requires
- * the READ_PRIVILEGED_PHONE_STATE permission.
+ * @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE}
* @param state the modem radio power state
* @hide
*/
@@ -1021,8 +983,11 @@
() -> mExecutor.execute(() -> psl.onCallForwardingIndicatorChanged(cfi)));
}
- public void onCellLocationChanged(Bundle bundle) {
- CellLocation location = CellLocation.newFromBundle(bundle);
+ public void onCellLocationChanged(CellIdentity cellIdentity) {
+ // There is no system/public API to create an CellIdentity in system server,
+ // so the server pass a null to indicate an empty initial location.
+ CellLocation location =
+ cellIdentity == null ? CellLocation.getEmpty() : cellIdentity.asCellLocation();
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
@@ -1042,11 +1007,21 @@
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> {
- psl.onDataConnectionStateChanged(state, networkType);
- psl.onDataConnectionStateChanged(state);
- }));
+ if (state == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> {
+ psl.onDataConnectionStateChanged(
+ TelephonyManager.DATA_CONNECTED, networkType);
+ psl.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED);
+ }));
+ } else {
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> {
+ psl.onDataConnectionStateChanged(state, networkType);
+ psl.onDataConnectionStateChanged(state);
+ }));
+ }
}
public void onDataActivity(int direction) {
@@ -1065,14 +1040,6 @@
() -> mExecutor.execute(() -> psl.onSignalStrengthsChanged(signalStrength)));
}
- public void onOtaspChanged(int otaspMode) {
- PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
- if (psl == null) return;
-
- Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> psl.onOtaspChanged(otaspMode)));
- }
-
public void onCellInfoChanged(List<CellInfo> cellInfo) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9d7b57b..1b2feda 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -21,17 +21,12 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
import android.os.Binder;
-import android.os.Bundle;
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;
-import android.telephony.Annotation.DataState;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.PreciseCallStates;
import android.telephony.Annotation.RadioPowerState;
@@ -357,27 +352,18 @@
* @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 state latest data connection state, e.g,
- * @param isDataConnectivityPossible indicates if data is allowed
- * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
- * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
- * @param linkProperties {@link LinkProperties} associated with this data connection.
- * @param networkCapabilities {@link NetworkCapabilities} associated with this data connection.
- * @param networkType associated with this data connection.
- * @param roaming {@code true} indicates in roaming, {@false} otherwise.
- * @see TelephonyManager#DATA_DISCONNECTED
- * @see TelephonyManager#isDataConnectivityPossible()
+ * @param apnType the APN type that triggered this update
+ * @param preciseState the PreciseDataConnectionState
*
+ * @see android.telephony.PreciseDataConnection
+ * @see TelephonyManager#DATA_DISCONNECTED
* @hide
*/
- public void notifyDataConnectionForSubscriber(int slotIndex, int subId, @DataState int state,
- boolean isDataConnectivityPossible,
- @ApnType String apn, String apnType, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
+ public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
+ String apnType, PreciseDataConnectionState preciseState) {
try {
- sRegistry.notifyDataConnectionForSubscriber(slotIndex, subId, state,
- isDataConnectivityPossible,
- apn, apnType, linkProperties, networkCapabilities, networkType, roaming);
+ sRegistry.notifyDataConnectionForSubscriber(
+ slotIndex, subId, apnType, preciseState);
} catch (RemoteException ex) {
// system process is dead
}
@@ -600,22 +586,6 @@
}
/**
- * Notify over the air sim provisioning(OTASP) mode changed on certain subscription.
- *
- * @param subId for which otasp mode changed.
- * @param otaspMode latest mode for OTASP e.g, OTASP needed.
- *
- * @hide
- */
- public void notifyOtaspChanged(int subId, int otaspMode) {
- try {
- sRegistry.notifyOtaspChanged(subId, otaspMode);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- /**
* Notify precise call state changed on certain subscription, including foreground, background
* and ringcall states.
*
@@ -662,29 +632,14 @@
}
/**
- * Notify data connection failed on certain subscription.
+ * Notify {@link android.telephony.CellLocation} changed.
*
- * @param subId for which data connection failed.
- * @param slotIndex for which data conenction faled. Can be derived from subId except when subId
- * is invalid.
- * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN. Note each data
- * connection can support multiple anyTypes.
+ * <p>To be compatible with {@link TelephonyRegistry}, use {@link CellIdentity} which is
+ * parcelable, and convert to CellLocation in client code.
*
* @hide
*/
- public void notifyDataConnectionFailed(int subId, int slotIndex, String apnType) {
- try {
- sRegistry.notifyDataConnectionFailedForSubscriber(slotIndex, subId, apnType);
- } catch (RemoteException ex) {
- // system process is dead
- }
- }
-
- /**
- * TODO change from bundle to CellLocation?
- * @hide
- */
- public void notifyCellLocation(int subId, Bundle cellLocation) {
+ public void notifyCellLocation(int subId, CellIdentity cellLocation) {
try {
sRegistry.notifyCellLocationForSubscriber(subId, cellLocation);
} catch (RemoteException ex) {
diff --git a/core/java/android/text/AlteredCharSequence.java b/core/java/android/text/AlteredCharSequence.java
index 4cc71fd..971a47d 100644
--- a/core/java/android/text/AlteredCharSequence.java
+++ b/core/java/android/text/AlteredCharSequence.java
@@ -16,12 +16,14 @@
package android.text;
-// XXX should this really be in the public API at all?
/**
* An AlteredCharSequence is a CharSequence that is largely mirrored from
* another CharSequence, except that a specified range of characters are
* mirrored from a different char array instead.
+ *
+ * @deprecated The functionality this class offers is easily implemented outside the framework.
*/
+@Deprecated
public class AlteredCharSequence
implements CharSequence, GetChars
{
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index b00a938..0c27923 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -59,8 +59,12 @@
public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
/**
- * This constant is actually the length of 364 days, not of a year!
+ * @deprecated Not all years have the same number of days, and this constant is actually the
+ * length of 364 days. Please use other date/time constructs such as
+ * {@link java.util.concurrent.TimeUnit}, {@link java.util.Calendar} or
+ * {@link java.time.Duration} instead.
*/
+ @Deprecated
public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52;
// The following FORMAT_* symbols are used for specifying the format of
diff --git a/core/java/android/util/CloseGuard.java b/core/java/android/util/CloseGuard.java
new file mode 100644
index 0000000..6ac7696
--- /dev/null
+++ b/core/java/android/util/CloseGuard.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+
+/**
+ * CloseGuard is a mechanism for flagging implicit finalizer cleanup of
+ * resources that should have been cleaned up by explicit close
+ * methods (aka "explicit termination methods" in Effective Java).
+ * <p>
+ * A simple example: <pre> {@code
+ * class Foo {
+ *
+ * private final CloseGuard guard = CloseGuard.get();
+ *
+ * ...
+ *
+ * public Foo() {
+ * ...;
+ * guard.open("cleanup");
+ * }
+ *
+ * public void cleanup() {
+ * guard.close();
+ * ...;
+ * if (Build.VERSION.SDK_INT >= 28) {
+ * Reference.reachabilityFence(this);
+ * }
+ * // For full correctness in the absence of a close() call, other methods may also need
+ * // reachabilityFence() calls.
+ * }
+ *
+ * protected void finalize() throws Throwable {
+ * try {
+ * // Note that guard could be null if the constructor threw.
+ * if (guard != null) {
+ * guard.warnIfOpen();
+ * }
+ * cleanup();
+ * } finally {
+ * super.finalize();
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * In usage where the resource to be explicitly cleaned up is
+ * allocated after object construction, CloseGuard protection can
+ * be deferred. For example: <pre> {@code
+ * class Bar {
+ *
+ * private final CloseGuard guard = CloseGuard.get();
+ *
+ * ...
+ *
+ * public Bar() {
+ * ...;
+ * }
+ *
+ * public void connect() {
+ * ...;
+ * guard.open("cleanup");
+ * }
+ *
+ * public void cleanup() {
+ * guard.close();
+ * ...;
+ * if (Build.VERSION.SDK_INT >= 28) {
+ * Reference.reachabilityFence(this);
+ * }
+ * // For full correctness in the absence of a close() call, other methods may also need
+ * // reachabilityFence() calls.
+ * }
+ *
+ * protected void finalize() throws Throwable {
+ * try {
+ * // Note that guard could be null if the constructor threw.
+ * if (guard != null) {
+ * guard.warnIfOpen();
+ * }
+ * cleanup();
+ * } finally {
+ * super.finalize();
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * When used in a constructor, calls to {@code open} should occur at
+ * the end of the constructor since an exception that would cause
+ * abrupt termination of the constructor will mean that the user will
+ * not have a reference to the object to cleanup explicitly. When used
+ * in a method, the call to {@code open} should occur just after
+ * resource acquisition.
+ */
+public final class CloseGuard {
+ private final dalvik.system.CloseGuard mImpl;
+
+ /**
+ * Constructs a new CloseGuard instance.
+ * {@link #open(String)} can be used to set up the instance to warn on failure to close.
+ */
+ public CloseGuard() {
+ mImpl = dalvik.system.CloseGuard.get();
+ }
+
+ /**
+ * Initializes the instance with a warning that the caller should have explicitly called the
+ * {@code closeMethodName} method instead of relying on finalization.
+ *
+ * @param closeMethodName non-null name of explicit termination method. Printed by warnIfOpen.
+ * @throws NullPointerException if closeMethodName is null.
+ */
+ public void open(@NonNull String closeMethodName) {
+ mImpl.open(closeMethodName);
+ }
+
+ /** Marks this CloseGuard instance as closed to avoid warnings on finalization. */
+ public void close() {
+ mImpl.close();
+ }
+
+ /**
+ * Logs a warning if the caller did not properly cleanup by calling an explicit close method
+ * before finalization.
+ */
+ public void warnIfOpen() {
+ mImpl.warnIfOpen();
+ }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 236e5ae..1b2db36 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -59,6 +59,7 @@
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");
}
/**
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index 3a1df3e..6a6bccf 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -17,9 +17,11 @@
package android.util;
import android.annotation.UnsupportedAppUsage;
+import android.os.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayDeque;
import java.util.Deque;
@@ -33,10 +35,22 @@
private final Deque<String> mLog;
private final int mMaxLines;
+ /**
+ * {@code true} to use log timestamps expressed in local date/time, {@code false} to use log
+ * timestamped expressed with the elapsed realtime clock and UTC system clock. {@code false} is
+ * useful when logging behavior that modifies device time zone or system clock.
+ */
+ private final boolean mUseLocalTimestamps;
+
@UnsupportedAppUsage
public LocalLog(int maxLines) {
+ this(maxLines, true /* useLocalTimestamps */);
+ }
+
+ public LocalLog(int maxLines, boolean useLocalTimestamps) {
mMaxLines = Math.max(0, maxLines);
mLog = new ArrayDeque<>(mMaxLines);
+ mUseLocalTimestamps = useLocalTimestamps;
}
@UnsupportedAppUsage
@@ -44,7 +58,14 @@
if (mMaxLines <= 0) {
return;
}
- append(String.format("%s - %s", LocalDateTime.now(), msg));
+ final String logLine;
+ if (mUseLocalTimestamps) {
+ logLine = String.format("%s - %s", LocalDateTime.now(), msg);
+ } else {
+ logLine = String.format(
+ "%s / %s - %s", SystemClock.elapsedRealtime(), Instant.now(), msg);
+ }
+ append(logLine);
}
private synchronized void append(String logLine) {
diff --git a/core/java/android/util/LongArrayQueue.java b/core/java/android/util/LongArrayQueue.java
index d5f0484..5c701db 100644
--- a/core/java/android/util/LongArrayQueue.java
+++ b/core/java/android/util/LongArrayQueue.java
@@ -162,4 +162,24 @@
final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
return mValues[index];
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ if (mSize <= 0) {
+ return "{}";
+ }
+
+ final StringBuilder buffer = new StringBuilder(mSize * 64);
+ buffer.append('{');
+ buffer.append(get(0));
+ for (int i = 1; i < mSize; i++) {
+ buffer.append(", ");
+ buffer.append(get(i));
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
}
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index c1873d7..9f0f246 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -27,8 +27,8 @@
}
/**
- * Add a value at index n.
- * @return FALSE when the value already existed at the given index, TRUE otherwise.
+ * Add a value for key n.
+ * @return FALSE when the value already existed for the given key, TRUE otherwise.
*/
public boolean add(int n, T value) {
ArraySet<T> set = mData.get(n);
@@ -51,7 +51,7 @@
}
/**
- * @return whether a value exists at index n.
+ * @return whether the value exists for the key n.
*/
public boolean contains(int n, T value) {
final ArraySet<T> set = mData.get(n);
@@ -62,15 +62,15 @@
}
/**
- * @return the set of items at index n
+ * @return the set of items of key n
*/
public ArraySet<T> get(int n) {
return mData.get(n);
}
/**
- * Remove a value from index n.
- * @return TRUE when the value existed at the given index and removed, FALSE otherwise.
+ * Remove a value for key n.
+ * @return TRUE when the value existed for the given key and removed, FALSE otherwise.
*/
public boolean remove(int n, T value) {
final ArraySet<T> set = mData.get(n);
@@ -85,7 +85,7 @@
}
/**
- * Remove all values from index n.
+ * Remove all values for key n.
*/
public void remove(int n) {
mData.remove(n);
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
index 8cb5b05..952d7cb 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -24,7 +24,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
-import android.os.IStatsManager;
+import android.os.IStatsd;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -36,7 +36,7 @@
private static final String TAG = "StatsLog";
private static final boolean DEBUG = false;
- private static IStatsManager sService;
+ private static IStatsd sService;
private static Object sLogLock = new Object();
@@ -52,7 +52,7 @@
public static boolean logStart(int label) {
synchronized (sLogLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
Slog.d(TAG, "Failed to find statsd when logging start");
@@ -81,7 +81,7 @@
public static boolean logStop(int label) {
synchronized (sLogLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
Slog.d(TAG, "Failed to find statsd when logging stop");
@@ -109,7 +109,7 @@
public static boolean logEvent(int label) {
synchronized (sLogLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
Slog.d(TAG, "Failed to find statsd when logging event");
@@ -151,7 +151,7 @@
@NonNull long[] experimentIds) {
synchronized (sLogLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
Slog.d(TAG, "Failed to find statsd when logging event");
@@ -191,7 +191,7 @@
long packageVersionCode, int rollbackReason, String failingPackageName) {
synchronized (sLogLock) {
try {
- IStatsManager service = getIStatsManagerLocked();
+ IStatsd service = getIStatsdLocked();
if (service == null) {
if (DEBUG) {
Slog.d(TAG, "Failed to find statsd when logging event");
@@ -215,11 +215,11 @@
}
- private static IStatsManager getIStatsManagerLocked() throws RemoteException {
+ private static IStatsd getIStatsdLocked() throws RemoteException {
if (sService != null) {
return sService;
}
- sService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+ sService = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
return sService;
}
@@ -248,12 +248,15 @@
/**
* Write an event to stats log using the raw format encapsulated in StatsEvent.
+ * After writing to stats log, release() is called on the StatsEvent object.
+ * No further action should be taken on the StatsEvent object following this call.
*
* @param statsEvent The StatsEvent object containing the encoded buffer of data to write.
* @hide
*/
public static void write(@NonNull final StatsEvent statsEvent) {
writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
+ statsEvent.release();
}
private static void enforceDumpCallingPermission(Context context) {
diff --git a/core/java/android/util/apk/VerityBuilder.java b/core/java/android/util/apk/VerityBuilder.java
index 443bbd8..e81e3f7 100644
--- a/core/java/android/util/apk/VerityBuilder.java
+++ b/core/java/android/util/apk/VerityBuilder.java
@@ -70,22 +70,6 @@
/**
* Generates the 4k, SHA-256 based Merkle tree for the given APK and stores in the {@link
- * ByteBuffer} created by the {@link ByteBufferFactory}. The output is suitable to be used as
- * the on-disk format for fs-verity to use.
- *
- * @return VerityResult containing a buffer with the generated Merkle tree stored at the
- * front, the tree size, and the calculated root hash.
- */
- @NonNull
- public static VerityResult generateFsVerityTree(@NonNull RandomAccessFile apk,
- @NonNull ByteBufferFactory bufferFactory)
- throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
- return generateVerityTreeInternal(apk, bufferFactory, null /* signatureInfo */,
- false /* skipSigningBlock */);
- }
-
- /**
- * Generates the 4k, SHA-256 based Merkle tree for the given APK and stores in the {@link
* ByteBuffer} created by the {@link ByteBufferFactory}. The Merkle tree does not cover Signing
* Block specificed in {@code signatureInfo}. The output is suitable to be used as the on-disk
* format for fs-verity to use (with elide and patch extensions).
@@ -97,21 +81,16 @@
public static VerityResult generateApkVerityTree(@NonNull RandomAccessFile apk,
@Nullable SignatureInfo signatureInfo, @NonNull ByteBufferFactory bufferFactory)
throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
- return generateVerityTreeInternal(apk, bufferFactory, signatureInfo,
- true /* skipSigningBlock */);
+ return generateVerityTreeInternal(apk, bufferFactory, signatureInfo);
}
@NonNull
private static VerityResult generateVerityTreeInternal(@NonNull RandomAccessFile apk,
- @NonNull ByteBufferFactory bufferFactory, @Nullable SignatureInfo signatureInfo,
- boolean skipSigningBlock)
+ @NonNull ByteBufferFactory bufferFactory, @Nullable SignatureInfo signatureInfo)
throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
- long dataSize = apk.length();
- if (skipSigningBlock) {
- long signingBlockSize =
- signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
- dataSize -= signingBlockSize;
- }
+ long signingBlockSize =
+ signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+ long dataSize = apk.length() - signingBlockSize;
int[] levelOffset = calculateVerityLevelOffset(dataSize);
int merkleTreeSize = levelOffset[levelOffset.length - 1];
@@ -120,10 +99,8 @@
+ CHUNK_SIZE_BYTES); // maximum size of apk-verity metadata
output.order(ByteOrder.LITTLE_ENDIAN);
ByteBuffer tree = slice(output, 0, merkleTreeSize);
- // Only use default salt in legacy case.
- byte[] salt = skipSigningBlock ? DEFAULT_SALT : null;
- byte[] apkRootHash = generateVerityTreeInternal(apk, signatureInfo, salt, levelOffset,
- tree, skipSigningBlock);
+ byte[] apkRootHash = generateVerityTreeInternal(apk, signatureInfo, DEFAULT_SALT,
+ levelOffset, tree);
return new VerityResult(output, merkleTreeSize, apkRootHash);
}
@@ -173,8 +150,7 @@
throws IOException, SignatureNotFoundException, SecurityException, DigestException,
NoSuchAlgorithmException {
try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
- VerityResult result = generateVerityTreeInternal(apk, bufferFactory, signatureInfo,
- true /* skipSigningBlock */);
+ VerityResult result = generateVerityTreeInternal(apk, bufferFactory, signatureInfo);
ByteBuffer footer = slice(result.verityData, result.merkleTreeSize,
result.verityData.limit());
generateApkVerityFooter(apk, signatureInfo, footer);
@@ -351,17 +327,12 @@
@NonNull
private static byte[] generateVerityTreeInternal(@NonNull RandomAccessFile apk,
@Nullable SignatureInfo signatureInfo, @Nullable byte[] salt,
- @NonNull int[] levelOffset, @NonNull ByteBuffer output, boolean skipSigningBlock)
+ @NonNull int[] levelOffset, @NonNull ByteBuffer output)
throws IOException, NoSuchAlgorithmException, DigestException {
// 1. Digest the apk to generate the leaf level hashes.
- if (skipSigningBlock) {
- assertSigningBlockAlignedAndHasFullPages(signatureInfo);
- generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output,
- levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
- } else {
- generateFsVerityDigestAtLeafLevel(apk, slice(output,
- levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
- }
+ assertSigningBlockAlignedAndHasFullPages(signatureInfo);
+ generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output,
+ levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
// 2. Digest the lower level hashes bottom up.
for (int level = levelOffset.length - 3; level >= 0; level--) {
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 6efcfbf..7b24ba9 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -16,7 +16,8 @@
package android.util.proto;
-import android.annotation.TestApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Log;
import java.io.FileDescriptor;
@@ -28,19 +29,23 @@
/**
* Class to write to a protobuf stream.
*
- * Each write method takes an ID code from the protoc generated classes
- * and the value to write. To make a nested object, call #start
- * and then #end when you are done.
+ * <p>
+ * This API is not as convenient or type safe as the standard protobuf
+ * classes. If possible, the best recommended library is to use protobuf lite.
+ * However, in environements (such as the Android platform itself), a
+ * more memory efficient version is necessary.
*
- * The ID codes have type information embedded into them, so if you call
- * the incorrect function you will get an IllegalArgumentException.
+ * <p>Each write method takes an ID code from the protoc generated classes
+ * and the value to write. To make a nested object, call {@link #start(long)}
+ * and then {@link #end(long)} when you are done.
*
- * To retrieve the encoded protobuf stream, call getBytes().
+ * <p>The ID codes have type information embedded into them, so if you call
+ * the incorrect function you will get an {@link IllegalArgumentException}.
*
- * TODO: Add a constructor that takes an OutputStream and write to that
+ * <p>To retrieve the encoded protobuf stream, call {@link #getBytes()}.
+ *
* stream as the top-level objects are finished.
*
- * @hide
*/
/* IMPLEMENTATION NOTES
@@ -99,7 +104,6 @@
* correctly matched pairs of #start and #end calls, and issue
* errors if they are not matched.
*/
-@TestApi
public final class ProtoOutputStream extends ProtoStream {
/**
* @hide
@@ -124,7 +128,9 @@
/**
* An ID given to objects and returned in the token from startObject
* and stored in the buffer until endObject is called, where the two
- * are checked. Starts at -1 and becomes more negative, so the values
+ * are checked.
+ *
+ * <p>Starts at -1 and becomes more negative, so the values
* aren't likely to alias with the size it will be overwritten with,
* which tend to be small, and we will be more likely to catch when
* the caller of endObject uses a stale token that they didn't intend
@@ -133,8 +139,9 @@
private int mNextObjectId = -1;
/**
- * The object token we are expecting in endObject. If another call to
- * startObject happens, this is written to that location, which gives
+ * The object token we are expecting in endObject.
+ *
+ * <p>If another call to startObject happens, this is written to that location, which gives
* us a stack, stored in the space for the as-yet unused size fields.
*/
private long mExpectedObjectToken;
@@ -151,39 +158,45 @@
private boolean mCompacted;
/**
- * Construct a ProtoOutputStream with the default chunk size.
+ * Construct a {@link ProtoOutputStream} with the default chunk size.
+ *
+ * <p>This is for an in-memory proto. The caller should use {@link #getBytes()} for the result.
*/
public ProtoOutputStream() {
this(0);
}
/**
- * Construct a ProtoOutputStream with the given chunk size.
+ * Construct a {@link ProtoOutputStream with the given chunk size.
+ *
+ * <p>This is for an in-memory proto. The caller should use {@link #getBytes()} for the result.
*/
public ProtoOutputStream(int chunkSize) {
mBuffer = new EncodedBuffer(chunkSize);
}
/**
- * Construct a ProtoOutputStream that sits on top of an OutputStream.
- * @more
- * The {@link #flush() flush()} method must be called when done writing
- * to flush any remanining data, althought data *may* be written at intermediate
+ * Construct a {@link ProtoOutputStream} that sits on top of an {@link OutputStream}.
+ *
+ * <p>The {@link #flush()} method must be called when done writing
+ * to flush any remaining data, although data *may* be written at intermediate
* points within the writing as well.
*/
- public ProtoOutputStream(OutputStream stream) {
+ public ProtoOutputStream(@NonNull OutputStream stream) {
this();
mStream = stream;
}
/**
- * Construct a ProtoOutputStream that sits on top of a FileDescriptor.
- * @more
- * The {@link #flush() flush()} method must be called when done writing
- * to flush any remanining data, althought data *may* be written at intermediate
+ * Construct a {@link ProtoOutputStream} that sits on top of a {@link FileDescriptor}.
+ *
+ * <p>The {@link #flush()} method must be called when done writing
+ * to flush any remaining data, although data *may* be written at intermediate
* points within the writing as well.
+ *
+ * @hide
*/
- public ProtoOutputStream(FileDescriptor fd) {
+ public ProtoOutputStream(@NonNull FileDescriptor fd) {
this(new FileOutputStream(fd));
}
@@ -202,7 +215,7 @@
/**
* Write a value for the given fieldId.
*
- * Will automatically convert for the following field types, and
+ * <p>Will automatically convert for the following field types, and
* throw an exception for others: double, float, int32, int64, uint32, uint64,
* sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
*
@@ -337,7 +350,7 @@
/**
* Write a value for the given fieldId.
*
- * Will automatically convert for the following field types, and
+ * <p>Will automatically convert for the following field types, and
* throw an exception for others: double, float, int32, int64, uint32, uint64,
* sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
*
@@ -472,7 +485,7 @@
/**
* Write a value for the given fieldId.
*
- * Will automatically convert for the following field types, and
+ * <p>Will automatically convert for the following field types, and
* throw an exception for others: double, float, int32, int64, uint32, uint64,
* sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
*
@@ -607,7 +620,7 @@
/**
* Write a value for the given fieldId.
*
- * Will automatically convert for the following field types, and
+ * <p>Will automatically convert for the following field types, and
* throw an exception for others: double, float, int32, int64, uint32, uint64,
* sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
*
@@ -742,7 +755,7 @@
/**
* Write a boolean value for the given fieldId.
*
- * If the field is not a bool field, an exception will be thrown.
+ * <p>If the field is not a bool field, an {@link IllegalStateException} will be thrown.
*
* @param fieldId The field identifier constant from the generated class.
* @param val The value.
@@ -771,12 +784,12 @@
/**
* Write a string value for the given fieldId.
*
- * If the field is not a string field, an exception will be thrown.
+ * <p>If the field is not a string field, an exception will be thrown.
*
* @param fieldId The field identifier constant from the generated class.
* @param val The value.
*/
- public void write(long fieldId, String val) {
+ public void write(long fieldId, @Nullable String val) {
assertNotCompacted();
final int id = (int)fieldId;
@@ -800,12 +813,12 @@
/**
* Write a byte[] value for the given fieldId.
*
- * If the field is not a bytes or object field, an exception will be thrown.
+ * <p>If the field is not a bytes or object field, an exception will be thrown.
*
* @param fieldId The field identifier constant from the generated class.
* @param val The value.
*/
- public void write(long fieldId, byte[] val) {
+ public void write(long fieldId, @Nullable byte[] val) {
assertNotCompacted();
final int id = (int)fieldId;
@@ -836,6 +849,9 @@
/**
* Start a sub object.
+ *
+ * @param fieldId The field identifier constant from the generated class.
+ * @return The token to call {@link #end(long)} with.
*/
public long start(long fieldId) {
assertNotCompacted();
@@ -855,6 +871,8 @@
/**
* End the object started by start() that returned token.
+ *
+ * @param token The token returned from {@link #start(long)}
*/
public void end(long token) {
endObjectImpl(token, getRepeatedFromToken(token));
@@ -870,7 +888,8 @@
/**
* Write a single proto "double" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, double)} instead.
+ * @hide
*/
@Deprecated
public void writeDouble(long fieldId, double val) {
@@ -890,7 +909,8 @@
/**
* Write a single repeated proto "double" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, double)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedDouble(long fieldId, double val) {
@@ -908,10 +928,11 @@
/**
* Write a list of packed proto "double" type field values.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, double)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedDouble(long fieldId, double[] val) {
+ public void writePackedDouble(long fieldId, @Nullable double[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_DOUBLE);
@@ -934,7 +955,8 @@
/**
* Write a single proto "float" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, float)} instead.
+ * @hide
*/
@Deprecated
public void writeFloat(long fieldId, float val) {
@@ -954,7 +976,8 @@
/**
* Write a single repeated proto "float" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, float)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedFloat(long fieldId, float val) {
@@ -972,10 +995,11 @@
/**
* Write a list of packed proto "float" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, float)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedFloat(long fieldId, float[] val) {
+ public void writePackedFloat(long fieldId, @Nullable float[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FLOAT);
@@ -999,7 +1023,7 @@
/**
* Writes a java int as an usigned varint.
*
- * The unadorned int32 type in protobuf is unfortunate because it
+ * <p>The unadorned int32 type in protobuf is unfortunate because it
* is stored in memory as a signed value, but encodes as unsigned
* varints, which are formally always longs. So here, we encode
* negative values as 64 bits, which will get the sign-extension,
@@ -1017,11 +1041,12 @@
/**
* Write a single proto "int32" type field value.
*
- * Note that these are stored in memory as signed values and written as unsigned
+ * <p>Note that these are stored in memory as signed values and written as unsigned
* varints, which if negative, are 10 bytes long. If you know the data is likely
* to be negative, use "sint32".
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeInt32(long fieldId, int val) {
@@ -1041,11 +1066,12 @@
/**
* Write a single repeated proto "int32" type field value.
*
- * Note that these are stored in memory as signed values and written as unsigned
+ * <p>Note that these are stored in memory as signed values and written as unsigned
* varints, which if negative, are 10 bytes long. If you know the data is likely
* to be negative, use "sint32".
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedInt32(long fieldId, int val) {
@@ -1063,14 +1089,15 @@
/**
* Write a list of packed proto "int32" type field value.
*
- * Note that these are stored in memory as signed values and written as unsigned
+ * <p>Note that these are stored in memory as signed values and written as unsigned
* varints, which if negative, are 10 bytes long. If you know the data is likely
* to be negative, use "sint32".
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedInt32(long fieldId, int[] val) {
+ public void writePackedInt32(long fieldId, @Nullable int[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_INT32);
@@ -1099,7 +1126,8 @@
/**
* Write a single proto "int64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeInt64(long fieldId, long val) {
@@ -1119,7 +1147,8 @@
/**
* Write a single repeated proto "int64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedInt64(long fieldId, long val) {
@@ -1137,10 +1166,11 @@
/**
* Write a list of packed proto "int64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedInt64(long fieldId, long[] val) {
+ public void writePackedInt64(long fieldId, @Nullable long[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_INT64);
@@ -1168,7 +1198,8 @@
/**
* Write a single proto "uint32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeUInt32(long fieldId, int val) {
@@ -1188,7 +1219,8 @@
/**
* Write a single repeated proto "uint32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedUInt32(long fieldId, int val) {
@@ -1206,10 +1238,11 @@
/**
* Write a list of packed proto "uint32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedUInt32(long fieldId, int[] val) {
+ public void writePackedUInt32(long fieldId, @Nullable int[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_UINT32);
@@ -1237,7 +1270,8 @@
/**
* Write a single proto "uint64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeUInt64(long fieldId, long val) {
@@ -1257,7 +1291,8 @@
/**
* Write a single proto "uint64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedUInt64(long fieldId, long val) {
@@ -1275,10 +1310,11 @@
/**
* Write a single proto "uint64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedUInt64(long fieldId, long[] val) {
+ public void writePackedUInt64(long fieldId, @Nullable long[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_UINT64);
@@ -1306,7 +1342,8 @@
/**
* Write a single proto "sint32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeSInt32(long fieldId, int val) {
@@ -1326,7 +1363,8 @@
/**
* Write a single repeated proto "sint32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedSInt32(long fieldId, int val) {
@@ -1344,10 +1382,11 @@
/**
* Write a list of packed proto "sint32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedSInt32(long fieldId, int[] val) {
+ public void writePackedSInt32(long fieldId, @Nullable int[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SINT32);
@@ -1375,7 +1414,8 @@
/**
* Write a single proto "sint64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeSInt64(long fieldId, long val) {
@@ -1395,7 +1435,8 @@
/**
* Write a single repeated proto "sint64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedSInt64(long fieldId, long val) {
@@ -1413,10 +1454,11 @@
/**
* Write a list of packed proto "sint64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedSInt64(long fieldId, long[] val) {
+ public void writePackedSInt64(long fieldId, @Nullable long[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SINT64);
@@ -1443,7 +1485,8 @@
/**
* Write a single proto "fixed32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeFixed32(long fieldId, int val) {
@@ -1463,7 +1506,8 @@
/**
* Write a single repeated proto "fixed32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedFixed32(long fieldId, int val) {
@@ -1481,10 +1525,11 @@
/**
* Write a list of packed proto "fixed32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedFixed32(long fieldId, int[] val) {
+ public void writePackedFixed32(long fieldId, @Nullable int[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FIXED32);
@@ -1507,7 +1552,8 @@
/**
* Write a single proto "fixed64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeFixed64(long fieldId, long val) {
@@ -1527,7 +1573,8 @@
/**
* Write a single repeated proto "fixed64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedFixed64(long fieldId, long val) {
@@ -1545,10 +1592,11 @@
/**
* Write a list of packed proto "fixed64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedFixed64(long fieldId, long[] val) {
+ public void writePackedFixed64(long fieldId, @Nullable long[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FIXED64);
@@ -1570,7 +1618,8 @@
/**
* Write a single proto "sfixed32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeSFixed32(long fieldId, int val) {
@@ -1590,7 +1639,8 @@
/**
* Write a single repeated proto "sfixed32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedSFixed32(long fieldId, int val) {
@@ -1608,10 +1658,11 @@
/**
* Write a list of packed proto "sfixed32" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedSFixed32(long fieldId, int[] val) {
+ public void writePackedSFixed32(long fieldId, @Nullable int[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SFIXED32);
@@ -1634,7 +1685,8 @@
/**
* Write a single proto "sfixed64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeSFixed64(long fieldId, long val) {
@@ -1654,7 +1706,8 @@
/**
* Write a single repeated proto "sfixed64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedSFixed64(long fieldId, long val) {
@@ -1672,10 +1725,11 @@
/**
* Write a list of packed proto "sfixed64" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, long)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedSFixed64(long fieldId, long[] val) {
+ public void writePackedSFixed64(long fieldId, @Nullable long[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SFIXED64);
@@ -1698,7 +1752,8 @@
/**
* Write a single proto "bool" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, boolean)} instead.
+ * @hide
*/
@Deprecated
public void writeBool(long fieldId, boolean val) {
@@ -1719,7 +1774,8 @@
/**
* Write a single repeated proto "bool" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, boolean)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedBool(long fieldId, boolean val) {
@@ -1737,10 +1793,11 @@
/**
* Write a list of packed proto "bool" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, boolean)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedBool(long fieldId, boolean[] val) {
+ public void writePackedBool(long fieldId, @Nullable boolean[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_BOOL);
@@ -1767,10 +1824,11 @@
/**
* Write a single proto "string" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, String)} instead.
+ * @hide
*/
@Deprecated
- public void writeString(long fieldId, String val) {
+ public void writeString(long fieldId, @Nullable String val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_STRING);
@@ -1786,10 +1844,11 @@
/**
* Write a single repeated proto "string" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, String)} instead.
+ * @hide
*/
@Deprecated
- public void writeRepeatedString(long fieldId, String val) {
+ public void writeRepeatedString(long fieldId, @Nullable String val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_STRING);
@@ -1828,10 +1887,11 @@
/**
* Write a single proto "bytes" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, byte[])} instead.
+ * @hide
*/
@Deprecated
- public void writeBytes(long fieldId, byte[] val) {
+ public void writeBytes(long fieldId, @Nullable byte[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES);
@@ -1848,10 +1908,11 @@
/**
* Write a single repeated proto "bytes" type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, byte[])} instead.
+ * @hide
*/
@Deprecated
- public void writeRepeatedBytes(long fieldId, byte[] val) {
+ public void writeRepeatedBytes(long fieldId, @Nullable byte[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BYTES);
@@ -1874,7 +1935,8 @@
/**
* Write a single proto enum type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeEnum(long fieldId, int val) {
@@ -1894,7 +1956,8 @@
/**
* Write a single repeated proto enum type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
public void writeRepeatedEnum(long fieldId, int val) {
@@ -1912,10 +1975,11 @@
/**
* Write a list of packed proto enum type field value.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, int)} instead.
+ * @hide
*/
@Deprecated
- public void writePackedEnum(long fieldId, int[] val) {
+ public void writePackedEnum(long fieldId, @Nullable int[] val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_ENUM);
@@ -1940,7 +2004,8 @@
* Returns a token which should be passed to endObject. Calls to endObject must be
* nested properly.
*
- * @deprecated Use #start() instead.
+ * @deprecated Use {@link #start(long)} instead.
+ * @hide
*/
@Deprecated
public long startObject(long fieldId) {
@@ -1953,7 +2018,8 @@
/**
* End a child object. Pass in the token from the correspoinding startObject call.
*
- * @deprecated Use #end() instead.
+ * @deprecated Use {@link #end(long)} instead.
+ * @hide
*/
@Deprecated
public void endObject(long token) {
@@ -1968,7 +2034,8 @@
* Returns a token which should be passed to endObject. Calls to endObject must be
* nested properly.
*
- * @deprecated Use #start() instead.
+ * @deprecated Use {@link #start(long)} instead.
+ * @hide
*/
@Deprecated
public long startRepeatedObject(long fieldId) {
@@ -1981,7 +2048,8 @@
/**
* End a child object. Pass in the token from the correspoinding startRepeatedObject call.
*
- * @deprecated Use #end() instead.
+ * @deprecated Use {@link #end(long)} instead.
+ * @hide
*/
@Deprecated
public void endRepeatedObject(long token) {
@@ -2064,12 +2132,13 @@
}
/**
- * Write an object that has already been flattend.
+ * Write an object that has already been flattened.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, byte[])} instead.
+ * @hide
*/
@Deprecated
- public void writeObject(long fieldId, byte[] value) {
+ public void writeObject(long fieldId, @Nullable byte[] value) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
@@ -2084,12 +2153,13 @@
}
/**
- * Write an object that has already been flattend.
+ * Write an object that has already been flattened.
*
- * @deprecated Use #write instead.
+ * @deprecated Use {@link #write(long, byte[])} instead.
+ * @hide
*/
@Deprecated
- public void writeRepeatedObject(long fieldId, byte[] value) {
+ public void writeRepeatedObject(long fieldId, @Nullable byte[] value) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
@@ -2115,11 +2185,11 @@
}
/**
- * Validates that the fieldId providied is of the type and count from expectedType.
+ * Validates that the fieldId provided is of the type and count from expectedType.
*
- * The type must match exactly to pass this check.
+ * <p>The type must match exactly to pass this check.
*
- * The count must match according to this truth table to pass the check:
+ * <p>The count must match according to this truth table to pass the check:
*
* expectedFlags
* UNKNOWN SINGLE REPEATED PACKED
@@ -2129,7 +2199,7 @@
* REPEATED x false true false
* PACKED x false true true
*
- * @throws IllegalArgumentException if it is not.
+ * @throws {@link IllegalArgumentException} if it is not.
*
* @return The raw ID of that field.
*/
@@ -2201,7 +2271,7 @@
}
/**
- * Write a field tage to the stream.
+ * Write a field tag to the stream.
*/
public void writeTag(int id, int wireType) {
mBuffer.writeRawVarint32((id << FIELD_ID_SHIFT) | wireType);
@@ -2239,10 +2309,10 @@
* Finish the encoding of the data, and return a byte[] with
* the protobuf formatted data.
*
- * After this call, do not call any of the write* functions. The
+ * <p>After this call, do not call any of the write* functions. The
* behavior is undefined.
*/
- public byte[] getBytes() {
+ public @NonNull byte[] getBytes() {
compactIfNecessary();
return mBuffer.getBytes(mBuffer.getReadableSize());
@@ -2289,7 +2359,7 @@
}
/**
- * First compaction pass. Iterate through the data, and fill in the
+ * First compaction pass. Iterate through the data, and fill in the
* nested object sizes so the next pass can compact them.
*/
private int editEncodedSize(int rawSize) {
@@ -2416,10 +2486,10 @@
/**
* Write remaining data to the output stream. If there is no output stream,
* this function does nothing. Any currently open objects (i.e. ones that
- * have not had endObject called for them will not be written). Whether this
+ * have not had {@link #end(long)} called for them will not be written). Whether this
* writes objects that are closed if there are remaining open objects is
* undefined (current implementation does not write it, future ones will).
- * For now, can either call getBytes() or flush(), but not both.
+ * For now, can either call {@link #getBytes()} or {@link #flush()}, but not both.
*/
public void flush() {
if (mStream == null) {
@@ -2457,7 +2527,7 @@
/**
* Dump debugging data about the buffers with the given log tag.
*/
- public void dump(String tag) {
+ public void dump(@NonNull String tag) {
Log.d(tag, mBuffer.getDebugString());
mBuffer.dumpBuffers(tag);
}
diff --git a/core/java/android/util/proto/ProtoStream.java b/core/java/android/util/proto/ProtoStream.java
index 9e2e95a..4969d8a 100644
--- a/core/java/android/util/proto/ProtoStream.java
+++ b/core/java/android/util/proto/ProtoStream.java
@@ -16,28 +16,104 @@
package android.util.proto;
-import android.annotation.TestApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
/**
- * Abstract base class for both protobuf streams.
+ * Base utility class for protobuf streams.
*
- * Contains a set of useful constants and methods used by both
- * ProtoOutputStream and ProtoInputStream
+ * Contains a set of constants and methods used in generated code for
+ * {@link ProtoOutputStream}.
*
* @hide
*/
-@TestApi
-public abstract class ProtoStream {
+public class ProtoStream {
+ /**
+ * Number of bits to shift the field number to form a tag.
+ *
+ * <pre>
+ * // Reading a field number from a tag.
+ * int fieldNumber = tag >>> FIELD_ID_SHIFT;
+ *
+ * // Building a tag from a field number and a wire type.
+ * int tag = (fieldNumber << FIELD_ID_SHIFT) | wireType;
+ * </pre>
+ *
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final int FIELD_ID_SHIFT = 3;
+
+ /**
+ * Mask to select the wire type from a tag.
+ *
+ * <pre>
+ * // Reading a wire type from a tag.
+ * int wireType = tag & WIRE_TYPE_MASK;
+ *
+ * // Building a tag from a field number and a wire type.
+ * int tag = (fieldNumber << FIELD_ID_SHIFT) | wireType;
+ * </pre>
+ *
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final int WIRE_TYPE_MASK = (1 << FIELD_ID_SHIFT) - 1;
+
+ /**
+ * Mask to select the field id from a tag.
+ * @hide (not used by anything, and not actually useful, because you also want
+ * to shift when you mask the field id).
+ */
public static final int FIELD_ID_MASK = ~WIRE_TYPE_MASK;
+ /**
+ * Varint wire type code.
+ *
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final int WIRE_TYPE_VARINT = 0;
+
+ /**
+ * Fixed64 wire type code.
+ *
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final int WIRE_TYPE_FIXED64 = 1;
+
+ /**
+ * Length delimited wire type code.
+ *
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final int WIRE_TYPE_LENGTH_DELIMITED = 2;
+
+ /**
+ * Start group wire type code.
+ *
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final int WIRE_TYPE_START_GROUP = 3;
+
+ /**
+ * End group wire type code.
+ *
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final int WIRE_TYPE_END_GROUP = 4;
+
+ /**
+ * Fixed32 wire type code.
+ *
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final int WIRE_TYPE_FIXED32 = 5;
/**
@@ -51,32 +127,147 @@
*/
public static final long FIELD_TYPE_MASK = 0x0ffL << FIELD_TYPE_SHIFT;
+ /**
+ * Not a real wire type.
+ * @hide
+ */
public static final long FIELD_TYPE_UNKNOWN = 0;
+
+ /*
+ * The FIELD_TYPE_ constants are copied from
+ * external/protobuf/src/google/protobuf/descriptor.h directly, so no
+ * extra mapping needs to be maintained in this case.
+ */
+
/**
- * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
- * so no extra mapping needs to be maintained in this case.
+ * Field type code for double fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, double)
+ * ProtoOutputStream.write(long, double)} method.
*/
public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for float fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, float)
+ * ProtoOutputStream.write(long, float)} method.
+ */
public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for int64 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, long)
+ * ProtoOutputStream.write(long, long)} method.
+ */
public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for uint64 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, long)
+ * ProtoOutputStream.write(long, long)} method.
+ */
public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for int32 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, int)
+ * ProtoOutputStream.write(long, int)} method.
+ */
public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for fixed64 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, long)
+ * ProtoOutputStream.write(long, long)} method.
+ */
public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for fixed32 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, int)
+ * ProtoOutputStream.write(long, int)} method.
+ */
+
+ /**
+ * Field type code for fixed32 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, int)
+ * ProtoOutputStream.write(long, int)} method.
+ */
public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for bool fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, boolean)
+ * ProtoOutputStream.write(long, boolean)} method.
+ */
public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for string fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, String)
+ * ProtoOutputStream.write(long, String)} method.
+ */
public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
+
// public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
+
+ /**
+ * Field type code for message fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#start(long)
+ * ProtoOutputStream.start(long)} method.
+ */
public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for bytes fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, byte[])
+ * ProtoOutputStream.write(long, byte[])} method.
+ */
public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for uint32 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, int)
+ * ProtoOutputStream.write(long, int)} method.
+ */
public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for enum fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, int)
+ * ProtoOutputStream.write(long, int)} method.
+ */
public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for sfixed32 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, int)
+ * ProtoOutputStream.write(long, int)} method.
+ */
public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for sfixed64 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, long)
+ * ProtoOutputStream.write(long, long)} method.
+ */
public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for sint32 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, int)
+ * ProtoOutputStream.write(long, int)} method.
+ */
public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
+
+ /**
+ * Field type code for sint64 fields. Used to build constants in generated
+ * code for use with the {@link ProtoOutputStream#write(long, long)
+ * ProtoOutputStream.write(long, long)} method.
+ */
public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
- protected static final String[] FIELD_TYPE_NAMES = new String[]{
+ private static final @NonNull String[] FIELD_TYPE_NAMES = new String[]{
"Double",
"Float",
"Int64",
@@ -100,19 +291,94 @@
//
// FieldId flags for whether the field is single, repeated or packed.
//
+ /**
+ * Bit offset for building a field id to be used with a
+ * <code>{@link ProtoOutputStream}.write(...)</code>.
+ *
+ * @see #FIELD_COUNT_MASK
+ * @see #FIELD_COUNT_UNKNOWN
+ * @see #FIELD_COUNT_SINGLE
+ * @see #FIELD_COUNT_REPEATED
+ * @see #FIELD_COUNT_PACKED
+ */
public static final int FIELD_COUNT_SHIFT = 40;
+
+ /**
+ * Bit mask for selecting the field count when reading a field id that
+ * is used with a <code>{@link ProtoOutputStream}.write(...)</code> method.
+ *
+ * @see #FIELD_COUNT_SHIFT
+ * @see #FIELD_COUNT_MASK
+ * @see #FIELD_COUNT_UNKNOWN
+ * @see #FIELD_COUNT_SINGLE
+ * @see #FIELD_COUNT_REPEATED
+ * @see #FIELD_COUNT_PACKED
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final long FIELD_COUNT_MASK = 0x0fL << FIELD_COUNT_SHIFT;
+ /**
+ * Unknown field count, encoded into a field id used with a
+ * <code>{@link ProtoOutputStream}.write(...)</code> method.
+ *
+ * @see #FIELD_COUNT_SHIFT
+ * @see #FIELD_COUNT_MASK
+ * @see #FIELD_COUNT_SINGLE
+ * @see #FIELD_COUNT_REPEATED
+ * @see #FIELD_COUNT_PACKED
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final long FIELD_COUNT_UNKNOWN = 0;
+
+ /**
+ * Single field count, encoded into a field id used with a
+ * <code>{@link ProtoOutputStream}.write(...)</code> method.
+ *
+ * @see #FIELD_COUNT_SHIFT
+ * @see #FIELD_COUNT_MASK
+ * @see #FIELD_COUNT_UNKNOWN
+ * @see #FIELD_COUNT_REPEATED
+ * @see #FIELD_COUNT_PACKED
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final long FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+
+ /**
+ * Repeated field count, encoded into a field id used with a
+ * <code>{@link ProtoOutputStream}.write(...)</code> method.
+ *
+ * @see #FIELD_COUNT_SHIFT
+ * @see #FIELD_COUNT_MASK
+ * @see #FIELD_COUNT_UNKNOWN
+ * @see #FIELD_COUNT_SINGLE
+ * @see #FIELD_COUNT_PACKED
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final long FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+
+ /**
+ * Repeated packed field count, encoded into a field id used with a
+ * <code>{@link ProtoOutputStream}.write(...)</code> method.
+ *
+ * @see #FIELD_COUNT_SHIFT
+ * @see #FIELD_COUNT_MASK
+ * @see #FIELD_COUNT_UNKNOWN
+ * @see #FIELD_COUNT_SINGLE
+ * @see #FIELD_COUNT_REPEATED
+ * @see <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
+ * Encoding</a>
+ */
public static final long FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
/**
* Get the developer-usable name of a field type.
*/
- public static String getFieldTypeString(long fieldType) {
+ public static @Nullable String getFieldTypeString(long fieldType) {
int index = ((int) ((fieldType & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) - 1;
if (index >= 0 && index < FIELD_TYPE_NAMES.length) {
return FIELD_TYPE_NAMES[index];
@@ -124,7 +390,7 @@
/**
* Get the developer-usable name of a field count.
*/
- public static String getFieldCountString(long fieldCount) {
+ public static @Nullable String getFieldCountString(long fieldCount) {
if (fieldCount == FIELD_COUNT_SINGLE) {
return "";
} else if (fieldCount == FIELD_COUNT_REPEATED) {
@@ -139,7 +405,7 @@
/**
* Get the developer-usable name of a wire type.
*/
- public static String getWireTypeString(int wireType) {
+ public static @Nullable String getWireTypeString(int wireType) {
switch (wireType) {
case WIRE_TYPE_VARINT:
return "Varint";
@@ -161,7 +427,7 @@
/**
* Get a debug string for a fieldId.
*/
- public static String getFieldIdString(long fieldId) {
+ public static @NonNull String getFieldIdString(long fieldId) {
final long fieldCount = fieldId & FIELD_COUNT_MASK;
String countString = getFieldCountString(fieldCount);
if (countString == null) {
@@ -218,29 +484,39 @@
/**
* Get the encoded tag size from the token.
+ *
+ * @hide
*/
public static int getTagSizeFromToken(long token) {
return (int) (0x7 & (token >> 61));
}
/**
- * Get whether this is a call to startObject (false) or startRepeatedObject (true).
+ * Get whether the token has the repeated bit set to true or false
+ *
+ * @hide
*/
public static boolean getRepeatedFromToken(long token) {
return (0x1 & (token >> 60)) != 0;
}
/**
- * Get the nesting depth of startObject calls from the token.
+ * Get the nesting depth from the token.
+ *
+ * @hide
*/
public static int getDepthFromToken(long token) {
return (int) (0x01ff & (token >> 51));
}
/**
- * Get the object ID from the token. The object ID is a serial number for the
+ * Get the object ID from the token.
+ *
+ * <p>The object ID is a serial number for the
* startObject calls that have happened on this object. The values are truncated
* to 9 bits, but that is sufficient for error checking.
+ *
+ * @hide
*/
public static int getObjectIdFromToken(long token) {
return (int) (0x07ffff & (token >> 32));
@@ -248,6 +524,8 @@
/**
* Get the location of the offset recorded in the token.
+ *
+ * @hide
*/
public static int getOffsetFromToken(long token) {
return (int) token;
@@ -255,8 +533,11 @@
/**
* Convert the object ID to the ordinal value -- the n-th call to startObject.
- * The object IDs start at -1 and count backwards, so that the value is unlikely
+ *
+ * <p>The object IDs start at -1 and count backwards, so that the value is unlikely
* to alias with an actual size field that had been written.
+ *
+ * @hide
*/
public static int convertObjectIdToOrdinal(int objectId) {
return (-1 & 0x07ffff) - objectId;
@@ -265,7 +546,7 @@
/**
* Return a debugging string of a token.
*/
- public static String token2String(long token) {
+ public static @NonNull String token2String(long token) {
if (token == 0L) {
return "Token(0)";
} else {
@@ -277,4 +558,9 @@
+ ')';
}
}
+
+ /**
+ * @hide
+ */
+ protected ProtoStream() {}
}
diff --git a/core/java/android/util/proto/ProtoUtils.java b/core/java/android/util/proto/ProtoUtils.java
index 03bf590..a71561b 100644
--- a/core/java/android/util/proto/ProtoUtils.java
+++ b/core/java/android/util/proto/ProtoUtils.java
@@ -24,12 +24,12 @@
/**
* This class contains a list of helper functions to write common proto in
* //frameworks/base/core/proto/android/base directory
+ * @hide
*/
public class ProtoUtils {
/**
* Dump AggStats to ProtoOutputStream
- * @hide
*/
public static void toAggStatsProto(ProtoOutputStream proto, long fieldId,
long min, long average, long max) {
@@ -42,7 +42,6 @@
/**
* Dump Duration to ProtoOutputStream
- * @hide
*/
public static void toDuration(ProtoOutputStream proto, long fieldId, long startMs, long endMs) {
final long token = proto.start(fieldId);
@@ -53,7 +52,6 @@
/**
* Helper function to write bit-wise flags to proto as repeated enums
- * @hide
*/
public static void writeBitWiseFlagsToProtoEnum(ProtoOutputStream proto, long fieldId,
int flags, int[] origEnums, int[] protoEnums) {
diff --git a/core/java/android/util/proto/TEST_MAPPING b/core/java/android/util/proto/TEST_MAPPING
index cf9f077..5b98741 100644
--- a/core/java/android/util/proto/TEST_MAPPING
+++ b/core/java/android/util/proto/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "ProtoInputStreamTests"
+ },
+ {
+ "name": "CtsProtoTestCases"
}
]
-}
\ No newline at end of file
+}
diff --git a/core/java/android/util/proto/package.html b/core/java/android/util/proto/package.html
index a636bd4..ef1125b 100644
--- a/core/java/android/util/proto/package.html
+++ b/core/java/android/util/proto/package.html
@@ -1,5 +1,5 @@
+<html>
<body>
Provides utility classes to export protocol buffers from the system.
-
-{@hide}
</body>
+</html>
\ No newline at end of file
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 28eb79a..987edf7 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,13 +16,13 @@
package android.view;
-import static android.view.DisplayEventReceiver.CONFIG_CHANGED_EVENT_SUPPRESS;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.FrameInfo;
+import android.graphics.Insets;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Build;
import android.os.Handler;
@@ -219,9 +219,10 @@
/**
* Callback type: Animation callback to handle inset updates. This is separate from
* {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via
- * {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then
- * update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress}
- * that contains all the combined updated insets.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} for multiple
+ * ongoing animations but then update the whole view system with a single callback to
+ * {@link View#dispatchWindowInsetsAnimationProgress} that contains all the combined updated
+ * insets.
* <p>
* Both input and animation may change insets, so we need to run this after these callbacks, but
* before traversals.
diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java
index 696e048..876331b 100644
--- a/core/java/android/view/ContextThemeWrapper.java
+++ b/core/java/android/view/ContextThemeWrapper.java
@@ -18,7 +18,7 @@
import android.annotation.Nullable;
import android.annotation.StyleRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.AssetManager;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 03e68b0..b555d20 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -22,8 +22,8 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.KeyguardManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -43,7 +43,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* Provides information about the size and density of a logical display.
@@ -382,6 +384,23 @@
/** @hide */
public static final int COLOR_MODE_DISPLAY_P3 = 9;
+ /** @hide **/
+ @IntDef(prefix = {"COLOR_MODE_"}, value = {
+ COLOR_MODE_INVALID,
+ COLOR_MODE_DEFAULT,
+ COLOR_MODE_BT601_625,
+ COLOR_MODE_BT601_625_UNADJUSTED,
+ COLOR_MODE_BT601_525,
+ COLOR_MODE_BT601_525_UNADJUSTED,
+ COLOR_MODE_BT709,
+ COLOR_MODE_DCI_P3,
+ COLOR_MODE_SRGB,
+ COLOR_MODE_ADOBE_RGB,
+ COLOR_MODE_DISPLAY_P3
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ColorMode {}
+
/**
* Indicates that when display is removed, all its activities will be moved to the primary
* display and the topmost activity should become focused.
@@ -960,6 +979,37 @@
}
/**
+ * Gets the supported wide color gamuts of this device.
+ *
+ * @return Supported WCG color spaces.
+ * @hide
+ */
+ public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
+ synchronized (this) {
+ final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
+ updateDisplayInfoLocked();
+ if (!isWideColorGamut()) {
+ return defaultColorSpaces;
+ }
+
+ final int[] colorModes = getSupportedColorModes();
+ final List<ColorSpace> colorSpaces = new ArrayList<>();
+ for (int colorMode : colorModes) {
+ // Refer to DisplayInfo#isWideColorGamut.
+ switch (colorMode) {
+ case COLOR_MODE_DCI_P3:
+ colorSpaces.add(ColorSpace.get(ColorSpace.Named.DCI_P3));
+ break;
+ case COLOR_MODE_DISPLAY_P3:
+ colorSpaces.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+ break;
+ }
+ }
+ return colorSpaces.toArray(defaultColorSpaces);
+ }
+ }
+
+ /**
* Gets the app VSYNC offset, in nanoseconds. This is a positive value indicating
* the phase offset of the VSYNC events provided by Choreographer relative to the
* display refresh. For example, if Choreographer reports that the refresh occurred
@@ -1525,5 +1575,14 @@
public int describeContents() {
return 0;
}
+
+ @Override
+ public String toString() {
+ return "HdrCapabilities{"
+ + "mSupportedHdrTypes=" + Arrays.toString(mSupportedHdrTypes)
+ + ", mMaxLuminance=" + mMaxLuminance
+ + ", mMaxAverageLuminance=" + mMaxAverageLuminance
+ + ", mMinLuminance=" + mMinLuminance + '}';
+ }
}
}
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index da4d92fa..834dd7b 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 411508f..615dab0 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -66,6 +66,7 @@
private static final String BOTTOM_MARKER = "@bottom";
private static final String DP_MARKER = "@dp";
private static final String RIGHT_MARKER = "@right";
+ private static final String LEFT_MARKER = "@left";
/**
* Category for overlays that allow emulating a display cutout on devices that don't have
@@ -647,6 +648,9 @@
if (spec.endsWith(RIGHT_MARKER)) {
offsetX = displayWidth;
spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
+ } else if (spec.endsWith(LEFT_MARKER)) {
+ offsetX = 0;
+ spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
} else {
offsetX = displayWidth / 2f;
}
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 91acc46..eaf297c 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 04e82c7..8a5385d 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -24,7 +24,7 @@
import static android.view.DisplayInfoProto.NAME;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index 8e6e99a..6035cbe 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.BaseRecordingCanvas;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index 2a43bcc..35af0f2 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
import android.os.Parcel;
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index ea66656..054dff7 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -17,7 +17,7 @@
package android.view;
import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/core/java/android/view/FrameMetricsObserver.java b/core/java/android/view/FrameMetricsObserver.java
index 0f38e84..41bc9a7 100644
--- a/core/java/android/view/FrameMetricsObserver.java
+++ b/core/java/android/view/FrameMetricsObserver.java
@@ -17,11 +17,8 @@
package android.view;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Looper;
-import android.os.MessageQueue;
-
-import com.android.internal.util.VirtualRefBasePtr;
+import android.graphics.HardwareRendererObserver;
+import android.os.Handler;
import java.lang.ref.WeakReference;
@@ -31,47 +28,39 @@
*
* @hide
*/
-public class FrameMetricsObserver {
- @UnsupportedAppUsage
- private MessageQueue mMessageQueue;
-
- private WeakReference<Window> mWindow;
-
- @UnsupportedAppUsage
- private FrameMetrics mFrameMetrics;
-
- /* pacage */ Window.OnFrameMetricsAvailableListener mListener;
- /** @hide */
- public VirtualRefBasePtr mNative;
+public class FrameMetricsObserver
+ implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
+ private final WeakReference<Window> mWindow;
+ private final FrameMetrics mFrameMetrics;
+ private final HardwareRendererObserver mObserver;
+ /*package*/ final Window.OnFrameMetricsAvailableListener mListener;
/**
* Creates a FrameMetricsObserver
*
- * @param looper the looper to use when invoking callbacks
+ * @param handler the Handler to use when invoking callbacks
*/
- FrameMetricsObserver(@NonNull Window window, @NonNull Looper looper,
+ FrameMetricsObserver(@NonNull Window window, @NonNull Handler handler,
@NonNull Window.OnFrameMetricsAvailableListener listener) {
- if (looper == null) {
- throw new NullPointerException("looper cannot be null");
- }
-
- mMessageQueue = looper.getQueue();
- if (mMessageQueue == null) {
- throw new IllegalStateException("invalid looper, null message queue\n");
- }
-
- mFrameMetrics = new FrameMetrics();
mWindow = new WeakReference<>(window);
mListener = listener;
+ mFrameMetrics = new FrameMetrics();
+ mObserver = new HardwareRendererObserver(this, mFrameMetrics.mTimingData, handler);
}
- // Called by native on the provided Handler
- @SuppressWarnings("unused")
- @UnsupportedAppUsage
- private void notifyDataAvailable(int dropCount) {
- final Window window = mWindow.get();
- if (window != null) {
- mListener.onFrameMetricsAvailable(window, mFrameMetrics, dropCount);
+ /**
+ * Implementation of OnFrameMetricsAvailableListener
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * @Override
+ */
+ public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+ if (mWindow.get() != null) {
+ mListener.onFrameMetricsAvailable(mWindow.get(), mFrameMetrics,
+ dropCountSinceLastInvocation);
}
}
+
+ /*package*/ HardwareRendererObserver getRendererObserver() {
+ return mObserver;
+ }
}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index d59ee92..4d71136 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -23,7 +23,7 @@
import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
-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/view/GhostView.java b/core/java/android/view/GhostView.java
index 3286bd6..a728327 100644
--- a/core/java/android/view/GhostView.java
+++ b/core/java/android/view/GhostView.java
@@ -15,7 +15,7 @@
*/
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RecordingCanvas;
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index f1d152b..00edb3a 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -45,4 +45,17 @@
* {@param animationDuration} suggests the animation duration transitioning to PiP window.
*/
void startAnimation(in Rect destinationBounds, in Rect sourceRectHint, int animationDuration);
+
+ /**
+ * Notifies the controller to reset on bounds animation, if there is any.
+ * This could happen when screen rotation is happening and we need to notify the WM to reset
+ * any running bounds animation on the pinned stack.
+ * {@param bounds} here is the final destination bounds.
+ */
+ void resetBoundsAnimation(in Rect bounds);
+
+ /**
+ * Reports the current default and movement bounds to controller.
+ */
+ void reportBounds(in Rect defaultBounds, in Rect movementBounds);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b829c9f..9496827 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -44,6 +44,7 @@
import android.view.IRotationWatcher;
import android.view.ISystemGestureExclusionListener;
import android.view.IWallpaperVisibilityListener;
+import android.view.IWindow;
import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
import android.view.KeyEvent;
@@ -120,6 +121,17 @@
void setDisplayWindowRotationController(IDisplayWindowRotationController controller);
/**
+ * Adds a root container that a client shell can populate with its own windows (usually via
+ * WindowlessWindowManager).
+ *
+ * @param client an IWindow used for window-level communication (ime, finish draw, etc.).
+ * @param windowType used by WM to determine the z-order. This is the same as the window type
+ * used in {@link WindowManager.LayoutParams}.
+ * @return a SurfaceControl to add things to.
+ */
+ SurfaceControl addShellRoot(int displayId, IWindow client, int windowType);
+
+ /**
* Like overridePendingAppTransitionMultiThumb, but uses a future to supply the specs. This is
* used for recents, where generating the thumbnails of the specs takes a non-trivial amount of
* time, so we want to move that off the critical path for starting the new activity.
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
index 5d0d5bd..5a64b13 100644
--- a/core/java/android/view/InputChannel.java
+++ b/core/java/android/view/InputChannel.java
@@ -16,7 +16,7 @@
package android.view;
-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/view/InputDevice.java b/core/java/android/view/InputDevice.java
index e723f91..360dedd 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -18,7 +18,7 @@
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 4b88a6a..5f9c480 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index e4b1a8d..c0a3cec 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.util.Log;
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 3080b42..5674de8 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.IBinder;
import android.os.Looper;
import android.os.MessageQueue;
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index c5f4c23..86a309e 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
diff --git a/core/java/android/view/InputFilter.java b/core/java/android/view/InputFilter.java
index 3aaf31e..36d5586 100644
--- a/core/java/android/view/InputFilter.java
+++ b/core/java/android/view/InputFilter.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index 69ebc46..74ce6ac 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.LongSparseArray;
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 3d139cd..cdfd397 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -21,7 +21,6 @@
import static android.view.InsetsState.ISIDE_LEFT;
import static android.view.InsetsState.ISIDE_RIGHT;
import static android.view.InsetsState.ISIDE_TOP;
-import static android.view.InsetsState.toPublicType;
import android.annotation.Nullable;
import android.graphics.Insets;
@@ -34,7 +33,8 @@
import android.view.InsetsState.InternalInsetsSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.WindowManager.LayoutParams;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,23 +66,28 @@
private final @InsetsType int mTypes;
private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier;
private final InsetsController mController;
- private final WindowInsetsAnimationListener.InsetsAnimation mAnimation;
+ private final WindowInsetsAnimationCallback.InsetsAnimation mAnimation;
private final Rect mFrame;
+ private final boolean mFade;
private Insets mCurrentInsets;
private Insets mPendingInsets;
+ private float mPendingFraction;
private boolean mFinished;
private boolean mCancelled;
- private int mFinishedShownTypes;
+ private boolean mShownOnFinish;
+ private float mCurrentAlpha;
+ private float mPendingAlpha;
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types,
Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
- InsetsController controller) {
+ InsetsController controller, long durationMs, boolean fade) {
mConsumers = consumers;
mListener = listener;
mTypes = types;
+ mFade = fade;
mTransactionApplierSupplier = transactionApplierSupplier;
mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
@@ -97,9 +102,11 @@
// TODO: Check for controllability first and wait for IME if needed.
listener.onReady(this, types);
- mAnimation = new WindowInsetsAnimationListener.InsetsAnimation(mTypes, mHiddenInsets,
- mShownInsets);
- mController.dispatchAnimationStarted(mAnimation);
+ mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
+ InsetsController.INTERPOLATOR, durationMs);
+ mAnimation.setAlpha(getCurrentAlpha());
+ mController.dispatchAnimationStarted(mAnimation,
+ new AnimationBounds(mHiddenInsets, mShownInsets));
}
@Override
@@ -118,12 +125,17 @@
}
@Override
+ public float getCurrentAlpha() {
+ return mCurrentAlpha;
+ }
+
+ @Override
@InsetsType public int getTypes() {
return mTypes;
}
@Override
- public void changeInsets(Insets insets) {
+ public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
if (mFinished) {
throw new IllegalStateException(
"Can't change insets on an animation that is finished.");
@@ -132,7 +144,9 @@
throw new IllegalStateException(
"Can't change insets on an animation that is cancelled.");
}
+ mPendingFraction = sanitize(fraction);
mPendingInsets = sanitize(insets);
+ mPendingAlpha = 1 - sanitize(alpha);
mController.scheduleApplyChangeInsets();
}
@@ -145,40 +159,52 @@
return false;
}
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
+ final Float alphaOffset = 1 - mPendingAlpha;
ArrayList<SurfaceParams> params = new ArrayList<>();
- updateLeashesForSide(ISIDE_LEFT, offset.left, mPendingInsets.left, params, state);
- updateLeashesForSide(ISIDE_TOP, offset.top, mPendingInsets.top, params, state);
- updateLeashesForSide(ISIDE_RIGHT, offset.right, mPendingInsets.right, params, state);
- updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mPendingInsets.bottom, params, state);
- updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, params, state);
+ updateLeashesForSide(ISIDE_LEFT, offset.left, mShownInsets.left, mPendingInsets.left,
+ params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_TOP, offset.top, mShownInsets.top, mPendingInsets.top, params,
+ state, alphaOffset);
+ updateLeashesForSide(ISIDE_RIGHT, offset.right, mShownInsets.right, mPendingInsets.right,
+ params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mShownInsets.bottom,
+ mPendingInsets.bottom, params, state, alphaOffset);
+ updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, 0 /* maxInset */,
+ params, state, alphaOffset);
SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
+ mAnimation.setFraction(mPendingFraction);
+ mCurrentAlpha = 1 - alphaOffset;
if (mFinished) {
- mController.notifyFinished(this, mFinishedShownTypes);
+ mController.notifyFinished(this, mShownOnFinish);
}
return mFinished;
}
@Override
- public void finish(int shownTypes) {
+ public void finish(boolean shown) {
if (mCancelled) {
return;
}
InsetsState state = new InsetsState(mController.getState());
for (int i = mConsumers.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = mConsumers.valueAt(i);
- boolean visible = (shownTypes & toPublicType(consumer.getType())) != 0;
- state.getSource(consumer.getType()).setVisible(visible);
+ state.getSource(consumer.getType()).setVisible(shown);
}
Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */);
- changeInsets(insets);
+ setInsetsAndAlpha(insets, 1f /* alpha */, shown ? 1f : 0f /* fraction */);
mFinished = true;
- mFinishedShownTypes = shownTypes;
+ mShownOnFinish = shown;
}
+ @Override
@VisibleForTesting
+ public float getCurrentFraction() {
+ return mAnimation.getFraction();
+ }
+
public void onCancelled() {
if (mFinished) {
return;
@@ -191,6 +217,10 @@
return mAnimation;
}
+ WindowInsetsAnimationControlListener getListener() {
+ return mListener;
+ }
+
private Insets calculateInsets(InsetsState state, Rect frame,
SparseArray<InsetsSourceConsumer> consumers, boolean shown,
@Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
@@ -210,11 +240,18 @@
}
private Insets sanitize(Insets insets) {
+ if (insets == null) {
+ insets = getCurrentInsets();
+ }
return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets);
}
+ private static float sanitize(float alpha) {
+ return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha);
+ }
+
private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
- ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
+ int maxInset, ArrayList<SurfaceParams> surfaceParams, InsetsState state, Float alpha) {
ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
if (items == null) {
return;
@@ -238,7 +275,9 @@
// If the system is controlling the insets source, the leash can be null.
if (leash != null) {
- surfaceParams.add(new SurfaceParams(leash, 1f /* alpha */, mTmpMatrix,
+ // TODO: use a better interpolation for fade.
+ alpha = mFade ? ((float) maxInset / inset * 0.3f + 0.7f) : alpha;
+ surfaceParams.add(new SurfaceParams(leash, alpha, mTmpMatrix,
null /* windowCrop */, 0 /* layer */, 0f /* cornerRadius*/,
side == ISIDE_FLOATING ? consumer.isVisible() : inset != 0 /* visible */));
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index bc70d63..5563d62 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -39,6 +39,8 @@
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -55,11 +57,12 @@
private static final int ANIMATION_DURATION_SHOW_MS = 275;
private static final int ANIMATION_DURATION_HIDE_MS = 340;
- private 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;
+ static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
@IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
private @interface AnimationDirection{}
@@ -85,8 +88,75 @@
return object.getCurrentInsets();
}
@Override
- public void set(WindowInsetsAnimationController object, Insets value) {
- object.changeInsets(value);
+ public void set(WindowInsetsAnimationController controller, Insets value) {
+ controller.setInsetsAndAlpha(
+ value, 1f /* alpha */, (((DefaultAnimationControlListener)
+ ((InsetsAnimationControlImpl) controller).getListener())
+ .getRawProgress()));
+ }
+ }
+
+ private class DefaultAnimationControlListener implements WindowInsetsAnimationControlListener {
+
+ private WindowInsetsAnimationController mController;
+ private ObjectAnimator mAnimator;
+ private boolean mShow;
+
+ DefaultAnimationControlListener(boolean show) {
+ mShow = show;
+ }
+
+ @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(),
+ sEvaluator,
+ mShow ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
+ mShow ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
+ );
+ mAnimator.setDuration(getDurationMs());
+ mAnimator.setInterpolator(INTERPOLATOR);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onAnimationFinish();
+ }
+ });
+ mAnimator.start();
+ }
+
+ @Override
+ public void onCancelled() {
+ // Animator can be null when it is cancelled before onReady() completes.
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
+
+ private void onAnimationFinish() {
+ mAnimationDirection = DIRECTION_NONE;
+ mController.finish(mShow);
+ }
+
+ private float getRawProgress() {
+ float fraction = (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration();
+ return mShow ? fraction : 1 - fraction;
+ }
+
+ private long getDurationMs() {
+ if (mAnimator != null) {
+ return mAnimator.getDuration();
+ }
+ return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS;
}
}
@@ -123,6 +193,10 @@
if (mAnimationControls.isEmpty()) {
return;
}
+ if (mViewRoot.mView == null) {
+ // The view has already detached from window.
+ return;
+ }
mTmpFinishedControls.clear();
InsetsState state = new InsetsState(mState, true /* copySources */);
@@ -154,6 +228,7 @@
mFrame.set(frame);
}
+ @Override
public InsetsState getState() {
return mState;
}
@@ -273,24 +348,25 @@
}
@Override
- public void controlWindowInsetsAnimation(@InsetsType int types,
+ public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
WindowInsetsAnimationControlListener listener) {
- controlWindowInsetsAnimation(types, listener, false /* fromIme */);
+ controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs);
}
private void controlWindowInsetsAnimation(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, boolean fromIme) {
+ WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) {
// 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);
+ controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
}
private void controlAnimationUnchecked(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme) {
+ WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
+ long durationMs, boolean fade) {
if (types == 0) {
// nothing to animate.
return;
@@ -321,7 +397,7 @@
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
frame, mState, listener, typesReady,
- () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
+ () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this, durationMs, fade);
mAnimationControls.add(controller);
}
@@ -392,10 +468,13 @@
}
@VisibleForTesting
- public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) {
+ public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
mAnimationControls.remove(controller);
- hideDirectly(controller.getTypes() & ~shownTypes);
- showDirectly(controller.getTypes() & shownTypes);
+ if (shown) {
+ showDirectly(controller.getTypes());
+ } else {
+ hideDirectly(controller.getTypes());
+ }
}
void notifyControlRevoked(InsetsSourceConsumer consumer) {
@@ -505,58 +584,12 @@
return;
}
- WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
-
- private WindowInsetsAnimationController mController;
- private ObjectAnimator mAnimator;
-
- @Override
- public void onReady(WindowInsetsAnimationController controller, int types) {
- mController = controller;
- if (show) {
- showDirectly(types);
- } else {
- hideDirectly(types);
- }
- mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
- mAnimator = ObjectAnimator.ofObject(
- controller,
- new InsetsProperty(),
- sEvaluator,
- show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
- show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
- );
- mAnimator.setDuration(show
- ? ANIMATION_DURATION_SHOW_MS
- : ANIMATION_DURATION_HIDE_MS);
- mAnimator.setInterpolator(INTERPOLATOR);
- mAnimator.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onAnimationFinish();
- }
- });
- mAnimator.start();
- }
-
- @Override
- public void onCancelled() {
- // Animator can be null when it is cancelled before onReady() completes.
- if (mAnimator != null) {
- mAnimator.cancel();
- }
- }
-
- private void onAnimationFinish() {
- mAnimationDirection = DIRECTION_NONE;
- mController.finish(show ? types : 0);
- }
- };
-
+ final DefaultAnimationControlListener listener = new DefaultAnimationControlListener(show);
// Show/hide animations always need to be relative to the display frame, in order that shown
// and hidden state insets are correct.
- controlAnimationUnchecked(types, listener, mState.getDisplayFrame(), fromIme);
+ controlAnimationUnchecked(
+ types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
+ true /* fade */);
}
private void hideDirectly(@InsetsType int types) {
@@ -587,12 +620,12 @@
}
@VisibleForTesting
- public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
- mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
+ public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
+ mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
}
@VisibleForTesting
- public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ public void dispatchAnimationFinished(InsetsAnimation animation) {
mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index bd03348..90e0f3f 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.input.InputManager;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 60db6a5..c638717 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
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/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 75862e0..1afe11e 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -21,7 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
@@ -31,10 +31,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.Message;
-import android.os.SystemProperties;
import android.os.Trace;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 1c94e02..c3c7b95 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -22,7 +22,7 @@
import android.annotation.IntDef;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Matrix;
import android.os.Build;
import android.os.Parcel;
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index d54e9d5..8ec5df8 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -17,9 +17,9 @@
package android.view;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.AppOpsManager;
import android.app.Notification;
+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/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index dfe34c80..18d0d7b 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -17,8 +17,8 @@
package android.view;
import android.annotation.NonNull;
-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/view/RemoteAnimationAdapter.java b/core/java/android/view/RemoteAnimationAdapter.java
index c6864401..166d3ba 100644
--- a/core/java/android/view/RemoteAnimationAdapter.java
+++ b/core/java/android/view/RemoteAnimationAdapter.java
@@ -16,8 +16,8 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityOptions;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index da599ef..5a8ac54 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -19,12 +19,15 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.ActivityType;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.WindowManager.TransitionType;
@@ -124,6 +127,20 @@
}
}
+ /**
+ * Links the death of the runner to the provided death recipient.
+ */
+ public void linkToDeath(IBinder.DeathRecipient deathRecipient) {
+ try {
+ for (int i = 0; i < mTransitionAnimationMap.size(); i++) {
+ mTransitionAnimationMap.valueAt(i).adapter.getRunner().asBinder()
+ .linkToDeath(deathRecipient, 0 /* flags */);
+ }
+ } catch (RemoteException e) {
+ Slog.e("RemoteAnimationDefinition", "Failed to link to death recipient");
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index b873482..b04372a 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -30,8 +30,8 @@
import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
import android.app.WindowConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Parcel;
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 93f52a0..06cb519 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -19,7 +19,7 @@
import android.animation.Animator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 1d72151..346f76c 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -16,7 +16,7 @@
package android.view;
-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/view/Surface.java b/core/java/android/view/Surface.java
index dac6282..7707ad1 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.CompatibilityInfo.Translator;
import android.graphics.Canvas;
import android.graphics.ColorSpace;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 87628da..c566eae 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -32,7 +32,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.Bitmap;
import android.graphics.ColorSpace;
import android.graphics.GraphicBuffer;
@@ -158,11 +158,10 @@
IBinder displayToken, long numFrames, long timestamp);
private static native int nativeGetActiveConfig(IBinder displayToken);
private static native boolean nativeSetActiveConfig(IBinder displayToken, int id);
- private static native boolean nativeSetAllowedDisplayConfigs(IBinder displayToken,
- int[] allowedConfigs);
- private static native int[] nativeGetAllowedDisplayConfigs(IBinder displayToken);
private static native boolean nativeSetDesiredDisplayConfigSpecs(IBinder displayToken,
SurfaceControl.DesiredDisplayConfigSpecs desiredDisplayConfigSpecs);
+ private static native SurfaceControl.DesiredDisplayConfigSpecs
+ nativeGetDesiredDisplayConfigSpecs(IBinder displayToken);
private static native int[] nativeGetDisplayColorModes(IBinder displayToken);
private static native SurfaceControl.DisplayPrimaries nativeGetDisplayNativePrimaries(
IBinder displayToken);
@@ -1474,58 +1473,60 @@
}
/**
- * @hide
- */
- public static boolean setAllowedDisplayConfigs(IBinder displayToken, int[] allowedConfigs) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (allowedConfigs == null) {
- throw new IllegalArgumentException("allowedConfigs must not be null");
- }
-
- return nativeSetAllowedDisplayConfigs(displayToken, allowedConfigs);
- }
-
- /**
- * @hide
- */
- public static int[] getAllowedDisplayConfigs(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- return nativeGetAllowedDisplayConfigs(displayToken);
- }
-
- /**
* Contains information about desired display configuration.
*
* @hide
*/
public static final class DesiredDisplayConfigSpecs {
- /**
- * @hide
- */
- public int mDefaultModeId;
+ public int defaultConfig;
+ public float minRefreshRate;
+ public float maxRefreshRate;
- /**
- * @hide
- */
- public float mMinRefreshRate;
+ public DesiredDisplayConfigSpecs() {}
- /**
- * @hide
- */
- public float mMaxRefreshRate;
+ public DesiredDisplayConfigSpecs(DesiredDisplayConfigSpecs other) {
+ copyFrom(other);
+ }
- /**
- * @hide
- */
public DesiredDisplayConfigSpecs(
- int defaultModeId, float minRefreshRate, float maxRefreshRate) {
- mDefaultModeId = defaultModeId;
- mMinRefreshRate = minRefreshRate;
- mMaxRefreshRate = maxRefreshRate;
+ int defaultConfig, float minRefreshRate, float maxRefreshRate) {
+ this.defaultConfig = defaultConfig;
+ this.minRefreshRate = minRefreshRate;
+ this.maxRefreshRate = maxRefreshRate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof DesiredDisplayConfigSpecs && equals((DesiredDisplayConfigSpecs) o);
+ }
+
+ /**
+ * Tests for equality.
+ */
+ public boolean equals(DesiredDisplayConfigSpecs other) {
+ return other != null && defaultConfig == other.defaultConfig
+ && minRefreshRate == other.minRefreshRate
+ && maxRefreshRate == other.maxRefreshRate;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ /**
+ * Copies the supplied object's values to this object.
+ */
+ public void copyFrom(DesiredDisplayConfigSpecs other) {
+ defaultConfig = other.defaultConfig;
+ minRefreshRate = other.minRefreshRate;
+ maxRefreshRate = other.maxRefreshRate;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("defaultConfig=%d min=%.0f max=%.0f", defaultConfig,
+ minRefreshRate, maxRefreshRate);
}
}
@@ -1544,6 +1545,18 @@
/**
* @hide
*/
+ public static SurfaceControl.DesiredDisplayConfigSpecs getDesiredDisplayConfigSpecs(
+ IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ return nativeGetDesiredDisplayConfigSpecs(displayToken);
+ }
+
+ /**
+ * @hide
+ */
public static int[] getDisplayColorModes(IBinder displayToken) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
@@ -2092,6 +2105,13 @@
Runnable mFreeNativeResources;
/**
+ * @hide
+ */
+ protected void checkPreconditions(SurfaceControl sc) {
+ sc.checkNotReleased();
+ }
+
+ /**
* Open a new transaction object. The transaction may be filed with commands to
* manipulate {@link SurfaceControl} instances, and then applied atomically with
* {@link #apply}. Eventually the user should invoke {@link #close}, when the object
@@ -2155,7 +2175,7 @@
*/
@NonNull
public Transaction setVisibility(@NonNull SurfaceControl sc, boolean visible) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
if (visible) {
return show(sc);
} else {
@@ -2172,7 +2192,7 @@
*/
@UnsupportedAppUsage
public Transaction show(SurfaceControl sc) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
return this;
}
@@ -2186,7 +2206,7 @@
*/
@UnsupportedAppUsage
public Transaction hide(SurfaceControl sc) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
return this;
}
@@ -2196,7 +2216,7 @@
*/
@UnsupportedAppUsage
public Transaction setPosition(SurfaceControl sc, float x, float y) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
return this;
}
@@ -2213,7 +2233,7 @@
@NonNull
public Transaction setBufferSize(@NonNull SurfaceControl sc,
@IntRange(from = 0) int w, @IntRange(from = 0) int h) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
mResizedSurfaces.put(sc, new Point(w, h));
nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
return this;
@@ -2231,7 +2251,7 @@
@NonNull
public Transaction setLayer(@NonNull SurfaceControl sc,
@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetLayer(mNativeObject, sc.mNativeObject, z);
return this;
}
@@ -2240,7 +2260,7 @@
* @hide
*/
public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.mNativeObject, z);
return this;
}
@@ -2249,7 +2269,7 @@
* @hide
*/
public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetTransparentRegionHint(mNativeObject,
sc.mNativeObject, transparentRegion);
return this;
@@ -2265,7 +2285,7 @@
@NonNull
public Transaction setAlpha(@NonNull SurfaceControl sc,
@FloatRange(from = 0.0, to = 1.0) float alpha) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
return this;
}
@@ -2274,7 +2294,7 @@
* @hide
*/
public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle);
return this;
}
@@ -2304,7 +2324,7 @@
@NonNull
public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop,
@Nullable Rect destFrame, @Surface.Rotation int orientation) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetGeometry(mNativeObject, sc.mNativeObject, sourceCrop, destFrame, orientation);
return this;
}
@@ -2315,7 +2335,7 @@
@UnsupportedAppUsage
public Transaction setMatrix(SurfaceControl sc,
float dsdx, float dtdx, float dtdy, float dsdy) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetMatrix(mNativeObject, sc.mNativeObject,
dsdx, dtdx, dtdy, dsdy);
return this;
@@ -2349,7 +2369,7 @@
*/
public Transaction setColorTransform(SurfaceControl sc, @Size(9) float[] matrix,
@Size(3) float[] translation) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetColorTransform(mNativeObject, sc.mNativeObject, matrix, translation);
return this;
}
@@ -2361,7 +2381,7 @@
* @hide
*/
public Transaction setColorSpaceAgnostic(SurfaceControl sc, boolean agnostic) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetColorSpaceAgnostic(mNativeObject, sc.mNativeObject, agnostic);
return this;
}
@@ -2378,7 +2398,7 @@
*/
@UnsupportedAppUsage
public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
if (crop != null) {
nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
crop.left, crop.top, crop.right, crop.bottom);
@@ -2399,7 +2419,7 @@
* @hide
*/
public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
return this;
}
@@ -2413,7 +2433,7 @@
*/
@UnsupportedAppUsage
public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius);
return this;
@@ -2424,7 +2444,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
return this;
}
@@ -2438,7 +2458,7 @@
if (frameNumber < 0) {
return this;
}
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, barrier.mNativeObject,
frameNumber);
return this;
@@ -2453,7 +2473,7 @@
if (frameNumber < 0) {
return this;
}
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
barrierSurface.mNativeObject, frameNumber);
return this;
@@ -2463,7 +2483,7 @@
* @hide
*/
public Transaction reparentChildren(SurfaceControl sc, SurfaceControl newParent) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeReparentChildren(mNativeObject, sc.mNativeObject, newParent.mNativeObject);
return this;
}
@@ -2480,7 +2500,7 @@
@NonNull
public Transaction reparent(@NonNull SurfaceControl sc,
@Nullable SurfaceControl newParent) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
long otherObject = 0;
if (newParent != null) {
newParent.checkNotReleased();
@@ -2494,7 +2514,7 @@
* @hide
*/
public Transaction detachChildren(SurfaceControl sc) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSeverChildren(mNativeObject, sc.mNativeObject);
return this;
}
@@ -2503,7 +2523,7 @@
* @hide
*/
public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
overrideScalingMode);
return this;
@@ -2516,7 +2536,7 @@
*/
@UnsupportedAppUsage
public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetColor(mNativeObject, sc.mNativeObject, color);
return this;
}
@@ -2527,7 +2547,7 @@
* @hide
*/
public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
if (isSecure) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
} else {
@@ -2542,7 +2562,7 @@
* @hide
*/
public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
if (isOpaque) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
} else {
@@ -2657,7 +2677,7 @@
* @hide
*/
public Transaction setMetadata(SurfaceControl sc, int key, Parcel data) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetMetadata(mNativeObject, sc.mNativeObject, key, data);
return this;
}
@@ -2680,7 +2700,7 @@
* @hide
*/
public Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
- sc.checkNotReleased();
+ checkPreconditions(sc);
nativeSetShadowRadius(mNativeObject, sc.mNativeObject, shadowRadius);
return this;
}
@@ -2763,4 +2783,27 @@
}
};
}
+
+ /**
+ * A debugging utility subclass of SurfaceControl.Transaction. At construction
+ * you can pass in a monitor object, and all the other methods will throw an exception
+ * if the monitor is not held when they are called.
+ * @hide
+ */
+ public static class LockDebuggingTransaction extends SurfaceControl.Transaction {
+ Object mMonitor;
+
+ public LockDebuggingTransaction(Object o) {
+ mMonitor = o;
+ }
+
+ @Override
+ protected void checkPreconditions(SurfaceControl sc) {
+ super.checkPreconditions(sc);
+ if (!Thread.holdsLock(mMonitor)) {
+ throw new RuntimeException(
+ "Unlocked access to synchronized SurfaceControl.Transaction");
+ }
+ }
+ }
}
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index 361ac93..0f851c1 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* An instance of this class represents a connection to the surface
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0b5af2d..95a75eb 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -22,7 +22,7 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.CompatibilityInfo.Translator;
import android.graphics.BlendMode;
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 5876b03..277b872 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.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.graphics.Bitmap;
import android.graphics.Canvas;
diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java
index 2ea95e9..de0f9e5 100644
--- a/core/java/android/view/TouchDelegate.java
+++ b/core/java/android/view/TouchDelegate.java
@@ -17,7 +17,7 @@
package android.view;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.ArrayMap;
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 7154f2b..a56633e 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Pools.SynchronizedPool;
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9d4f3878..0db80e2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -43,7 +43,7 @@
import android.annotation.StyleRes;
import android.annotation.TestApi;
import android.annotation.UiThread;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.AutofillOptions;
import android.content.ClipData;
import android.content.Context;
@@ -109,7 +109,8 @@
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
@@ -4626,7 +4627,7 @@
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
- private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
+ private WindowInsetsAnimationCallback mWindowInsetsAnimationCallback;
/**
* This lives here since it's only valid for interactive views.
@@ -6161,10 +6162,27 @@
mRenderNode = RenderNode.create(getClass().getName(), new ViewAnimationHostBridge(this));
}
- final boolean debugDraw() {
+ /**
+ * Returns {@code true} when the View is attached and the system developer setting to show
+ * the layout bounds is enabled or {@code false} otherwise.
+ */
+ public final boolean isShowingLayoutBounds() {
return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout;
}
+ /**
+ * Used to test isShowingLayoutBounds(). This sets the local value used
+ * by that function. This method does nothing if the layout isn't attached.
+ *
+ * @hide
+ */
+ @TestApi
+ public final void setShowingLayoutBounds(boolean debugLayout) {
+ if (mAttachInfo != null) {
+ mAttachInfo.mDebugLayout = debugLayout;
+ }
+ }
+
private static SparseArray<String> getAttributeMap() {
if (mAttributeMap == null) {
mAttributeMap = new SparseArray<>();
@@ -7146,10 +7164,9 @@
mFrameMetricsObservers = new ArrayList<>();
}
- FrameMetricsObserver fmo = new FrameMetricsObserver(window,
- handler.getLooper(), listener);
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window, handler, listener);
mFrameMetricsObservers.add(fmo);
- mAttachInfo.mThreadedRenderer.addFrameMetricsObserver(fmo);
+ mAttachInfo.mThreadedRenderer.addObserver(fmo.getRendererObserver());
} else {
Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
}
@@ -7158,8 +7175,7 @@
mFrameMetricsObservers = new ArrayList<>();
}
- FrameMetricsObserver fmo = new FrameMetricsObserver(window,
- handler.getLooper(), listener);
+ FrameMetricsObserver fmo = new FrameMetricsObserver(window, handler, listener);
mFrameMetricsObservers.add(fmo);
}
}
@@ -7181,7 +7197,7 @@
if (mFrameMetricsObservers != null) {
mFrameMetricsObservers.remove(fmo);
if (renderer != null) {
- renderer.removeFrameMetricsObserver(fmo);
+ renderer.removeObserver(fmo.getRendererObserver());
}
}
}
@@ -7191,7 +7207,7 @@
ThreadedRenderer renderer = getThreadedRenderer();
if (renderer != null) {
for (FrameMetricsObserver fmo : mFrameMetricsObservers) {
- renderer.addFrameMetricsObserver(fmo);
+ renderer.addObserver(fmo.getRendererObserver());
}
} else {
Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
@@ -11091,33 +11107,55 @@
}
/**
- * Sets a {@link WindowInsetsAnimationListener} to be notified about animations of windows that
+ * Sets a {@link WindowInsetsAnimationCallback} to be notified about animations of windows that
* cause insets.
*
* @param listener The listener to set.
- * @hide pending unhide
*/
- public void setWindowInsetsAnimationListener(WindowInsetsAnimationListener listener) {
- getListenerInfo().mWindowInsetsAnimationListener = listener;
+ public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) {
+ getListenerInfo().mWindowInsetsAnimationCallback = listener;
}
- void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- mListenerInfo.mWindowInsetsAnimationListener.onStarted(animation);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+ * when Window Insets animation is started.
+ * @param animation current animation
+ * @param bounds the upper and lower {@link AnimationBounds} that provides range of
+ * {@link InsetsAnimation}.
+ * @return the upper and lower {@link AnimationBounds}.
+ */
+ @NonNull
+ public AnimationBounds dispatchWindowInsetsAnimationStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
}
+ return bounds;
}
- WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- return mListenerInfo.mWindowInsetsAnimationListener.onProgress(insets);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onProgress(WindowInsets)}
+ * when Window Insets animation makes progress.
+ * @param insets The current {@link WindowInsets}.
+ * @return current {@link WindowInsets}.
+ */
+ @NonNull
+ public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ return mListenerInfo.mWindowInsetsAnimationCallback.onProgress(insets);
} else {
return insets;
}
}
- void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
- if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
- mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation);
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+ * when Window Insets animation finishes.
+ * @param animation The current ongoing {@link InsetsAnimation}.
+ */
+ public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
}
}
@@ -11253,7 +11291,6 @@
* @return The {@link WindowInsetsController} or {@code null} if the view isn't attached to a
* a window.
* @see Window#getInsetsController()
- * @hide pending unhide
*/
public @Nullable WindowInsetsController getWindowInsetsController() {
if (mAttachInfo != null) {
@@ -16163,7 +16200,7 @@
* by the most recent call to {@link #measure(int, int)}. This result is a bit mask
* as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
* This should be used during measurement and layout calculations only. Use
- * {@link #getHeight()} to see how wide a view is after layout.
+ * {@link #getHeight()} to see how high a view is after layout.
*
* @return The measured height of this view as a bit mask.
*/
@@ -20861,7 +20898,7 @@
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
- if (debugDraw()) {
+ if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
} else {
@@ -22006,7 +22043,7 @@
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
- if (debugDraw()) {
+ if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
@@ -22181,7 +22218,7 @@
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
- if (debugDraw()) {
+ if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 9e914d4..774a2de 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -18,8 +18,8 @@
import android.annotation.FloatRange;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.AppGlobals;
+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/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index a62ba63..2f44fe0 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.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.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 853a302..5fb7177 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -24,7 +24,7 @@
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UiThread;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
@@ -51,7 +51,8 @@
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -680,7 +681,7 @@
private void initViewGroup() {
// ViewGroup doesn't draw by default
- if (!debugDraw()) {
+ if (!isShowingLayoutBounds()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
mGroupFlags |= FLAG_CLIP_CHILDREN;
@@ -4155,7 +4156,7 @@
}
if (usingRenderNodeProperties) canvas.insertInorderBarrier();
- if (debugDraw()) {
+ if (isShowingLayoutBounds()) {
onDebugDraw(canvas);
}
@@ -7198,16 +7199,20 @@
}
@Override
- void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
- super.dispatchWindowInsetsAnimationStarted(animation);
+ @NonNull
+ public AnimationBounds dispatchWindowInsetsAnimationStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ super.dispatchWindowInsetsAnimationStarted(animation, bounds);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
- getChildAt(i).dispatchWindowInsetsAnimationStarted(animation);
+ getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
}
+ return bounds;
}
@Override
- WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
+ @NonNull
+ public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
insets = super.dispatchWindowInsetsAnimationProgress(insets);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
@@ -7217,7 +7222,7 @@
}
@Override
- void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
+ public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
super.dispatchWindowInsetsAnimationFinished(animation);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
diff --git a/core/java/android/view/ViewHierarchyEncoder.java b/core/java/android/view/ViewHierarchyEncoder.java
index d5716bf..b0e0524 100644
--- a/core/java/android/view/ViewHierarchyEncoder.java
+++ b/core/java/android/view/ViewHierarchyEncoder.java
@@ -2,7 +2,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index e23c687..7830c57 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -17,7 +17,7 @@
import android.animation.LayoutTransition;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3171306..3bab9eb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -19,12 +19,29 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.View.PFLAG_DRAW_ANIMATION;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import android.Manifest;
@@ -32,10 +49,10 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ResourcesManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
@@ -93,6 +110,7 @@
import android.view.View.AttachInfo;
import android.view.View.FocusDirection;
import android.view.View.MeasureSpec;
+import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.accessibility.AccessibilityEvent;
@@ -118,6 +136,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.PhoneFallbackEventHandler;
@@ -519,6 +538,9 @@
private WindowInsets mLastWindowInsets;
+ // Insets types hidden by legacy window flags or system UI flags.
+ private @InsetsType int mTypesHiddenByFlags = 0;
+
/** Last applied configuration obtained from resources. */
private final Configuration mLastConfigurationFromResources = new Configuration();
/** Last configuration reported from WM or via {@link #MSG_UPDATE_CONFIGURATION}. */
@@ -890,6 +912,7 @@
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
+ adjustLayoutParamsForCompatibility(mWindowAttributes);
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
@@ -1841,15 +1864,96 @@
private int getImpliedSystemUiVisibility(WindowManager.LayoutParams params) {
int vis = 0;
// Translucent decor window flags imply stable system ui visibility.
- if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) {
+ if ((params.flags & FLAG_TRANSLUCENT_STATUS) != 0) {
vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
}
- if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0) {
+ if ((params.flags & FLAG_TRANSLUCENT_NAVIGATION) != 0) {
vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
return vis;
}
+ @VisibleForTesting
+ public static void adjustLayoutParamsForCompatibility(WindowManager.LayoutParams inOutParams) {
+ if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
+ return;
+ }
+ final int sysUiVis = inOutParams.systemUiVisibility | inOutParams.subtreeSystemUiVisibility;
+ final int flags = inOutParams.flags;
+ final int type = inOutParams.type;
+ final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST;
+
+ if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0) {
+ inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+ } else if ((sysUiVis & SYSTEM_UI_FLAG_IMMERSIVE) != 0) {
+ inOutParams.insetsFlags.behavior = BEHAVIOR_SHOW_BARS_BY_SWIPE;
+ }
+
+ if ((inOutParams.privateFlags & PRIVATE_FLAG_FIT_INSETS_CONTROLLED) != 0) {
+ return;
+ }
+
+ int types = inOutParams.getFitWindowInsetsTypes();
+ int sides = inOutParams.getFitWindowInsetsSides();
+ boolean ignoreVis = inOutParams.getFitIgnoreVisibility();
+
+ if (((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0
+ || (flags & FLAG_LAYOUT_IN_SCREEN) != 0)
+ || (flags & FLAG_TRANSLUCENT_STATUS) != 0) {
+ types &= ~Type.statusBars();
+ }
+ if ((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+ || (flags & FLAG_TRANSLUCENT_NAVIGATION) != 0) {
+ types &= ~Type.systemBars();
+ }
+ if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
+ ignoreVis = true;
+ } else if ((types & Type.systemBars()) == Type.systemBars()
+ && adjust == SOFT_INPUT_ADJUST_RESIZE) {
+ types |= Type.ime();
+ }
+ inOutParams.setFitWindowInsetsTypes(types);
+ inOutParams.setFitWindowInsetsSides(sides);
+ inOutParams.setFitIgnoreVisibility(ignoreVis);
+
+ // The fitting of insets are not really controlled by the clients, so we remove the flag.
+ inOutParams.privateFlags &= ~PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+ }
+
+ private void controlInsetsForCompatibility(WindowManager.LayoutParams params) {
+ if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
+ return;
+ }
+ final int sysUiVis = params.systemUiVisibility | params.subtreeSystemUiVisibility;
+ final int flags = params.flags;
+ final boolean statusWasHiddenByFlags = (mTypesHiddenByFlags & Type.statusBars()) != 0;
+ final boolean statusIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_FULLSCREEN) != 0
+ || (flags & FLAG_FULLSCREEN) != 0;
+ final boolean navWasHiddenByFlags = (mTypesHiddenByFlags & Type.navigationBars()) != 0;
+ final boolean navIsHiddenByFlags = (sysUiVis & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+
+ @InsetsType int typesToHide = 0;
+ @InsetsType int typesToShow = 0;
+ if (statusIsHiddenByFlags && !statusWasHiddenByFlags) {
+ typesToHide |= Type.statusBars();
+ } else if (!statusIsHiddenByFlags && statusWasHiddenByFlags) {
+ typesToShow |= Type.statusBars();
+ }
+ if (navIsHiddenByFlags && !navWasHiddenByFlags) {
+ typesToHide |= Type.navigationBars();
+ } else if (!navIsHiddenByFlags && navWasHiddenByFlags) {
+ typesToShow |= Type.navigationBars();
+ }
+ if (typesToHide != 0) {
+ getInsetsController().hide(typesToHide);
+ }
+ if (typesToShow != 0) {
+ getInsetsController().show(typesToShow);
+ }
+ mTypesHiddenByFlags |= typesToHide;
+ mTypesHiddenByFlags &= ~typesToShow;
+ }
+
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
@@ -2271,6 +2375,8 @@
&& !PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
}
+ adjustLayoutParamsForCompatibility(params);
+ controlInsetsForCompatibility(params);
}
if (mFirst || windowShouldResize || insetsChanged ||
@@ -2757,7 +2863,7 @@
if (changedVisibility || regainedFocus) {
// Toasts are presented as notifications - don't present them as windows as well
boolean isToast = (mWindowAttributes == null) ? false
- : (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST);
+ : (mWindowAttributes.type == TYPE_TOAST);
if (!isToast) {
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
@@ -4051,7 +4157,7 @@
}
boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
- final Rect ci = mAttachInfo.mContentInsets;
+ final Rect ci = getWindowInsets(false).getSystemWindowInsetsAsRect();
final Rect vi = mAttachInfo.mVisibleInsets;
int scrollY = 0;
boolean handled = false;
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index c72baca..d7b0afc 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.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.graphics.Rect;
import android.graphics.Region;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index c699cdc..0776469 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -26,8 +26,8 @@
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.WindowConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -46,6 +46,8 @@
import android.transition.Scene;
import android.transition.Transition;
import android.transition.TransitionManager;
+import android.view.WindowInsets.Side.InsetsSide;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.accessibility.AccessibilityEvent;
import java.util.Collections;
@@ -1170,16 +1172,6 @@
/**
* {@hide}
*/
- @UnsupportedAppUsage
- protected void setNeedsMenuKey(int value) {
- final WindowManager.LayoutParams attrs = getAttributes();
- attrs.needsMenuKey = value;
- dispatchWindowAttributesChanged(attrs);
- }
-
- /**
- * {@hide}
- */
protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
if (mCallback != null) {
mCallback.onWindowAttributesChanged(attrs);
@@ -1251,6 +1243,60 @@
}
/**
+ * A shortcut for {@link WindowManager.LayoutParams#setFitWindowInsetsTypes(int)}
+ * @hide pending unhide
+ */
+ public void setFitWindowInsetsTypes(@InsetsType int types) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.setFitWindowInsetsTypes(types);
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * A shortcut for {@link WindowManager.LayoutParams#setFitWindowInsetsSides(int)}
+ * @hide pending unhide
+ */
+ public void setFitWindowInsetsSides(@InsetsSide int sides) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.setFitWindowInsetsSides(sides);
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * A shortcut for {@link WindowManager.LayoutParams#setFitIgnoreVisibility(boolean)}
+ * @hide pending unhide
+ */
+ public void setFitIgnoreVisibility(boolean ignore) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ attrs.setFitIgnoreVisibility(ignore);
+ dispatchWindowAttributesChanged(attrs);
+ }
+
+ /**
+ * A shortcut for {@link WindowManager.LayoutParams#getFitWindowInsetsTypes}
+ * @hide pending unhide
+ */
+ public @InsetsType int getFitWindowInsetsTypes() {
+ return getAttributes().getFitWindowInsetsTypes();
+ }
+
+ /**
+ * A shortcut for {@link WindowManager.LayoutParams#getFitWindowInsetsSides()}
+ * @hide pending unhide
+ */
+ public @InsetsSide int getFitWindowInsetsSides() {
+ return getAttributes().getFitWindowInsetsSides();
+ }
+
+ /**
+ * A shortcut for {@link WindowManager.LayoutParams#getFitIgnoreVisibility()}
+ * @hide pending unhide
+ */
+ public boolean getFitIgnoreVisibility() {
+ return getAttributes().getFitIgnoreVisibility();
+ }
+
+ /**
* Specify custom window attributes. <strong>PLEASE NOTE:</strong> the
* layout params you give here should generally be from values previously
* retrieved with {@link #getAttributes()}; you probably do not want to
@@ -2527,7 +2573,8 @@
/**
* @return The {@link WindowInsetsController} associated with this window
* @see View#getWindowInsetsController()
- * @hide pending unhide
*/
- public abstract @NonNull WindowInsetsController getInsetsController();
+ public @Nullable WindowInsetsController getInsetsController() {
+ return null;
+ }
}
diff --git a/core/java/android/view/WindowAnimationFrameStats.java b/core/java/android/view/WindowAnimationFrameStats.java
index 399dfba..dfc4f0c 100644
--- a/core/java/android/view/WindowAnimationFrameStats.java
+++ b/core/java/android/view/WindowAnimationFrameStats.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/view/WindowContentFrameStats.java b/core/java/android/view/WindowContentFrameStats.java
index 9fa5a00..217197c 100644
--- a/core/java/android/view/WindowContentFrameStats.java
+++ b/core/java/android/view/WindowContentFrameStats.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 57bd5bb..a9cc50f 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -34,7 +34,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.Intent;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -177,7 +177,7 @@
* @return The insets that include system bars indicated by {@code typeMask}, taken from
* {@code typeInsetsMap}.
*/
- private static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
+ static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
Insets result = null;
for (int i = FIRST; i <= LAST; i = i << 1) {
if ((typeMask & i) == 0) {
@@ -289,9 +289,8 @@
*
* @param typeMask Bit mask of {@link InsetsType}s to query the insets for.
* @return The insets.
- *
- * @hide pending unhide
*/
+ @NonNull
public Insets getInsets(@InsetsType int typeMask) {
return getInsets(mTypeInsetsMap, typeMask);
}
@@ -313,8 +312,8 @@
* insets are not available for this type as the height of the
* IME is dynamic depending on the {@link EditorInfo} of the
* currently focused view, as well as the UI state of the IME.
- * @hide pending unhide
*/
+ @NonNull
public Insets getMaxInsets(@InsetsType int typeMask) throws IllegalArgumentException {
if ((typeMask & IME) != 0) {
throw new IllegalArgumentException("Unable to query the maximum insets for IME");
@@ -329,7 +328,6 @@
* @param typeMask Bit mask of {@link Type.InsetsType}s to query visibility status.
* @return {@code true} if and only if all windows included in {@code typeMask} are currently
* visible on screen.
- * @hide pending unhide
*/
public boolean isVisible(@InsetsType int typeMask) {
for (int i = FIRST; i <= LAST; i = i << 1) {
@@ -874,7 +872,7 @@
return typeInsetsMap;
}
- private static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
+ static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
int newLeft = Math.max(0, insets.left - left);
int newTop = Math.max(0, insets.top - top);
int newRight = Math.max(0, insets.right - right);
@@ -1015,7 +1013,6 @@
* @param insets The insets to set.
*
* @return itself
- * @hide pending unhide
*/
@NonNull
public Builder setInsets(@InsetsType int typeMask, @NonNull Insets insets) {
@@ -1046,7 +1043,6 @@
* the IME is dynamic depending on the {@link EditorInfo}
* of the currently focused view, as well as the UI
* state of the IME.
- * @hide pending unhide
*/
@NonNull
public Builder setMaxInsets(@InsetsType int typeMask, @NonNull Insets insets)
@@ -1070,7 +1066,6 @@
* @param visible Whether to mark the windows as visible or not.
*
* @return itself
- * @hide pending unhide
*/
@NonNull
public Builder setVisible(@InsetsType int typeMask, boolean visible) {
@@ -1145,7 +1140,6 @@
/**
* Class that defines different types of sources causing window insets.
- * @hide pending unhide
*/
public static final class Type {
@@ -1295,4 +1289,31 @@
return 0xFFFFFFFF;
}
}
+
+ /**
+ * Class that defines different sides for insets.
+ * @hide pending unhide
+ */
+ public static final class Side {
+
+ public static final int LEFT = 1 << 0;
+ public static final int TOP = 1 << 1;
+ public static final int RIGHT = 1 << 2;
+ public static final int BOTTOM = 1 << 3;
+
+ private Side() {
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {LEFT, TOP, RIGHT, BOTTOM})
+ public @interface InsetsSide {}
+
+ /**
+ * @return all four sides.
+ */
+ public static @InsetsSide int all() {
+ return LEFT | TOP | RIGHT | BOTTOM;
+ }
+ }
}
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
new file mode 100644
index 0000000..5e71f27
--- /dev/null
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -0,0 +1,276 @@
+/*
+ * 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.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.animation.Interpolator;
+
+/**
+ * Interface that allows the application to listen to animation events for windows that cause
+ * insets.
+ */
+public interface WindowInsetsAnimationCallback {
+
+ /**
+ * Called when an inset 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
+ * of the specific {@link View} that is being traversed. The method my return a modified
+ * instance of the bounds by calling {@link AnimationBounds#inset} to indicate that a part of
+ * the insets have been used to offset or clip its children, and the children shouldn't worry
+ * about that part anymore.
+ *
+ * @param animation The animation that is about to start.
+ * @param bounds The bounds in which animation happens.
+ * @return The animation representing the part of the insets that should be dispatched to the
+ * subtree of the hierarchy.
+ */
+ @NonNull
+ default AnimationBounds onStarted(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ return bounds;
+ }
+
+ /**
+ * Called when the insets change as part of running an animation. Note that even if multiple
+ * animations for different types are running, there will only be one progress callback per
+ * frame. The {@code insets} passed as an argument represents the overall state and will include
+ * all types, regardless of whether they are animating or not.
+ * <p>
+ * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
+ * and then traverse it and invoke the callback of the specific {@link View} being traversed.
+ * The method may return a modified instance by calling
+ * {@link WindowInsets#inset(int, int, int, int)} to indicate that a part of the insets have
+ * been used to offset or clip its children, and the children shouldn't worry about that part
+ * anymore.
+ * TODO: Introduce a way to map (type -> InsetAnimation) so app developer can query animation
+ * for a given type e.g. callback.getAnimation(type) OR controller.getAnimation(type).
+ * Or on the controller directly?
+ * @param insets The current insets.
+ * @return The insets to dispatch to the subtree of the hierarchy.
+ */
+ @NonNull
+ WindowInsets onProgress(@NonNull WindowInsets insets);
+
+ /**
+ * Called when an inset animation has finished.
+ *
+ * @param animation The animation that has finished running. This will be the same instance as
+ * passed into {@link #onStarted}
+ */
+ default void onFinished(@NonNull InsetsAnimation animation) {
+ }
+
+ /**
+ * Class representing an animation of a set of windows that cause insets.
+ */
+ final class InsetsAnimation {
+
+ private final @InsetsType int mTypeMask;
+ private float mFraction;
+ @Nullable private final Interpolator mInterpolator;
+ private long mDurationMs;
+ private float mAlpha;
+
+ public InsetsAnimation(
+ @InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMs) {
+ mTypeMask = typeMask;
+ mInterpolator = interpolator;
+ mDurationMs = durationMs;
+ }
+
+ /**
+ * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
+ */
+ public @InsetsType int getTypeMask() {
+ return mTypeMask;
+ }
+
+ /**
+ * Returns the raw fractional progress of this animation between
+ * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+ * that this progress is the global progress of the animation, whereas
+ * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
+ * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+ * Progress per insets animation is global for the entire animation. One animation animates
+ * all things together (in, out, ...). If they don't animate together, we'd have
+ * multiple animations.
+ *
+ * @return The current progress of this animation.
+ */
+ @FloatRange(from = 0f, to = 1f)
+ public float getFraction() {
+ return mFraction;
+ }
+
+ /**
+ * Returns the interpolated fractional progress of this animation between
+ * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+ * that this progress is the global progress of the animation, whereas
+ * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
+ * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+ * Progress per insets animation is global for the entire animation. One animation animates
+ * all things together (in, out, ...). If they don't animate together, we'd have
+ * multiple animations.
+ * @see #getFraction() for raw fraction.
+ * @return The current interpolated progress of this animation. -1 if interpolator isn't
+ * specified.
+ */
+ public float getInterpolatedFraction() {
+ if (mInterpolator != null) {
+ return mInterpolator.getInterpolation(mFraction);
+ }
+ return -1;
+ }
+
+ @Nullable
+ public Interpolator getInterpolator() {
+ return mInterpolator;
+ }
+
+ /**
+ * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+ */
+ public long getDurationMillis() {
+ return mDurationMs;
+ }
+
+ /**
+ * Set fraction of the progress if {@link WindowInsets.Type.InsetsType} animation is
+ * controlled by the app {@see #getCurrentFraction}.
+ * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set progress either.
+ * Progress would be set by system with the system-default animation.
+ * </p>
+ * @param fraction fractional progress between 0 and 1 where 0 represents hidden and
+ * zero progress and 1 represent fully shown final state.
+ */
+ public void setFraction(@FloatRange(from = 0f, to = 1f) float fraction) {
+ mFraction = fraction;
+ }
+
+ /**
+ * Set duration of the animation if {@link WindowInsets.Type.InsetsType} animation is
+ * controlled by the app.
+ * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set duration either.
+ * Duration would be set by system with the system-default animation.
+ * </p>
+ * @param durationMs in {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+ */
+ public void setDuration(long durationMs) {
+ mDurationMs = durationMs;
+ }
+
+ /**
+ * @return alpha of {@link WindowInsets.Type.InsetsType}.
+ */
+ @FloatRange(from = 0f, to = 1f)
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) {
+ mAlpha = alpha;
+ }
+ }
+
+ /**
+ * Class representing the range of an {@link InsetsAnimation}
+ */
+ final class AnimationBounds {
+ private final Insets mLowerBound;
+ private final Insets mUpperBound;
+
+ public AnimationBounds(@NonNull Insets lowerBound, @NonNull Insets upperBound) {
+ mLowerBound = lowerBound;
+ mUpperBound = upperBound;
+ }
+
+ /**
+ * Queries the lower inset bound of the animation. If the animation is about showing or
+ * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
+ * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
+ * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+ * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+ * invoked because of an animation that originates from
+ * {@link WindowInsetsAnimationController}.
+ * <p>
+ * However, if the size of a window that causes insets is changing, these are the
+ * lower/upper bounds of that size animation.
+ * </p>
+ * There are no overlapping animations for a specific type, but there may be multiple
+ * animations running at the same time for different inset types.
+ *
+ * @see #getUpperBound()
+ * @see WindowInsetsAnimationController#getHiddenStateInsets
+ */
+ @NonNull
+ public Insets getLowerBound() {
+ return mLowerBound;
+ }
+
+ /**
+ * Queries the upper inset bound of the animation. If the animation is about showing or
+ * hiding a window that cause insets, the lower bound is {@link Insets#NONE}
+ * nd the upper bound is the same as {@link WindowInsets#getInsets(int)} for the fully
+ * shown state. This is the same as
+ * {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+ * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+ * invoked because of an animation that originates from
+ * {@link WindowInsetsAnimationController}.
+ * <p>
+ * However, if the size of a window that causes insets is changing, these are the
+ * lower/upper bounds of that size animation.
+ * <p>
+ * There are no overlapping animations for a specific type, but there may be multiple
+ * animations running at the same time for different inset types.
+ *
+ * @see #getLowerBound()
+ * @see WindowInsetsAnimationController#getShownStateInsets
+ */
+ @NonNull
+ public Insets getUpperBound() {
+ return mUpperBound;
+ }
+
+ /**
+ * 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
+ * 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
+ */
+ @NonNull
+ public AnimationBounds inset(@NonNull Insets insets) {
+ return new AnimationBounds(
+ // TODO: refactor so that WindowInsets.insetInsets() is in a more appropriate
+ // place eventually.
+ WindowInsets.insetInsets(
+ mLowerBound, insets.left, insets.top, insets.right, insets.bottom),
+ WindowInsets.insetInsets(
+ mUpperBound, insets.left, insets.top, insets.right, insets.bottom));
+ }
+ }
+}
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 33fb327..8a226c1 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -22,19 +22,17 @@
/**
* Interface that informs the client about {@link WindowInsetsAnimationController} state changes.
- * @hide pending unhide
*/
public interface WindowInsetsAnimationControlListener {
/**
- * Gets called as soon as 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.
+ * 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.
*
* @param controller The controller to control the inset animation.
* @param types The {@link InsetsType}s it was able to gain control over. Note that this may be
* different than the types passed into
- * {@link WindowInsetsController#controlWindowInsetsAnimation} in case the window
+ * {@link WindowInsetsController#controlInputMethodAnimation} in case the window
* wasn't able to gain the controls because it wasn't the IME target or not
* currently the window that's controlling the system bars.
*/
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 5cbf3b8..2bf0d27 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -16,83 +16,133 @@
package android.view;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.graphics.Insets;
import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
/**
- * Interface to control a window inset animation frame-by-frame.
- * @hide pending unhide
+ * Controller for app-driven animation of system windows.
+ * <p>
+ * {@code WindowInsetsAnimationController} lets apps animate system windows such as
+ * the {@link android.inputmethodservice.InputMethodService IME}. The animation is
+ * synchronized, such that changes the system windows and the app's current frame
+ * are rendered at the same time.
+ * <p>
+ * Control is obtained through {@link WindowInsetsController#controlInputMethodAnimation}.
*/
+@SuppressLint("NotClosable")
public interface WindowInsetsAnimationController {
/**
* Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden.
* <p>
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
+ * <p>
* If there are any animation listeners registered, this value is the same as
- * {@link InsetsAnimation#getLowerBound()} that will be passed into the callbacks.
+ * {@link AnimationBounds#getLowerBound()} that is being be passed into the root view of the
+ * hierarchy.
*
* @return Insets when the windows this animation is controlling are fully hidden.
*
- * @see InsetsAnimation#getLowerBound()
+ * @see AnimationBounds#getLowerBound()
*/
@NonNull Insets getHiddenStateInsets();
/**
* Retrieves the {@link Insets} when the windows this animation is controlling are fully shown.
* <p>
- * In case the size of a window causing insets is changing in the middle of the animation, we
- * execute that height change after this animation has finished.
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
* <p>
* If there are any animation listeners registered, this value is the same as
- * {@link InsetsAnimation#getUpperBound()} that will be passed into the callbacks.
+ * {@link AnimationBounds#getUpperBound()} that is being passed into the root view of hierarchy.
*
* @return Insets when the windows this animation is controlling are fully shown.
*
- * @see InsetsAnimation#getUpperBound()
+ * @see AnimationBounds#getUpperBound()
*/
@NonNull Insets getShownStateInsets();
/**
- * @return The current insets on the window. These will follow any animation changes.
+ * Retrieves the current insets.
+ * <p>
+ * Note that these insets are always relative to the window, which is the same as
+ * being relative
+ * to {@link View#getRootView}
+ * @return The current insets on the currently showing frame. These insets will change as the
+ * animation progresses to reflect the current insets provided by the controlled window.
*/
@NonNull Insets getCurrentInsets();
/**
+ * Returns the progress as previously set by {@code fraction} in {@link #setInsetsAndAlpha}
+ *
+ * @return the progress of the animation, where {@code 0} is fully hidden and {@code 1} is
+ * fully shown.
+ * <p>
+ * Note: this value represents raw overall progress of the animation
+ * i.e. the combined progress of insets and alpha.
+ * <p>
+ */
+ @FloatRange(from = 0f, to = 1f)
+ float getCurrentFraction();
+
+ /**
+ * Current alpha value of the window.
+ * @return float value between 0 and 1.
+ */
+ float getCurrentAlpha();
+
+ /**
* @return The {@link InsetsType}s this object is currently controlling.
*/
@InsetsType int getTypes();
/**
- * Modifies the insets by indirectly moving the windows around in the system that are causing
- * window insets.
+ * Modifies the insets for the frame being drawn by indirectly moving the windows around in the
+ * system that are causing window insets.
* <p>
- * Note that this will <b>not</b> inform the view system of a full inset change via
+ * Note that these insets are always relative to the window, which is the same as being relative
+ * to {@link View#getRootView}
+ * <p>
+ * Also note that this will <b>not</b> inform the view system of a full inset change via
* {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the
* animation. If you'd like to animate views during a window inset animation, register a
- * {@link WindowInsetsAnimationListener} by calling
- * {@link View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)} that will be
- * notified about any insets change via {@link WindowInsetsAnimationListener#onProgress} during
+ * {@link WindowInsetsAnimationCallback} by calling
+ * {@link View#setWindowInsetsAnimationCallback(WindowInsetsAnimationCallback)} that will be
+ * notified about any insets change via {@link WindowInsetsAnimationCallback#onProgress} during
* the animation.
* <p>
* {@link View#dispatchApplyWindowInsets} will instead be called once the animation has
* finished, i.e. once {@link #finish} has been called.
+ * Note: If there are no insets, alpha animation is still applied.
*
* @param insets The new insets to apply. Based on the requested insets, the system will
* calculate the positions of the windows in the system causing insets such that
* the resulting insets of that configuration will match the passed in parameter.
* Note that these insets are being clamped to the range from
- * {@link #getHiddenStateInsets} to {@link #getShownStateInsets}
+ * {@link #getHiddenStateInsets} to {@link #getShownStateInsets}.
+ * If you intend on changing alpha only, pass null or {@link #getCurrentInsets()}.
+ * @param alpha The new alpha to apply to the inset side.
+ * @param fraction instantaneous animation progress. This value is dispatched to
+ * {@link WindowInsetsAnimationCallback}.
*
- * @see WindowInsetsAnimationListener
- * @see View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)
+ * @see WindowInsetsAnimationCallback
+ * @see View#setWindowInsetsAnimationCallback(WindowInsetsAnimationCallback)
*/
- void changeInsets(@NonNull Insets insets);
+ void setInsetsAndAlpha(@Nullable Insets insets, @FloatRange(from = 0f, to = 1f) float alpha,
+ @FloatRange(from = 0f, to = 1f) float fraction);
/**
- * @param shownTypes The list of windows causing insets that should remain shown after finishing
- * the animation.
+ * Finishes the animation, and leaves the windows shown or hidden. After invoking
+ * {@link #finish(boolean)}, this instance is no longer valid.
+ * @param shown if {@code true}, the windows will be shown after finishing the
+ * animation. Otherwise they will be hidden.
*/
- void finish(@InsetsType int shownTypes);
+ void finish(boolean shown);
}
diff --git a/core/java/android/view/WindowInsetsAnimationListener.java b/core/java/android/view/WindowInsetsAnimationListener.java
deleted file mode 100644
index f734b4b..0000000
--- a/core/java/android/view/WindowInsetsAnimationListener.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.view;
-
-import android.graphics.Insets;
-
-/**
- * Interface that allows the application to listen to animation events for windows that cause
- * insets.
- * @hide pending unhide
- */
-public interface WindowInsetsAnimationListener {
-
- /**
- * Called when an inset animation gets started.
- *
- * @param animation The animation that is about to start.
- */
- void onStarted(InsetsAnimation animation);
-
- /**
- * Called when the insets change as part of running an animation. Note that even if multiple
- * animations for different types are running, there will only be one progress callback per
- * frame. The {@code insets} passed as an argument represents the overall state and will include
- * all types, regardless of whether they are animating or not.
- * <p>
- * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
- * and then traverse it and invoke the callback of the specific {@link View} being traversed.
- * The callback may return a modified instance by calling {@link WindowInsets#inset(int, int, int, int)}
- * to indicate that a part of the insets have been used to offset or clip its children, and the
- * children shouldn't worry about that part anymore.
- *
- * @param insets The current insets.
- * @return The insets to dispatch to the subtree of the hierarchy.
- */
- WindowInsets onProgress(WindowInsets insets);
-
- /**
- * Called when an inset animation has finished.
- *
- * @param animation The animation that has finished running.
- */
- void onFinished(InsetsAnimation animation);
-
- /**
- * Class representing an animation of a set of windows that cause insets.
- */
- class InsetsAnimation {
-
- private final @WindowInsets.Type.InsetsType int mTypeMask;
- private final Insets mLowerBound;
- private final Insets mUpperBound;
-
- /**
- * @hide
- */
- InsetsAnimation(int typeMask, Insets lowerBound, Insets upperBound) {
- mTypeMask = typeMask;
- mLowerBound = lowerBound;
- mUpperBound = upperBound;
- }
-
- /**
- * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
- */
- public @WindowInsets.Type.InsetsType int getTypeMask() {
- return mTypeMask;
- }
-
- /**
- * Queries the lower inset bound of the animation. If the animation is about showing or
- * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
- * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
- * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
- * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
- * invoked because of an animation that originates from
- * {@link WindowInsetsAnimationController}.
- * <p>
- * However, if the size of a window that causes insets is changing, these are the
- * lower/upper bounds of that size animation.
- * <p>
- * There are no overlapping animations for a specific type, but there may be two animations
- * running at the same time for different inset types.
- *
- * @see #getUpperBound()
- * @see WindowInsetsAnimationController#getHiddenStateInsets
- * TODO: It's a bit weird that these are global per window but onProgress is hierarchical.
- * TODO: If multiple types are animating, querying the bound per type isn't possible. Should
- * we:
- * 1. Offer bounds by type here?
- * 2. Restrict one animation to one single type only?
- * Returning WindowInsets here isn't feasible in case of overlapping animations: We can't
- * fill in the insets for the types from the other animation into the WindowInsets object
- * as it's changing as well.
- */
- public Insets getLowerBound() {
- return mLowerBound;
- }
-
- /**
- * @see #getLowerBound()
- * @see WindowInsetsAnimationController#getShownStateInsets
- */
- public Insets getUpperBound() {
- return mUpperBound;
- }
- }
-}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 39e2e73..6de56be 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -30,7 +30,6 @@
* Interface to control windows that generate insets.
*
* TODO Needs more information and examples once the API is more baked.
- * @hide pending unhide
*/
public interface WindowInsetsController {
@@ -64,7 +63,10 @@
*/
int APPEARANCE_LIGHT_NAVIGATION_BARS = 1 << 4;
- /** Determines the appearance of system bars. */
+ /**
+ * Determines the appearance of system bars.
+ * @hide
+ */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
@@ -75,33 +77,40 @@
/**
* The default option for {@link #setSystemBarsBehavior(int)}. System bars will be forcibly
* shown on any user interaction on the corresponding display if navigation bars are hidden by
- * {@link #hide(int)} or {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link #hide(int)} or
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+ * @hide
*/
int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0;
/**
* Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
* hiding navigation bars by calling {@link #hide(int)} or
- * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
*
* <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
* as swiping from the edge of the screen where the bar is hidden from.</p>
+ * @hide
*/
int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1;
/**
* Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
* hiding navigation bars by calling {@link #hide(int)} or
- * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+ * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
*
* <p>When system bars are hidden in this mode, they can be revealed temporarily with system
* gestures, such as swiping from the edge of the screen where the bar is hidden from. These
* transient system bars will overlay app’s content, may have some degree of transparency, and
* will automatically hide after a short timeout.</p>
+ * @hide
*/
int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2;
- /** Determines the behavior of system bars when hiding them by calling {@link #hide}. */
+ /**
+ * Determines the behavior of system bars when hiding them by calling {@link #hide}.
+ * @hide
+ */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {BEHAVIOR_SHOW_BARS_BY_TOUCH, BEHAVIOR_SHOW_BARS_BY_SWIPE,
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE})
@@ -139,23 +148,27 @@
* the position of the windows in the system causing insets directly.
*
* @param types The {@link InsetsType}s the application has requested to control.
+ * @param durationMillis duration of animation in
+ * {@link java.util.concurrent.TimeUnit#MILLISECONDS}
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* windows are ready to be controlled, among other callbacks.
* @hide
*/
- void controlWindowInsetsAnimation(@InsetsType int types,
+ void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
@NonNull WindowInsetsAnimationControlListener listener);
/**
* Lets the application control the animation for showing the IME in a frame-by-frame manner by
* modifying the position of the IME when it's causing insets.
*
+ * @param durationMillis duration of the animation in
+ * {@link java.util.concurrent.TimeUnit#MILLISECONDS}
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* IME are ready to be controlled, among other callbacks.
*/
- default void controlInputMethodAnimation(
+ default void controlInputMethodAnimation(long durationMillis,
@NonNull WindowInsetsAnimationControlListener listener) {
- controlWindowInsetsAnimation(ime(), listener);
+ controlWindowInsetsAnimation(ime(), durationMillis, listener);
}
/**
@@ -166,7 +179,7 @@
* the event by observing {@link View#onApplyWindowInsets} and checking visibility with
* {@link WindowInsets#isVisible}.
*
- * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
* @see #hideInputMethod()
*/
default void showInputMethod() {
@@ -181,7 +194,7 @@
* the event by observing {@link View#onApplyWindowInsets} and checking visibility with
* {@link WindowInsets#isVisible}.
*
- * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+ * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
* @see #showInputMethod()
*/
default void hideInputMethod() {
@@ -203,4 +216,9 @@
* @see Behavior
*/
void setSystemBarsBehavior(@Behavior int behavior);
+
+ /**
+ * @hide
+ */
+ InsetsState getState();
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d40f832..fc12639 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -17,11 +17,26 @@
package android.view;
import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+import static android.view.WindowInsets.Side.BOTTOM;
+import static android.view.WindowInsets.Side.LEFT;
+import static android.view.WindowInsets.Side.RIGHT;
+import static android.view.WindowInsets.Side.TOP;
+import static android.view.WindowInsets.Type.CAPTION_BAR;
+import static android.view.WindowInsets.Type.IME;
+import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
+import static android.view.WindowInsets.Type.NAVIGATION_BARS;
+import static android.view.WindowInsets.Type.STATUS_BARS;
+import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
+import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
+import static android.view.WindowInsets.Type.WINDOW_DECOR;
import static android.view.WindowLayoutParamsProto.ALPHA;
import static android.view.WindowLayoutParamsProto.APPEARANCE;
import static android.view.WindowLayoutParamsProto.BEHAVIOR;
import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS;
import static android.view.WindowLayoutParamsProto.COLOR_MODE;
+import static android.view.WindowLayoutParamsProto.FIT_IGNORE_VISIBILITY;
+import static android.view.WindowLayoutParamsProto.FIT_INSETS_SIDES;
+import static android.view.WindowLayoutParamsProto.FIT_INSETS_TYPES;
import static android.view.WindowLayoutParamsProto.FLAGS;
import static android.view.WindowLayoutParamsProto.FORMAT;
import static android.view.WindowLayoutParamsProto.GRAVITY;
@@ -29,7 +44,6 @@
import static android.view.WindowLayoutParamsProto.HEIGHT;
import static android.view.WindowLayoutParamsProto.HORIZONTAL_MARGIN;
import static android.view.WindowLayoutParamsProto.INPUT_FEATURE_FLAGS;
-import static android.view.WindowLayoutParamsProto.NEEDS_MENU_KEY;
import static android.view.WindowLayoutParamsProto.PREFERRED_REFRESH_RATE;
import static android.view.WindowLayoutParamsProto.PRIVATE_FLAGS;
import static android.view.WindowLayoutParamsProto.ROTATION_ANIMATION;
@@ -52,9 +66,9 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.KeyguardManager;
import android.app.Presentation;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
@@ -66,6 +80,10 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import android.view.WindowInsets.Side;
+import android.view.WindowInsets.Side.InsetsSide;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.accessibility.AccessibilityNodeInfo;
import java.lang.annotation.Retention;
@@ -1106,6 +1124,13 @@
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
/**
+ * Window type: Window for adding accessibility window magnification above other windows.
+ * This will place the window in the overlay windows.
+ * @hide
+ */
+ public static final int TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 39;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
@@ -1288,14 +1313,11 @@
* set for you by Window as described in {@link Window#setFlags}.*/
public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
- /** Window flag: invert the state of {@link #FLAG_NOT_FOCUSABLE} with
- * respect to how this window interacts with the current method. That
- * is, if FLAG_NOT_FOCUSABLE is set and this flag is set, then the
- * window will behave as if it needs to interact with the input method
- * and thus be placed behind/away from it; if FLAG_NOT_FOCUSABLE is
- * not set and this flag is set, then the window will behave as if it
- * doesn't need to interact with the input method and can be placed
- * to use more space and cover the input method.
+ /** Window flag: When set, input method can't interact with the focusable window
+ * and can be placed to use more space and cover the input method.
+ * Note: When combined with {@link #FLAG_NOT_FOCUSABLE}, this flag has no
+ * effect since input method cannot interact with windows having {@link #FLAG_NOT_FOCUSABLE}
+ * flag set.
*/
public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
@@ -1834,6 +1856,20 @@
public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000;
/**
+ * Flag to indicate that the window is controlling how it fits window insets on its own.
+ * So we don't need to adjust its attributes for fitting window insets.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_FIT_INSETS_CONTROLLED = 0x04000000;
+
+ /**
+ * Flag to indicate that the window only draws the bottom bar background so that we don't
+ * extend it to system bar areas at other sides.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND = 0x08000000;
+
+ /**
* An internal annotation for flags that can be specified to {@link #softInputMode}.
*
* @hide
@@ -1935,54 +1971,20 @@
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
- name = "COLOR_SPACE_AGNOSTIC")
+ name = "COLOR_SPACE_AGNOSTIC"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
+ equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
+ name = "FIT_INSETS_CONTROLLED"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND,
+ equals = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND,
+ name = "ONLY_DRAW_BOTTOM_BAR_BACKGROUND")
})
@TestApi
public int privateFlags;
/**
- * Value for {@link #needsMenuKey} for a window that has not explicitly specified if it
- * needs {@link #NEEDS_MENU_SET_TRUE} or doesn't need {@link #NEEDS_MENU_SET_FALSE} a menu
- * key. For this case, we should look at windows behind it to determine the appropriate
- * value.
- *
- * @hide
- */
- public static final int NEEDS_MENU_UNSET = 0;
-
- /**
- * Value for {@link #needsMenuKey} for a window that has explicitly specified it needs a
- * menu key.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public static final int NEEDS_MENU_SET_TRUE = 1;
-
- /**
- * Value for {@link #needsMenuKey} for a window that has explicitly specified it doesn't
- * needs a menu key.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public static final int NEEDS_MENU_SET_FALSE = 2;
-
- /**
- * State variable for a window belonging to an activity that responds to
- * {@link KeyEvent#KEYCODE_MENU} and therefore needs a Menu key. For devices where Menu is a
- * physical button this variable is ignored, but on devices where the Menu key is drawn in
- * software it may be hidden unless this variable is set to {@link #NEEDS_MENU_SET_TRUE}.
- *
- * (Note that Action Bars, when available, are the preferred way to offer additional
- * functions otherwise accessed via an options menu.)
- *
- * {@hide}
- */
- @UnsupportedAppUsage
- public int needsMenuKey = NEEDS_MENU_UNSET;
-
- /**
* Given a particular set of window manager flags, determine whether
* such a window may be a target for an input method when it has
* focus. In particular, this checks the
@@ -1993,16 +1995,12 @@
*
* @param flags The current window manager flags.
*
- * @return Returns true if such a window should be behind/interact
- * with an input method, false if not.
+ * @return Returns {@code true} if such a window should be behind/interact
+ * with an input method, {@code false} if not.
*/
public static boolean mayUseInputMethod(int flags) {
- switch (flags&(FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)) {
- case 0:
- case FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM:
- return true;
- }
- return false;
+ return (flags & FLAG_NOT_FOCUSABLE) != FLAG_NOT_FOCUSABLE
+ && (flags & FLAG_ALT_FOCUSABLE_IM) != FLAG_ALT_FOCUSABLE_IM;
}
/**
@@ -2627,6 +2625,124 @@
*/
public final InsetsFlags insetsFlags = new InsetsFlags();
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(
+ mask = STATUS_BARS,
+ equals = STATUS_BARS,
+ name = "STATUS_BARS"),
+ @ViewDebug.FlagToString(
+ mask = NAVIGATION_BARS,
+ equals = NAVIGATION_BARS,
+ name = "NAVIGATION_BARS"),
+ @ViewDebug.FlagToString(
+ mask = CAPTION_BAR,
+ equals = CAPTION_BAR,
+ name = "CAPTION_BAR"),
+ @ViewDebug.FlagToString(
+ mask = IME,
+ equals = IME,
+ name = "IME"),
+ @ViewDebug.FlagToString(
+ mask = SYSTEM_GESTURES,
+ equals = SYSTEM_GESTURES,
+ name = "SYSTEM_GESTURES"),
+ @ViewDebug.FlagToString(
+ mask = MANDATORY_SYSTEM_GESTURES,
+ equals = MANDATORY_SYSTEM_GESTURES,
+ name = "MANDATORY_SYSTEM_GESTURES"),
+ @ViewDebug.FlagToString(
+ mask = TAPPABLE_ELEMENT,
+ equals = TAPPABLE_ELEMENT,
+ name = "TAPPABLE_ELEMENT"),
+ @ViewDebug.FlagToString(
+ mask = WINDOW_DECOR,
+ equals = WINDOW_DECOR,
+ name = "WINDOW_DECOR")
+ })
+ private @InsetsType int mFitWindowInsetsTypes = Type.systemBars();
+
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(
+ mask = LEFT,
+ equals = LEFT,
+ name = "LEFT"),
+ @ViewDebug.FlagToString(
+ mask = TOP,
+ equals = TOP,
+ name = "TOP"),
+ @ViewDebug.FlagToString(
+ mask = RIGHT,
+ equals = RIGHT,
+ name = "RIGHT"),
+ @ViewDebug.FlagToString(
+ mask = BOTTOM,
+ equals = BOTTOM,
+ name = "BOTTOM")
+ })
+ private @InsetsSide int mFitWindowInsetsSides = Side.all();
+
+ private boolean mFitIgnoreVisibility = false;
+
+ /**
+ * Specifies types of insets that this window should avoid overlapping during layout.
+ *
+ * @param types which types of insets that this window should avoid. The initial value of
+ * this object includes all system bars.
+ * @hide pending unhide
+ */
+ public void setFitWindowInsetsTypes(@InsetsType int types) {
+ mFitWindowInsetsTypes = types;
+ privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+ }
+
+ /**
+ * Specifies sides of insets that this window should avoid overlapping during layout.
+ *
+ * @param sides which sides that this window should avoid overlapping with the types
+ * specified. The initial value of this object includes all sides.
+ * @hide pending unhide
+ */
+ public void setFitWindowInsetsSides(@InsetsSide int sides) {
+ mFitWindowInsetsSides = sides;
+ privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+ }
+
+ /**
+ * Specifies if this window should fit the window insets no matter they are visible or not.
+ *
+ * @param ignore if true, this window will fit the given types even if they are not visible.
+ * @hide pending unhide
+ */
+ public void setFitIgnoreVisibility(boolean ignore) {
+ mFitIgnoreVisibility = ignore;
+ privateFlags |= PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
+ }
+
+ /**
+ * @return the insets types that this window is avoiding overlapping.
+ * @hide pending unhide
+ */
+ public @InsetsType int getFitWindowInsetsTypes() {
+ return mFitWindowInsetsTypes;
+ }
+
+ /**
+ * @return the sides that this window is avoiding overlapping.
+ * @hide pending unhide
+ */
+ public @InsetsSide int getFitWindowInsetsSides() {
+ return mFitWindowInsetsSides;
+ }
+
+ /**
+ * @return {@code true} if this window fits the window insets no matter they are visible or
+ * not.
+ * @hide pending unhide
+ */
+ public boolean getFitIgnoreVisibility() {
+ return mFitIgnoreVisibility;
+ }
+
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
@@ -2784,13 +2900,15 @@
out.writeInt(surfaceInsets.bottom);
out.writeInt(hasManualSurfaceInsets ? 1 : 0);
out.writeInt(preservePreviousSurfaceInsets ? 1 : 0);
- out.writeInt(needsMenuKey);
out.writeLong(accessibilityIdOfAnchor);
TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
out.writeInt(mColorMode);
out.writeLong(hideTimeoutMilliseconds);
out.writeInt(insetsFlags.appearance);
out.writeInt(insetsFlags.behavior);
+ out.writeInt(mFitWindowInsetsTypes);
+ out.writeInt(mFitWindowInsetsSides);
+ out.writeBoolean(mFitIgnoreVisibility);
}
public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -2842,13 +2960,15 @@
surfaceInsets.bottom = in.readInt();
hasManualSurfaceInsets = in.readInt() != 0;
preservePreviousSurfaceInsets = in.readInt() != 0;
- needsMenuKey = in.readInt();
accessibilityIdOfAnchor = in.readLong();
accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mColorMode = in.readInt();
hideTimeoutMilliseconds = in.readLong();
insetsFlags.appearance = in.readInt();
insetsFlags.behavior = in.readInt();
+ mFitWindowInsetsTypes = in.readInt();
+ mFitWindowInsetsSides = in.readInt();
+ mFitIgnoreVisibility = in.readBoolean();
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -2884,8 +3004,6 @@
/** {@hide} */
public static final int PREFERRED_REFRESH_RATE_CHANGED = 1 << 21;
/** {@hide} */
- public static final int NEEDS_MENU_KEY_CHANGED = 1 << 22;
- /** {@hide} */
public static final int PREFERRED_DISPLAY_MODE_ID = 1 << 23;
/** {@hide} */
public static final int ACCESSIBILITY_ANCHOR_CHANGED = 1 << 24;
@@ -3059,11 +3177,6 @@
changes |= SURFACE_INSETS_CHANGED;
}
- if (needsMenuKey != o.needsMenuKey) {
- needsMenuKey = o.needsMenuKey;
- changes |= NEEDS_MENU_KEY_CHANGED;
- }
-
if (accessibilityIdOfAnchor != o.accessibilityIdOfAnchor) {
accessibilityIdOfAnchor = o.accessibilityIdOfAnchor;
changes |= ACCESSIBILITY_ANCHOR_CHANGED;
@@ -3094,6 +3207,21 @@
changes |= INSET_FLAGS_CHANGED;
}
+ if (mFitWindowInsetsTypes != o.mFitWindowInsetsTypes) {
+ mFitWindowInsetsTypes = o.mFitWindowInsetsTypes;
+ changes |= LAYOUT_CHANGED;
+ }
+
+ if (mFitWindowInsetsSides != o.mFitWindowInsetsSides) {
+ mFitWindowInsetsSides = o.mFitWindowInsetsSides;
+ changes |= LAYOUT_CHANGED;
+ }
+
+ if (mFitIgnoreVisibility != o.mFitIgnoreVisibility) {
+ mFitIgnoreVisibility = o.mFitIgnoreVisibility;
+ changes |= LAYOUT_CHANGED;
+ }
+
return changes;
}
@@ -3217,9 +3345,6 @@
sb.append(" (!preservePreviousSurfaceInsets)");
}
}
- if (needsMenuKey == NEEDS_MENU_SET_TRUE) {
- sb.append(" needsMenuKey");
- }
if (mColorMode != COLOR_MODE_DEFAULT) {
sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
}
@@ -3251,6 +3376,20 @@
sb.append(prefix).append(" bhv=").append(ViewDebug.flagsToString(
InsetsFlags.class, "behavior", insetsFlags.behavior));
}
+ if (mFitWindowInsetsTypes != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" fitTypes=").append(ViewDebug.flagsToString(
+ LayoutParams.class, "mFitWindowInsetsTypes", mFitWindowInsetsTypes));
+ }
+ if (mFitWindowInsetsSides != Side.all()) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" fitSides=").append(ViewDebug.flagsToString(
+ LayoutParams.class, "mFitWindowInsetsSides", mFitWindowInsetsSides));
+ }
+ if (mFitIgnoreVisibility) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" fitIgnoreVis");
+ }
sb.append('}');
return sb.toString();
@@ -3281,7 +3420,6 @@
proto.write(HAS_SYSTEM_UI_LISTENERS, hasSystemUiListeners);
proto.write(INPUT_FEATURE_FLAGS, inputFeatures);
proto.write(USER_ACTIVITY_TIMEOUT, userActivityTimeout);
- proto.write(NEEDS_MENU_KEY, needsMenuKey);
proto.write(COLOR_MODE, mColorMode);
proto.write(FLAGS, flags);
proto.write(PRIVATE_FLAGS, privateFlags);
@@ -3289,6 +3427,9 @@
proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility);
proto.write(APPEARANCE, insetsFlags.appearance);
proto.write(BEHAVIOR, insetsFlags.behavior);
+ proto.write(FIT_INSETS_TYPES, mFitWindowInsetsTypes);
+ proto.write(FIT_INSETS_SIDES, mFitWindowInsetsSides);
+ proto.write(FIT_IGNORE_VISIBILITY, mFitIgnoreVisibility);
proto.end(token);
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 55b2a2a..9578002 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -18,8 +18,8 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.pm.ApplicationInfo;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index c349443..cdeeaa4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -17,7 +17,7 @@
package android.view;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Region;
import android.os.Bundle;
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index dc8bf9b..9ab2c2b 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -69,6 +69,8 @@
private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ private int mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+
private boolean mIsAllWindowsCached;
// The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
@@ -164,16 +166,19 @@
switch (eventType) {
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
- refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
}
mAccessibilityFocus = event.getSourceNodeId();
- refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ mAccessibilityFocusedWindow = event.getWindowId();
+ refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
} break;
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
- if (mAccessibilityFocus == event.getSourceNodeId()) {
- refreshCachedNodeLocked(event.getWindowId(), mAccessibilityFocus);
+ if (mAccessibilityFocus == event.getSourceNodeId()
+ && mAccessibilityFocusedWindow == event.getWindowId()) {
+ refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus);
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
} break;
@@ -210,6 +215,13 @@
} break;
case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+ if (event.getWindowChanges()
+ == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
+ // Don't need to clear all cache. Unless the changes are related to
+ // content, we won't clear all cache here.
+ refreshCachedWindowLocked(event.getWindowId());
+ break;
+ }
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
clear();
} break;
@@ -243,6 +255,34 @@
clearSubTreeLocked(windowId, sourceId);
}
+ private void refreshCachedWindowLocked(int windowId) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Refreshing cached window.");
+ }
+
+ if (windowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+ return;
+ }
+
+ final int displayCounts = mWindowCacheByDisplay.size();
+ for (int i = 0; i < displayCounts; i++) {
+ final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+ mWindowCacheByDisplay.valueAt(i);
+ if (windowsOfDisplay == null) {
+ continue;
+ }
+ final AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
+ if (window == null) {
+ continue;
+ }
+ if (!mAccessibilityNodeRefresher.refreshWindow(window)) {
+ // If we fail to refresh the window, clear all windows.
+ clearWindowCacheLocked();
+ }
+ return;
+ }
+ }
+
/**
* Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
* window and the accessibility id of the node.
@@ -413,8 +453,10 @@
refreshCachedNodeLocked(windowId, mAccessibilityFocus);
}
mAccessibilityFocus = sourceId;
+ mAccessibilityFocusedWindow = windowId;
} else if (mAccessibilityFocus == sourceId) {
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+ mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
if (clone.isFocused()) {
mInputFocus = sourceId;
@@ -439,6 +481,8 @@
mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
+
+ mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
}
@@ -653,8 +697,14 @@
// Layer of indirection included to break dependency chain for testing
public static class AccessibilityNodeRefresher {
+ /** Refresh the given AccessibilityNodeInfo object. */
public boolean refreshNode(AccessibilityNodeInfo info, boolean bypassCache) {
return info.refresh(null, bypassCache);
}
+
+ /** Refresh the given AccessibilityWindowInfo object. */
+ public boolean refreshWindow(AccessibilityWindowInfo info) {
+ return info.refresh();
+ }
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 34654ed..3b83683 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
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/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index bb10ef1..914ff18 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,7 +17,7 @@
package android.view.accessibility;
import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -223,19 +223,36 @@
* @return The {@link AccessibilityWindowInfo}.
*/
public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) {
+ return getWindow(connectionId, accessibilityWindowId, /* bypassCache */ false);
+ }
+
+ /**
+ * Gets the info for a window.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param bypassCache Whether to bypass the cache.
+ * @return The {@link AccessibilityWindowInfo}.
+ */
+ public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId,
+ boolean bypassCache) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
- AccessibilityWindowInfo window = sAccessibilityCache.getWindow(
- accessibilityWindowId);
- if (window != null) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Window cache hit");
+ AccessibilityWindowInfo window;
+ if (!bypassCache) {
+ window = sAccessibilityCache.getWindow(accessibilityWindowId);
+ if (window != null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Window cache hit");
+ }
+ return window;
}
- return window;
- }
- if (DEBUG) {
- Log.i(LOG_TAG, "Window cache miss");
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Window cache miss");
+ }
}
final long identityToken = Binder.clearCallingIdentity();
try {
@@ -244,7 +261,9 @@
Binder.restoreCallingIdentity(identityToken);
}
if (window != null) {
- sAccessibilityCache.addWindow(window);
+ if (!bypassCache) {
+ sAccessibilityCache.addWindow(window);
+ }
return window;
}
} else {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index cc28840..ff31bcc 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -30,8 +30,8 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -117,7 +117,7 @@
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
* <p>
- * Input: Nothing.
+ * Input: {@link #EXTRA_SHORTCUT_TYPE} is the shortcut type.
* </p>
* <p>
* Output: Nothing.
@@ -130,6 +130,42 @@
"com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
/**
+ * Used as an int extra field in {@link #ACTION_CHOOSE_ACCESSIBILITY_BUTTON} intent to specify
+ * the shortcut type.
+ *
+ * @hide
+ */
+ public static final String EXTRA_SHORTCUT_TYPE =
+ "com.android.internal.intent.extra.SHORTCUT_TYPE";
+
+ /**
+ * Used as an int value for {@link #EXTRA_SHORTCUT_TYPE} to represent the accessibility button
+ * shortcut type.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_BUTTON = 0;
+
+ /**
+ * Used as an int value for {@link #EXTRA_SHORTCUT_TYPE} to represent hardware key shortcut,
+ * such as volume key button.
+ *
+ * @hide
+ */
+ public static final int ACCESSIBILITY_SHORTCUT_KEY = 1;
+
+ /**
+ * Annotations for the shortcut type.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ ACCESSIBILITY_BUTTON,
+ ACCESSIBILITY_SHORTCUT_KEY
+ })
+ public @interface ShortcutType {}
+
+ /**
* Annotations for content flag of UI.
* @hide
*/
@@ -1242,27 +1278,28 @@
}
/**
- * Get the component name of the service currently assigned to the accessibility shortcut.
+ * Returns the list of shortcut target names currently assigned to the given shortcut.
*
- * @return The flattened component name
+ * @param shortcutType The shortcut type.
+ * @return The list of shortcut target names.
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
- @Nullable
- public String getAccessibilityShortcutService() {
+ @NonNull
+ public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
}
if (service != null) {
try {
- return service.getAccessibilityShortcutService();
+ return service.getAccessibilityShortcutTargets(shortcutType);
} catch (RemoteException re) {
re.rethrowFromSystemServer();
}
}
- return null;
+ return Collections.emptyList();
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index f2f84cd..92aa7d5 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -26,7 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Build;
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 4f6c9ef..c3a4d32 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcelable;
import android.view.View;
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 2cc6e9a..ca5c417 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -87,6 +87,8 @@
/** @hide */
public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE;
/** @hide */
+ public static final int UNDEFINED_CONNECTION_ID = -1;
+ /** @hide */
public static final int UNDEFINED_WINDOW_ID = -1;
/** @hide */
public static final int ANY_WINDOW_ID = -2;
@@ -117,7 +119,7 @@
private CharSequence mTitle;
private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
- private int mConnectionId = UNDEFINED_WINDOW_ID;
+ private int mConnectionId = UNDEFINED_CONNECTION_ID;
/**
* Creates a new {@link AccessibilityWindowInfo}.
@@ -539,6 +541,30 @@
}
}
+ /**
+ * Refreshes this window with the latest state of the window it represents.
+ * <p>
+ * <strong>Note:</strong> If this method returns false this info is obsolete
+ * since it represents a window that is no longer exist.
+ * </p>
+ *
+ * @hide
+ */
+ public boolean refresh() {
+ if (mConnectionId == UNDEFINED_CONNECTION_ID || mId == UNDEFINED_WINDOW_ID) {
+ return false;
+ }
+ final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ final AccessibilityWindowInfo refreshedInfo = client.getWindow(mConnectionId,
+ mId, /* bypassCache */true);
+ if (refreshedInfo == null) {
+ return false;
+ }
+ init(refreshedInfo);
+ refreshedInfo.recycle();
+ return true;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -586,6 +612,7 @@
mTitle = other.mTitle;
mAnchorId = other.mAnchorId;
+ if (mChildIds != null) mChildIds.clear();
if (other.mChildIds != null && other.mChildIds.size() > 0) {
if (mChildIds == null) {
mChildIds = other.mChildIds.clone();
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index c42e9fe..3d68692 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 023fda5..36515b3 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -73,7 +73,7 @@
void performAccessibilityShortcut();
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
- String getAccessibilityShortcutService();
+ List<String> getAccessibilityShortcutTargets(int shortcutType);
// System process only
boolean sendFingerprintGesture(int gestureKeyCode);
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index b03732a..b1d618e 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -19,7 +19,7 @@
import android.annotation.AnimRes;
import android.annotation.ColorInt;
import android.annotation.InterpolatorRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.RectF;
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index f5b0746..7ce0f45 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -19,7 +19,7 @@
import android.annotation.AnimRes;
import android.annotation.InterpolatorRes;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index 58da04d..cfc6e39 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -17,7 +17,7 @@
package android.view.animation;
import android.annotation.FloatRange;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Matrix;
import android.graphics.Rect;
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
index 6c040d4..ec55a02 100644
--- a/core/java/android/view/animation/TranslateAnimation.java
+++ b/core/java/android/view/animation/TranslateAnimation.java
@@ -16,7 +16,7 @@
package android.view.animation;
-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/android/view/animation/TranslateYAnimation.java b/core/java/android/view/animation/TranslateYAnimation.java
index a6e0ccb..1a1dfbf 100644
--- a/core/java/android/view/animation/TranslateYAnimation.java
+++ b/core/java/android/view/animation/TranslateYAnimation.java
@@ -16,7 +16,7 @@
package android.view.animation;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Matrix;
/**
diff --git a/core/java/android/view/inline/InlineContentView.java b/core/java/android/view/inline/InlineContentView.java
index 4bc2176..b143fad 100644
--- a/core/java/android/view/inline/InlineContentView.java
+++ b/core/java/android/view/inline/InlineContentView.java
@@ -50,7 +50,10 @@
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
- // TODO(b/137800469): implement this.
+ new SurfaceControl.Transaction()
+ .setVisibility(surfaceControl, false)
+ .reparent(surfaceControl, null)
+ .apply();
}
});
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 25e34d3..c10144e 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -271,7 +271,7 @@
};
@DataClass.Generated(
- time = 1574446220398L,
+ time = 1575933636929L,
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)")
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 112653a..1e5a3b0 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -21,11 +21,16 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.util.Log;
+import android.view.autofill.AutofillId;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
/**
* The InputMethod interface represents an input method which can generate key
@@ -104,6 +109,20 @@
}
/**
+ * Called to notify the IME that Autofill Frameworks requested an inline suggestions request.
+ *
+ * @hide
+ */
+ default void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("InputMethod", "RemoteException calling onInlineSuggestionsUnsupported: " + e);
+ }
+ }
+
+ /**
* Called first thing after an input method is created, this supplies a
* unique token for the session it has with the system service. It is
* needed to identify itself with the service to validate its operations.
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index fe07fee..34005ac 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -17,7 +17,7 @@
package android.view.inputmethod;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2650a67..67ce8d2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -26,9 +26,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -2863,7 +2863,7 @@
}
/**
- * This is kept due to {@link android.annotation.UnsupportedAppUsage}.
+ * This is kept due to {@link android.compat.annotation.UnsupportedAppUsage}.
*
* <p>TODO(Bug 113914148): Check if we can remove this. We have accidentally exposed
* WindowManagerInternal#getInputMethodWindowVisibleHeight to app developers and some of them
diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
index 8dd0dcd..50e95c8 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -16,7 +16,7 @@
package android.view.inputmethod;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.util.Slog;
diff --git a/core/java/android/view/textclassifier/ConfigParser.java b/core/java/android/view/textclassifier/ConfigParser.java
deleted file mode 100644
index 9b51458..0000000
--- a/core/java/android/view/textclassifier/ConfigParser.java
+++ /dev/null
@@ -1,264 +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 android.view.textclassifier;
-
-import android.annotation.Nullable;
-import android.provider.DeviceConfig;
-import android.util.ArrayMap;
-import android.util.KeyValueListParser;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.Preconditions;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Retrieves settings from {@link DeviceConfig} and {@link android.provider.Settings}.
- * It will try DeviceConfig first and then Settings.
- *
- * @hide
- */
-@VisibleForTesting(visibility = Visibility.PACKAGE)
-public final class ConfigParser {
- private static final String TAG = "ConfigParser";
-
- public static final boolean ENABLE_DEVICE_CONFIG = true;
-
- private static final String STRING_LIST_DELIMITER = ":";
-
- private final Supplier<String> mLegacySettingsSupplier;
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private final Map<String, Object> mCache = new ArrayMap<>();
- @GuardedBy("mLock")
- private @Nullable KeyValueListParser mSettingsParser; // Call getLegacySettings() instead.
-
- public ConfigParser(Supplier<String> legacySettingsSupplier) {
- mLegacySettingsSupplier = Preconditions.checkNotNull(legacySettingsSupplier);
- }
-
- private KeyValueListParser getLegacySettings() {
- synchronized (mLock) {
- if (mSettingsParser == null) {
- final String legacySettings = mLegacySettingsSupplier.get();
- try {
- mSettingsParser = new KeyValueListParser(',');
- mSettingsParser.setString(legacySettings);
- } catch (IllegalArgumentException e) {
- // Failed to parse the settings string, log this and move on with defaults.
- Log.w(TAG, "Bad text_classifier_constants: " + legacySettings);
- }
- }
- return mSettingsParser;
- }
- }
-
- /**
- * Reads a boolean setting through the cache.
- */
- public boolean getBoolean(String key, boolean defaultValue) {
- synchronized (mLock) {
- final Object cached = mCache.get(key);
- if (cached instanceof Boolean) {
- return (boolean) cached;
- }
- final boolean value;
- if (ENABLE_DEVICE_CONFIG) {
- value = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- key,
- getLegacySettings().getBoolean(key, defaultValue));
- } else {
- value = getLegacySettings().getBoolean(key, defaultValue);
- }
- mCache.put(key, value);
- return value;
- }
- }
-
- /**
- * Reads an integer setting through the cache.
- */
- public int getInt(String key, int defaultValue) {
- synchronized (mLock) {
- final Object cached = mCache.get(key);
- if (cached instanceof Integer) {
- return (int) cached;
- }
- final int value;
- if (ENABLE_DEVICE_CONFIG) {
- value = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- key,
- getLegacySettings().getInt(key, defaultValue));
- } else {
- value = getLegacySettings().getInt(key, defaultValue);
- }
- mCache.put(key, value);
- return value;
- }
- }
-
- /**
- * Reads a float setting through the cache.
- */
- public float getFloat(String key, float defaultValue) {
- synchronized (mLock) {
- final Object cached = mCache.get(key);
- if (cached instanceof Float) {
- return (float) cached;
- }
- final float value;
- if (ENABLE_DEVICE_CONFIG) {
- value = DeviceConfig.getFloat(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- key,
- getLegacySettings().getFloat(key, defaultValue));
- } else {
- value = getLegacySettings().getFloat(key, defaultValue);
- }
- mCache.put(key, value);
- return value;
- }
- }
-
- /**
- * Reads a string setting through the cache.
- */
- public String getString(String key, String defaultValue) {
- synchronized (mLock) {
- final Object cached = mCache.get(key);
- if (cached instanceof String) {
- return (String) cached;
- }
- final String value;
- if (ENABLE_DEVICE_CONFIG) {
- value = DeviceConfig.getString(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- key,
- getLegacySettings().getString(key, defaultValue));
- } else {
- value = getLegacySettings().getString(key, defaultValue);
- }
- mCache.put(key, value);
- return value;
- }
- }
-
- /**
- * Reads a string list setting through the cache.
- */
- public List<String> getStringList(String key, List<String> defaultValue) {
- synchronized (mLock) {
- final Object cached = mCache.get(key);
- if (cached instanceof List) {
- final List asList = (List) cached;
- if (asList.isEmpty()) {
- return Collections.emptyList();
- } else if (asList.get(0) instanceof String) {
- return (List<String>) cached;
- }
- }
- final List<String> value;
- if (ENABLE_DEVICE_CONFIG) {
- value = getDeviceConfigStringList(
- key,
- getSettingsStringList(key, defaultValue));
- } else {
- value = getSettingsStringList(key, defaultValue);
- }
- mCache.put(key, value);
- return value;
- }
- }
-
- /**
- * Reads a float array through the cache. The returned array should be expected to be of the
- * same length as that of the defaultValue.
- */
- public float[] getFloatArray(String key, float[] defaultValue) {
- synchronized (mLock) {
- final Object cached = mCache.get(key);
- if (cached instanceof float[]) {
- return (float[]) cached;
- }
- final float[] value;
- if (ENABLE_DEVICE_CONFIG) {
- value = getDeviceConfigFloatArray(
- key,
- getSettingsFloatArray(key, defaultValue));
- } else {
- value = getSettingsFloatArray(key, defaultValue);
- }
- mCache.put(key, value);
- return value;
- }
- }
-
- private List<String> getSettingsStringList(String key, List<String> defaultValue) {
- return parse(mSettingsParser.getString(key, null), defaultValue);
- }
-
- private static List<String> getDeviceConfigStringList(String key, List<String> defaultValue) {
- return parse(
- DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null),
- defaultValue);
- }
-
- private static float[] getDeviceConfigFloatArray(String key, float[] defaultValue) {
- return parse(
- DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null),
- defaultValue);
- }
-
- private float[] getSettingsFloatArray(String key, float[] defaultValue) {
- return parse(mSettingsParser.getString(key, null), defaultValue);
- }
-
- private static List<String> parse(@Nullable String listStr, List<String> defaultValue) {
- if (listStr != null) {
- return Collections.unmodifiableList(
- Arrays.asList(listStr.split(STRING_LIST_DELIMITER)));
- }
- return defaultValue;
- }
-
- private static float[] parse(@Nullable String arrayStr, float[] defaultValue) {
- if (arrayStr != null) {
- final String[] split = arrayStr.split(STRING_LIST_DELIMITER);
- if (split.length != defaultValue.length) {
- return defaultValue;
- }
- final float[] result = new float[split.length];
- for (int i = 0; i < split.length; i++) {
- try {
- result[i] = Float.parseFloat(split[i]);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
- return result;
- } else {
- return defaultValue;
- }
- }
-}
diff --git a/core/java/android/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java
index 6070b53..e633404 100644
--- a/core/java/android/view/textclassifier/ConversationAction.java
+++ b/core/java/android/view/textclassifier/ConversationAction.java
@@ -26,9 +26,8 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
+import java.util.Objects;
/** Represents the action suggested by a {@link TextClassifier} on a given conversation. */
public final class ConversationAction implements Parcelable {
@@ -133,11 +132,11 @@
@Nullable CharSequence textReply,
float score,
@NonNull Bundle extras) {
- mType = Preconditions.checkNotNull(type);
+ mType = Objects.requireNonNull(type);
mAction = action;
mTextReply = textReply;
mScore = score;
- mExtras = Preconditions.checkNotNull(extras);
+ mExtras = Objects.requireNonNull(extras);
}
private ConversationAction(Parcel in) {
@@ -221,7 +220,7 @@
private Bundle mExtras;
public Builder(@NonNull @ActionType String actionType) {
- mType = Preconditions.checkNotNull(actionType);
+ mType = Objects.requireNonNull(actionType);
}
/**
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index f7f503a..80027b1e 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -38,6 +38,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Represents a list of actions suggested by a {@link TextClassifier} on a given conversation.
@@ -66,7 +67,7 @@
public ConversationActions(
@NonNull List<ConversationAction> conversationActions, @Nullable String id) {
mConversationActions =
- Collections.unmodifiableList(Preconditions.checkNotNull(conversationActions));
+ Collections.unmodifiableList(Objects.requireNonNull(conversationActions));
mId = id;
}
@@ -149,7 +150,7 @@
mAuthor = author;
mReferenceTime = referenceTime;
mText = text;
- mExtras = Preconditions.checkNotNull(bundle);
+ mExtras = Objects.requireNonNull(bundle);
}
private Message(Parcel in) {
@@ -243,7 +244,7 @@
* {@link #PERSON_USER_OTHERS} to represent a remote user.
*/
public Builder(@NonNull Person author) {
- mAuthor = Preconditions.checkNotNull(author);
+ mAuthor = Objects.requireNonNull(author);
}
/** Sets the text of this message. */
@@ -329,8 +330,8 @@
int maxSuggestions,
@Nullable @Hint List<String> hints,
@NonNull Bundle extras) {
- mConversation = Preconditions.checkNotNull(conversation);
- mTypeConfig = Preconditions.checkNotNull(typeConfig);
+ mConversation = Objects.requireNonNull(conversation);
+ mTypeConfig = Objects.requireNonNull(typeConfig);
mMaxSuggestions = maxSuggestions;
mHints = hints;
mExtras = extras;
@@ -483,7 +484,7 @@
* actions for.
*/
public Builder(@NonNull List<Message> conversation) {
- mConversation = Preconditions.checkNotNull(conversation);
+ mConversation = Objects.requireNonNull(conversation);
}
/**
diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java
index 3c51c38..4c12dda 100644
--- a/core/java/android/view/textclassifier/EntityConfidence.java
+++ b/core/java/android/view/textclassifier/EntityConfidence.java
@@ -22,12 +22,11 @@
import android.os.Parcelable;
import android.util.ArrayMap;
-import com.android.internal.util.Preconditions;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Helper object for setting and getting entity scores for classified text.
@@ -42,7 +41,7 @@
EntityConfidence() {}
EntityConfidence(@NonNull EntityConfidence source) {
- Preconditions.checkNotNull(source);
+ Objects.requireNonNull(source);
mEntityConfidence.putAll(source.mEntityConfidence);
mSortedEntities.addAll(source.mSortedEntities);
}
@@ -56,7 +55,7 @@
* 1 (high confidence).
*/
EntityConfidence(@NonNull Map<String, Float> source) {
- Preconditions.checkNotNull(source);
+ Objects.requireNonNull(source);
// Prune non-existent entities and clamp to 1.
mEntityConfidence.ensureCapacity(source.size());
diff --git a/core/java/android/view/textclassifier/GenerateLinksLogger.java b/core/java/android/view/textclassifier/GenerateLinksLogger.java
index 3996b27..17ec73a 100644
--- a/core/java/android/view/textclassifier/GenerateLinksLogger.java
+++ b/core/java/android/view/textclassifier/GenerateLinksLogger.java
@@ -23,7 +23,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
import java.util.Locale;
import java.util.Map;
@@ -66,9 +65,9 @@
/** Logs statistics about a call to generateLinks. */
public void logGenerateLinks(CharSequence text, TextLinks links, String callingPackageName,
long latencyMs) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(links);
- Preconditions.checkNotNull(callingPackageName);
+ Objects.requireNonNull(text);
+ Objects.requireNonNull(links);
+ Objects.requireNonNull(callingPackageName);
if (!shouldLog()) {
return;
}
diff --git a/core/java/android/view/textclassifier/ModelFileManager.java b/core/java/android/view/textclassifier/ModelFileManager.java
index e04285d..0a4ff5d 100644
--- a/core/java/android/view/textclassifier/ModelFileManager.java
+++ b/core/java/android/view/textclassifier/ModelFileManager.java
@@ -23,7 +23,6 @@
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
@@ -51,7 +50,7 @@
private List<ModelFile> mModelFiles;
public ModelFileManager(Supplier<List<ModelFile>> modelFileSupplier) {
- mModelFileSupplier = Preconditions.checkNotNull(modelFileSupplier);
+ mModelFileSupplier = Objects.requireNonNull(modelFileSupplier);
}
/**
@@ -106,12 +105,12 @@
File updatedModelFile,
Function<Integer, Integer> versionSupplier,
Function<Integer, String> supportedLocalesSupplier) {
- mUpdatedModelFile = Preconditions.checkNotNull(updatedModelFile);
- mFactoryModelDir = Preconditions.checkNotNull(factoryModelDir);
+ mUpdatedModelFile = Objects.requireNonNull(updatedModelFile);
+ mFactoryModelDir = Objects.requireNonNull(factoryModelDir);
mModelFilenamePattern = Pattern.compile(
- Preconditions.checkNotNull(factoryModelFileNameRegex));
- mVersionSupplier = Preconditions.checkNotNull(versionSupplier);
- mSupportedLocalesSupplier = Preconditions.checkNotNull(supportedLocalesSupplier);
+ Objects.requireNonNull(factoryModelFileNameRegex));
+ mVersionSupplier = Objects.requireNonNull(versionSupplier);
+ mSupportedLocalesSupplier = Objects.requireNonNull(supportedLocalesSupplier);
}
@Override
@@ -208,10 +207,10 @@
public ModelFile(File file, int version, List<Locale> supportedLocales,
String supportedLocalesStr,
boolean languageIndependent) {
- mFile = Preconditions.checkNotNull(file);
+ mFile = Objects.requireNonNull(file);
mVersion = version;
- mSupportedLocales = Preconditions.checkNotNull(supportedLocales);
- mSupportedLocalesStr = Preconditions.checkNotNull(supportedLocalesStr);
+ mSupportedLocales = Objects.requireNonNull(supportedLocales);
+ mSupportedLocalesStr = Objects.requireNonNull(supportedLocalesStr);
mLanguageIndependent = languageIndependent;
}
@@ -232,7 +231,7 @@
/** Returns whether the language supports any language in the given ranges. */
public boolean isAnyLanguageSupported(List<Locale.LanguageRange> languageRanges) {
- Preconditions.checkNotNull(languageRanges);
+ Objects.requireNonNull(languageRanges);
return mLanguageIndependent || Locale.lookup(languageRanges, mSupportedLocales) != null;
}
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 12ed4b9..09cb7a0 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -149,7 +149,7 @@
mAbsoluteStart = start;
mAbsoluteEnd = end;
mEventType = eventType;
- mEntityType = Preconditions.checkNotNull(entityType);
+ mEntityType = Objects.requireNonNull(entityType);
mResultId = resultId;
mInvocationMethod = invocationMethod;
}
@@ -161,7 +161,6 @@
mEntityType = in.readString();
mWidgetVersion = in.readInt() > 0 ? in.readString() : null;
mPackageName = in.readString();
- mUserId = in.readInt();
mWidgetType = in.readString();
mInvocationMethod = in.readInt();
mResultId = in.readString();
@@ -175,6 +174,7 @@
mEnd = in.readInt();
mSmartStart = in.readInt();
mSmartEnd = in.readInt();
+ mUserId = in.readInt();
}
@Override
@@ -188,7 +188,6 @@
dest.writeString(mWidgetVersion);
}
dest.writeString(mPackageName);
- dest.writeInt(mUserId);
dest.writeString(mWidgetType);
dest.writeInt(mInvocationMethod);
dest.writeString(mResultId);
@@ -204,6 +203,7 @@
dest.writeInt(mEnd);
dest.writeInt(mSmartStart);
dest.writeInt(mSmartEnd);
+ dest.writeInt(mUserId);
}
@Override
@@ -257,7 +257,7 @@
public static SelectionEvent createSelectionModifiedEvent(
int start, int end, @NonNull TextClassification classification) {
Preconditions.checkArgument(end >= start, "end cannot be less than start");
- Preconditions.checkNotNull(classification);
+ Objects.requireNonNull(classification);
final String entityType = classification.getEntityCount() > 0
? classification.getEntity(0)
: TextClassifier.TYPE_UNKNOWN;
@@ -281,7 +281,7 @@
public static SelectionEvent createSelectionModifiedEvent(
int start, int end, @NonNull TextSelection selection) {
Preconditions.checkArgument(end >= start, "end cannot be less than start");
- Preconditions.checkNotNull(selection);
+ Objects.requireNonNull(selection);
final String entityType = selection.getEntityCount() > 0
? selection.getEntity(0)
: TextClassifier.TYPE_UNKNOWN;
@@ -329,7 +329,7 @@
int start, int end, @SelectionEvent.ActionType int actionType,
@NonNull TextClassification classification) {
Preconditions.checkArgument(end >= start, "end cannot be less than start");
- Preconditions.checkNotNull(classification);
+ Objects.requireNonNull(classification);
checkActionType(actionType);
final String entityType = classification.getEntityCount() > 0
? classification.getEntity(0)
@@ -398,7 +398,7 @@
}
void setEntityType(@EntityType String entityType) {
- mEntityType = Preconditions.checkNotNull(entityType);
+ mEntityType = Objects.requireNonNull(entityType);
}
/**
diff --git a/core/java/android/view/textclassifier/SelectionSessionLogger.java b/core/java/android/view/textclassifier/SelectionSessionLogger.java
index 20cc944..9c9b2d0 100644
--- a/core/java/android/view/textclassifier/SelectionSessionLogger.java
+++ b/core/java/android/view/textclassifier/SelectionSessionLogger.java
@@ -24,7 +24,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
import java.text.BreakIterator;
import java.util.List;
@@ -65,12 +64,12 @@
@VisibleForTesting
public SelectionSessionLogger(@NonNull MetricsLogger metricsLogger) {
- mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
+ mMetricsLogger = Objects.requireNonNull(metricsLogger);
}
/** Emits a selection event to the logs. */
public void writeEvent(@NonNull SelectionEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
.setType(getLogType(event))
.setSubtype(getLogSubType(event))
@@ -240,7 +239,7 @@
* Returns a token iterator for tokenizing text for logging purposes.
*/
public static BreakIterator getTokenIterator(@NonNull Locale locale) {
- return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale));
+ return BreakIterator.getWordInstance(Objects.requireNonNull(locale));
}
/**
@@ -249,9 +248,9 @@
public static String createId(
String text, int start, int end, Context context, int modelVersion,
List<Locale> locales) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(context);
- Preconditions.checkNotNull(locales);
+ Objects.requireNonNull(text);
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(locales);
final StringJoiner localesJoiner = new StringJoiner(",");
for (Locale locale : locales) {
localesJoiner.add(locale.toLanguageTag());
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 66f7504..138d25d 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -33,8 +33,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -61,10 +61,10 @@
throws ServiceManager.ServiceNotFoundException {
mManagerService = ITextClassifierService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
- mSettings = Preconditions.checkNotNull(settings);
+ mSettings = Objects.requireNonNull(settings);
mFallback = context.getSystemService(TextClassificationManager.class)
.getTextClassifier(TextClassifier.LOCAL);
- mPackageName = Preconditions.checkNotNull(context.getOpPackageName());
+ mPackageName = Objects.requireNonNull(context.getOpPackageName());
mUserId = context.getUserId();
}
@@ -74,7 +74,7 @@
@Override
@WorkerThread
public TextSelection suggestSelection(TextSelection.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
request.setCallingPackageName(mPackageName);
@@ -98,7 +98,7 @@
@Override
@WorkerThread
public TextClassification classifyText(TextClassification.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
request.setCallingPackageName(mPackageName);
@@ -122,7 +122,7 @@
@Override
@WorkerThread
public TextLinks generateLinks(@NonNull TextLinks.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
if (!mSettings.isSmartLinkifyEnabled() && request.isLegacyFallback()) {
@@ -147,7 +147,7 @@
@Override
public void onSelectionEvent(SelectionEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
Utils.checkMainThread();
try {
@@ -160,7 +160,7 @@
@Override
public void onTextClassifierEvent(@NonNull TextClassifierEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
Utils.checkMainThread();
try {
@@ -178,7 +178,7 @@
@Override
public TextLanguage detectLanguage(TextLanguage.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
@@ -199,7 +199,7 @@
@Override
public ConversationActions suggestConversationActions(ConversationActions.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
@@ -260,7 +260,7 @@
void initializeRemoteSession(
@NonNull TextClassificationContext classificationContext,
@NonNull TextClassificationSessionId sessionId) {
- mSessionId = Preconditions.checkNotNull(sessionId);
+ mSessionId = Objects.requireNonNull(sessionId);
try {
classificationContext.setUserId(mUserId);
mManagerService.onCreateTextClassificationSession(classificationContext, mSessionId);
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 93f7103..3628d2d4 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -150,7 +150,7 @@
mLegacyIntent = legacyIntent;
mLegacyOnClickListener = legacyOnClickListener;
mActions = Collections.unmodifiableList(actions);
- mEntityConfidence = Preconditions.checkNotNull(entityConfidence);
+ mEntityConfidence = Objects.requireNonNull(entityConfidence);
mId = id;
mExtras = extras;
}
@@ -287,7 +287,7 @@
* @hide
*/
public static OnClickListener createIntentOnClickListener(@NonNull final PendingIntent intent) {
- Preconditions.checkNotNull(intent);
+ Objects.requireNonNull(intent);
return v -> {
try {
intent.send();
diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java
index 9f8671a..ed69513 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -16,38 +16,31 @@
package android.view.textclassifier;
+import android.annotation.Nullable;
+import android.provider.DeviceConfig;
+
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
-import java.util.function.Supplier;
/**
* TextClassifier specific settings.
- * This is encoded as a key=value list, separated by commas.
- * <p>
- * Example of setting the values for testing.
- * <p>
- * <pre>
- * adb shell settings put global text_classifier_constants \
- * model_dark_launch_enabled=true,smart_selection_enabled=true, \
- * entity_list_default=phone:address, \
- * lang_id_context_settings=20:1.0:0.4
- * </pre>
- * <p>
- * Settings are also available in device config. These take precedence over those in settings
- * global.
- * <p>
+ *
+ * <p>Currently, this class does not guarantee co-diverted flags are updated atomically.
+ *
* <pre>
* adb shell cmd device_config put textclassifier system_textclassifier_enabled true
* </pre>
*
- * @see android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
- * @see android.provider.DeviceConfig.NAMESPACE_TEXTCLASSIFIER
+ * @see android.provider.DeviceConfig#NAMESPACE_TEXTCLASSIFIER
* @hide
*/
// TODO: Rename to TextClassifierSettings.
public final class TextClassificationConstants {
+ private static final String DELIMITER = ":";
/**
* Whether the smart linkify feature is enabled.
@@ -56,11 +49,12 @@
/**
* Whether SystemTextClassifier is enabled.
*/
- private static final String SYSTEM_TEXT_CLASSIFIER_ENABLED = "system_textclassifier_enabled";
+ static final String SYSTEM_TEXT_CLASSIFIER_ENABLED = "system_textclassifier_enabled";
/**
* Whether TextClassifierImpl is enabled.
*/
- private static final String LOCAL_TEXT_CLASSIFIER_ENABLED = "local_textclassifier_enabled";
+ @VisibleForTesting
+ static final String LOCAL_TEXT_CLASSIFIER_ENABLED = "local_textclassifier_enabled";
/**
* Enable smart selection without a visible UI changes.
*/
@@ -82,7 +76,8 @@
/**
* Max length of text that suggestSelection can accept.
*/
- private static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH =
+ @VisibleForTesting
+ static final String SUGGEST_SELECTION_MAX_RANGE_LENGTH =
"suggest_selection_max_range_length";
/**
* Max length of text that classifyText can accept.
@@ -101,7 +96,8 @@
* A colon(:) separated string that specifies the default entities types for
* generateLinks when hint is not given.
*/
- private static final String ENTITY_LIST_DEFAULT = "entity_list_default";
+ @VisibleForTesting
+ static final String ENTITY_LIST_DEFAULT = "entity_list_default";
/**
* A colon(:) separated string that specifies the default entities types for
* generateLinks when the text is in a not editable UI widget.
@@ -127,7 +123,8 @@
/**
* Threshold to accept a suggested language from LangID model.
*/
- private static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override";
+ @VisibleForTesting
+ static final String LANG_ID_THRESHOLD_OVERRIDE = "lang_id_threshold_override";
/**
* Whether to enable {@link android.view.textclassifier.TemplateIntentFactory}.
*/
@@ -156,7 +153,8 @@
* Reject all text less than minimumTextSize with penalizeRatio=0
* @see {@code TextClassifierImpl#detectLanguages(String, int, int)} for reference.
*/
- private static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings";
+ @VisibleForTesting
+ static final String LANG_ID_CONTEXT_SETTINGS = "lang_id_context_settings";
/**
* The TextClassifierService which would like to use. Example of setting the package:
@@ -164,9 +162,9 @@
* adb shell cmd device_config put textclassifier textclassifier_service_package_override \
* com.android.textclassifier
* </pre>
- *
*/
- private static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE =
+ @VisibleForTesting
+ static final String TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE =
"textclassifier_service_package_override";
private static final String DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE = null;
@@ -213,142 +211,124 @@
private static final boolean DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT = true;
private static final float[] LANG_ID_CONTEXT_SETTINGS_DEFAULT = new float[] {20f, 1.0f, 0.4f};
- private final ConfigParser mConfigParser;
-
- public TextClassificationConstants(Supplier<String> legacySettingsSupplier) {
- mConfigParser = new ConfigParser(legacySettingsSupplier);
- }
-
- public String getTextClassifierServiceName() {
- return mConfigParser.getString(
+ @Nullable
+ public String getTextClassifierServicePackageOverride() {
+ return DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE,
DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE);
}
public boolean isLocalTextClassifierEnabled() {
- return mConfigParser.getBoolean(
- LOCAL_TEXT_CLASSIFIER_ENABLED,
- LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT);
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ LOCAL_TEXT_CLASSIFIER_ENABLED, LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT);
}
public boolean isSystemTextClassifierEnabled() {
- return mConfigParser.getBoolean(
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
SYSTEM_TEXT_CLASSIFIER_ENABLED,
SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT);
}
public boolean isModelDarkLaunchEnabled() {
- return mConfigParser.getBoolean(
- MODEL_DARK_LAUNCH_ENABLED,
- MODEL_DARK_LAUNCH_ENABLED_DEFAULT);
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ MODEL_DARK_LAUNCH_ENABLED, MODEL_DARK_LAUNCH_ENABLED_DEFAULT);
}
public boolean isSmartSelectionEnabled() {
- return mConfigParser.getBoolean(
- SMART_SELECTION_ENABLED,
- SMART_SELECTION_ENABLED_DEFAULT);
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ SMART_SELECTION_ENABLED, SMART_SELECTION_ENABLED_DEFAULT);
}
public boolean isSmartTextShareEnabled() {
- return mConfigParser.getBoolean(
- SMART_TEXT_SHARE_ENABLED,
- SMART_TEXT_SHARE_ENABLED_DEFAULT);
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ SMART_TEXT_SHARE_ENABLED, SMART_TEXT_SHARE_ENABLED_DEFAULT);
}
public boolean isSmartLinkifyEnabled() {
- return mConfigParser.getBoolean(
- SMART_LINKIFY_ENABLED,
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, SMART_LINKIFY_ENABLED,
SMART_LINKIFY_ENABLED_DEFAULT);
}
public boolean isSmartSelectionAnimationEnabled() {
- return mConfigParser.getBoolean(
- SMART_SELECT_ANIMATION_ENABLED,
- SMART_SELECT_ANIMATION_ENABLED_DEFAULT);
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ SMART_SELECT_ANIMATION_ENABLED, SMART_SELECT_ANIMATION_ENABLED_DEFAULT);
}
public int getSuggestSelectionMaxRangeLength() {
- return mConfigParser.getInt(
- SUGGEST_SELECTION_MAX_RANGE_LENGTH,
- SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT);
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ SUGGEST_SELECTION_MAX_RANGE_LENGTH, SUGGEST_SELECTION_MAX_RANGE_LENGTH_DEFAULT);
}
public int getClassifyTextMaxRangeLength() {
- return mConfigParser.getInt(
- CLASSIFY_TEXT_MAX_RANGE_LENGTH,
- CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT);
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ CLASSIFY_TEXT_MAX_RANGE_LENGTH, CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT);
}
public int getGenerateLinksMaxTextLength() {
- return mConfigParser.getInt(
- GENERATE_LINKS_MAX_TEXT_LENGTH,
- GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ GENERATE_LINKS_MAX_TEXT_LENGTH, GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT);
}
public int getGenerateLinksLogSampleRate() {
- return mConfigParser.getInt(
- GENERATE_LINKS_LOG_SAMPLE_RATE,
- GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ GENERATE_LINKS_LOG_SAMPLE_RATE, GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
}
public List<String> getEntityListDefault() {
- return mConfigParser.getStringList(
- ENTITY_LIST_DEFAULT,
- ENTITY_LIST_DEFAULT_VALUE);
+ return getDeviceConfigStringList(ENTITY_LIST_DEFAULT, ENTITY_LIST_DEFAULT_VALUE);
}
public List<String> getEntityListNotEditable() {
- return mConfigParser.getStringList(
- ENTITY_LIST_NOT_EDITABLE,
- ENTITY_LIST_DEFAULT_VALUE);
+ return getDeviceConfigStringList(ENTITY_LIST_NOT_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
}
public List<String> getEntityListEditable() {
- return mConfigParser.getStringList(
- ENTITY_LIST_EDITABLE,
- ENTITY_LIST_DEFAULT_VALUE);
+ return getDeviceConfigStringList(ENTITY_LIST_EDITABLE, ENTITY_LIST_DEFAULT_VALUE);
}
public List<String> getInAppConversationActionTypes() {
- return mConfigParser.getStringList(
+ return getDeviceConfigStringList(
IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT,
CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
}
public List<String> getNotificationConversationActionTypes() {
- return mConfigParser.getStringList(
+ return getDeviceConfigStringList(
NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT,
CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES);
}
public float getLangIdThresholdOverride() {
- return mConfigParser.getFloat(
+ return DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
LANG_ID_THRESHOLD_OVERRIDE,
LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
}
public boolean isTemplateIntentFactoryEnabled() {
- return mConfigParser.getBoolean(
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
TEMPLATE_INTENT_FACTORY_ENABLED,
TEMPLATE_INTENT_FACTORY_ENABLED_DEFAULT);
}
public boolean isTranslateInClassificationEnabled() {
- return mConfigParser.getBoolean(
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
TRANSLATE_IN_CLASSIFICATION_ENABLED,
TRANSLATE_IN_CLASSIFICATION_ENABLED_DEFAULT);
}
public boolean isDetectLanguagesFromTextEnabled() {
- return mConfigParser.getBoolean(
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
DETECT_LANGUAGES_FROM_TEXT_ENABLED,
DETECT_LANGUAGES_FROM_TEXT_ENABLED_DEFAULT);
}
public float[] getLangIdContextSettings() {
- return mConfigParser.getFloatArray(
- LANG_ID_CONTEXT_SETTINGS,
- LANG_ID_CONTEXT_SETTINGS_DEFAULT);
+ return getDeviceConfigFloatArray(
+ LANG_ID_CONTEXT_SETTINGS, LANG_ID_CONTEXT_SETTINGS_DEFAULT);
}
void dump(IndentingPrintWriter pw) {
@@ -356,7 +336,7 @@
pw.increaseIndent();
pw.printPair("classify_text_max_range_length", getClassifyTextMaxRangeLength())
.println();
- pw.printPair("detect_language_from_text_enabled", isDetectLanguagesFromTextEnabled())
+ pw.printPair("detect_languages_from_text_enabled", isDetectLanguagesFromTextEnabled())
.println();
pw.printPair("entity_list_default", getEntityListDefault())
.println();
@@ -396,8 +376,47 @@
.println();
pw.printPair("translate_in_classification_enabled", isTranslateInClassificationEnabled())
.println();
- pw.printPair("textclassifier_service_package_override", getTextClassifierServiceName())
- .println();
+ pw.printPair("textclassifier_service_package_override",
+ getTextClassifierServicePackageOverride()).println();
pw.decreaseIndent();
}
+
+ private static List<String> getDeviceConfigStringList(String key, List<String> defaultValue) {
+ return parse(
+ DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null),
+ defaultValue);
+ }
+
+ private static float[] getDeviceConfigFloatArray(String key, float[] defaultValue) {
+ return parse(
+ DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, null),
+ defaultValue);
+ }
+
+ private static List<String> parse(@Nullable String listStr, List<String> defaultValue) {
+ if (listStr != null) {
+ return Collections.unmodifiableList(Arrays.asList(listStr.split(DELIMITER)));
+ }
+ return defaultValue;
+ }
+
+ private static float[] parse(@Nullable String arrayStr, float[] defaultValue) {
+ if (arrayStr != null) {
+ final String[] split = arrayStr.split(DELIMITER);
+ if (split.length != defaultValue.length) {
+ return defaultValue;
+ }
+ final float[] result = new float[split.length];
+ for (int i = 0; i < split.length; i++) {
+ try {
+ result[i] = Float.parseFloat(split[i]);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+ return result;
+ } else {
+ return defaultValue;
+ }
+ }
}
\ No newline at end of file
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index e4baaac..930765b 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -24,9 +24,8 @@
import android.os.UserHandle;
import android.view.textclassifier.TextClassifier.WidgetType;
-import com.android.internal.util.Preconditions;
-
import java.util.Locale;
+import java.util.Objects;
/**
* A representation of the context in which text classification would be performed.
@@ -44,8 +43,8 @@
String packageName,
String widgetType,
String widgetVersion) {
- mPackageName = Preconditions.checkNotNull(packageName);
- mWidgetType = Preconditions.checkNotNull(widgetType);
+ mPackageName = Objects.requireNonNull(packageName);
+ mWidgetType = Objects.requireNonNull(widgetType);
mWidgetVersion = widgetVersion;
}
@@ -121,8 +120,8 @@
* @return this builder
*/
public Builder(@NonNull String packageName, @NonNull @WidgetType String widgetType) {
- mPackageName = Preconditions.checkNotNull(packageName);
- mWidgetType = Preconditions.checkNotNull(widgetType);
+ mPackageName = Objects.requireNonNull(packageName);
+ mWidgetType = Objects.requireNonNull(widgetType);
}
/**
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index 3e0f7ee..bb96d55 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -19,23 +19,22 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.database.ContentObserver;
import android.os.ServiceManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
-import android.provider.Settings;
import android.service.textclassifier.TextClassifierService;
import android.view.textclassifier.TextClassifier.TextClassifierType;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.Set;
/**
* Interface to the text classification service.
@@ -46,7 +45,7 @@
private static final String LOG_TAG = "TextClassificationManager";
private static final TextClassificationConstants sDefaultSettings =
- new TextClassificationConstants(() -> null);
+ new TextClassificationConstants();
private final Object mLock = new Object();
private final TextClassificationSessionFactory mDefaultSessionFactory =
@@ -72,7 +71,7 @@
/** @hide */
public TextClassificationManager(Context context) {
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
mSessionFactory = mDefaultSessionFactory;
mSettingsObserver = new SettingsObserver(this);
}
@@ -132,10 +131,7 @@
private TextClassificationConstants getSettings() {
synchronized (mLock) {
if (mSettings == null) {
- mSettings = new TextClassificationConstants(
- () -> Settings.Global.getString(
- getApplicationContext().getContentResolver(),
- Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
+ mSettings = new TextClassificationConstants();
}
return mSettings;
}
@@ -161,10 +157,10 @@
@NonNull
public TextClassifier createTextClassificationSession(
@NonNull TextClassificationContext classificationContext) {
- Preconditions.checkNotNull(classificationContext);
+ Objects.requireNonNull(classificationContext);
final TextClassifier textClassifier =
mSessionFactory.createTextClassificationSession(classificationContext);
- Preconditions.checkNotNull(textClassifier, "Session Factory should never return null");
+ Objects.requireNonNull(textClassifier, "Session Factory should never return null");
return textClassifier;
}
@@ -174,8 +170,8 @@
*/
public TextClassifier createTextClassificationSession(
TextClassificationContext classificationContext, TextClassifier textClassifier) {
- Preconditions.checkNotNull(classificationContext);
- Preconditions.checkNotNull(textClassifier);
+ Objects.requireNonNull(classificationContext);
+ Objects.requireNonNull(textClassifier);
return new TextClassificationSession(classificationContext, textClassifier);
}
@@ -201,11 +197,7 @@
try {
// Note that fields could be null if the constructor threw.
if (mSettingsObserver != null) {
- getApplicationContext().getContentResolver()
- .unregisterContentObserver(mSettingsObserver);
- if (ConfigParser.ENABLE_DEVICE_CONFIG) {
- DeviceConfig.removeOnPropertiesChangedListener(mSettingsObserver);
- }
+ DeviceConfig.removeOnPropertiesChangedListener(mSettingsObserver);
}
} finally {
super.finalize();
@@ -250,7 +242,7 @@
private boolean isSystemTextClassifierEnabled() {
return getSettings().isSystemTextClassifierEnabled()
- && TextClassifierService.getServiceComponentName(mContext, getSettings()) != null;
+ && TextClassifierService.getServiceComponentName(mContext) != null;
}
/** @hide */
@@ -262,6 +254,12 @@
private void invalidate() {
synchronized (mLock) {
mSettings = null;
+ invalidateTextClassifiers();
+ }
+ }
+
+ private void invalidateTextClassifiers() {
+ synchronized (mLock) {
mLocalTextClassifier = null;
mSystemTextClassifier = null;
}
@@ -282,7 +280,7 @@
/** @hide */
public static TextClassificationConstants getSettings(Context context) {
- Preconditions.checkNotNull(context);
+ Objects.requireNonNull(context);
final TextClassificationManager tcm =
context.getSystemService(TextClassificationManager.class);
if (tcm != null) {
@@ -293,40 +291,29 @@
}
}
- private static final class SettingsObserver extends ContentObserver
- implements DeviceConfig.OnPropertiesChangedListener {
+ private static final class SettingsObserver implements
+ DeviceConfig.OnPropertiesChangedListener {
private final WeakReference<TextClassificationManager> mTcm;
SettingsObserver(TextClassificationManager tcm) {
- super(null);
mTcm = new WeakReference<>(tcm);
- tcm.getApplicationContext().getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS),
- false /* notifyForDescendants */,
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ ActivityThread.currentApplication().getMainExecutor(),
this);
- if (ConfigParser.ENABLE_DEVICE_CONFIG) {
- DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- ActivityThread.currentApplication().getMainExecutor(),
- this);
- }
- }
-
- @Override
- public void onChange(boolean selfChange) {
- invalidateSettings();
}
@Override
public void onPropertiesChanged(Properties properties) {
- invalidateSettings();
- }
-
- private void invalidateSettings() {
final TextClassificationManager tcm = mTcm.get();
if (tcm != null) {
- tcm.invalidate();
+ final Set<String> keys = properties.getKeyset();
+ if (keys.contains(TextClassificationConstants.SYSTEM_TEXT_CLASSIFIER_ENABLED)
+ || keys.contains(
+ TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED)) {
+ tcm.invalidateTextClassifiers();
+ }
}
}
}
diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
index 6a706f5..abfbc6c 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -20,6 +20,7 @@
import android.view.textclassifier.SelectionEvent.InvocationMethod;
import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
* Session-aware TextClassifier.
@@ -37,8 +38,8 @@
private boolean mDestroyed;
TextClassificationSession(TextClassificationContext context, TextClassifier delegate) {
- mClassificationContext = Preconditions.checkNotNull(context);
- mDelegate = Preconditions.checkNotNull(delegate);
+ mClassificationContext = Objects.requireNonNull(context);
+ mDelegate = Objects.requireNonNull(delegate);
mSessionId = new TextClassificationSessionId();
mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext);
initializeRemoteSession();
@@ -149,8 +150,8 @@
SelectionEventHelper(
TextClassificationSessionId sessionId, TextClassificationContext context) {
- mSessionId = Preconditions.checkNotNull(sessionId);
- mContext = Preconditions.checkNotNull(context);
+ mSessionId = Objects.requireNonNull(sessionId);
+ mContext = Objects.requireNonNull(context);
}
/**
diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java
index 8b68e17..f90e6b2 100644
--- a/core/java/android/view/textclassifier/TextClassificationSessionId.java
+++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java
@@ -20,9 +20,8 @@
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.Preconditions;
-
import java.util.Locale;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -118,7 +117,7 @@
@Override
public TextClassificationSessionId createFromParcel(Parcel parcel) {
return new TextClassificationSessionId(
- Preconditions.checkNotNull(parcel.readString()));
+ Objects.requireNonNull(parcel.readString()));
}
@Override
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index ac8a429..9b33693 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -46,6 +46,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -193,7 +194,7 @@
@WorkerThread
@NonNull
default TextSelection suggestSelection(@NonNull TextSelection.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return new TextSelection.Builder(request.getStartIndex(), request.getEndIndex()).build();
}
@@ -252,7 +253,7 @@
@WorkerThread
@NonNull
default TextClassification classifyText(@NonNull TextClassification.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return TextClassification.EMPTY;
}
@@ -313,7 +314,7 @@
@WorkerThread
@NonNull
default TextLinks generateLinks(@NonNull TextLinks.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return new TextLinks.Builder(request.getText().toString()).build();
}
@@ -346,7 +347,7 @@
@WorkerThread
@NonNull
default TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return TextLanguage.EMPTY;
}
@@ -358,7 +359,7 @@
@NonNull
default ConversationActions suggestConversationActions(
@NonNull ConversationActions.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
return new ConversationActions(Collections.emptyList(), null);
}
@@ -427,9 +428,9 @@
List<String> excludedEntityTypes,
List<String> hints,
boolean includeTypesFromTextClassifier) {
- mIncludedTypes = Preconditions.checkNotNull(includedEntityTypes);
- mExcludedTypes = Preconditions.checkNotNull(excludedEntityTypes);
- mHints = Preconditions.checkNotNull(hints);
+ mIncludedTypes = Objects.requireNonNull(includedEntityTypes);
+ mExcludedTypes = Objects.requireNonNull(excludedEntityTypes);
+ mHints = Objects.requireNonNull(hints);
mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier;
}
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index eb6aec0..db88011 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -25,11 +25,11 @@
import android.os.Parcelable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Objects;
/**
* This class represents events that are sent by components to the {@link TextClassifier} to report
@@ -549,7 +549,7 @@
*/
@NonNull
public T setEntityTypes(@NonNull String... entityTypes) {
- Preconditions.checkNotNull(entityTypes);
+ Objects.requireNonNull(entityTypes);
mEntityTypes = new String[entityTypes.length];
System.arraycopy(entityTypes, 0, mEntityTypes, 0, entityTypes.length);
return self();
@@ -587,7 +587,7 @@
*/
@NonNull
public T setScores(@NonNull float... scores) {
- Preconditions.checkNotNull(scores);
+ Objects.requireNonNull(scores);
mScores = new float[scores.length];
System.arraycopy(scores, 0, mScores, 0, scores.length);
return self();
@@ -652,7 +652,7 @@
*/
@NonNull
public T setExtras(@NonNull Bundle extras) {
- mExtras = Preconditions.checkNotNull(extras);
+ mExtras = Objects.requireNonNull(extras);
return self();
}
diff --git a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
index 3e088b8..8162699 100644
--- a/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
+++ b/core/java/android/view/textclassifier/TextClassifierEventTronLogger.java
@@ -29,7 +29,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
@@ -50,12 +50,12 @@
@VisibleForTesting
public TextClassifierEventTronLogger(MetricsLogger metricsLogger) {
- mMetricsLogger = Preconditions.checkNotNull(metricsLogger);
+ mMetricsLogger = Objects.requireNonNull(metricsLogger);
}
/** Emits a text classifier event to the logs. */
public void writeEvent(TextClassifierEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
int category = getCategory(event);
if (category == -1) {
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 024c379..61bd7c7 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -130,9 +130,9 @@
public TextClassifierImpl(
Context context, TextClassificationConstants settings, TextClassifier fallback) {
- mContext = Preconditions.checkNotNull(context);
- mFallback = Preconditions.checkNotNull(fallback);
- mSettings = Preconditions.checkNotNull(settings);
+ mContext = Objects.requireNonNull(context);
+ mFallback = Objects.requireNonNull(fallback);
+ mSettings = Objects.requireNonNull(settings);
mGenerateLinksLogger = new GenerateLinksLogger(mSettings.getGenerateLinksLogSampleRate());
mAnnotatorModelFileManager = new ModelFileManager(
new ModelFileManager.ModelFileSupplierImpl(
@@ -180,7 +180,7 @@
@Override
@WorkerThread
public TextSelection suggestSelection(TextSelection.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
final int rangeLength = request.getEndIndex() - request.getStartIndex();
@@ -245,7 +245,7 @@
@Override
@WorkerThread
public TextClassification classifyText(TextClassification.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
final int rangeLength = request.getEndIndex() - request.getStartIndex();
@@ -285,7 +285,7 @@
@Override
@WorkerThread
public TextLinks generateLinks(@NonNull TextLinks.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkTextLength(request.getText(), getMaxGenerateLinksTextLength());
Utils.checkMainThread();
@@ -399,7 +399,7 @@
/** @inheritDoc */
@Override
public TextLanguage detectLanguage(@NonNull TextLanguage.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
final TextLanguage.Builder builder = new TextLanguage.Builder();
@@ -420,7 +420,7 @@
@Override
public ConversationActions suggestConversationActions(ConversationActions.Request request) {
- Preconditions.checkNotNull(request);
+ Objects.requireNonNull(request);
Utils.checkMainThread();
try {
ActionsSuggestionsModel actionsImpl = getActionsImpl();
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index bbb7f07..1aa2aec 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -42,12 +42,14 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Function;
/**
@@ -109,7 +111,6 @@
/**
* Returns the text that was used to generate these links.
- * @hide
*/
@NonNull
public String getText() {
@@ -155,7 +156,7 @@
@NonNull Spannable text,
@ApplyStrategy int applyStrategy,
@Nullable Function<TextLink, TextLinkSpan> spanFactory) {
- Preconditions.checkNotNull(text);
+ Objects.requireNonNull(text);
return new TextLinksParams.Builder()
.setApplyStrategy(applyStrategy)
.setSpanFactory(spanFactory)
@@ -223,10 +224,10 @@
*/
private TextLink(int start, int end, @NonNull EntityConfidence entityConfidence,
@NonNull Bundle extras, @Nullable URLSpan urlSpan) {
- Preconditions.checkNotNull(entityConfidence);
+ Objects.requireNonNull(entityConfidence);
Preconditions.checkArgument(!entityConfidence.getEntities().isEmpty());
Preconditions.checkArgument(start <= end);
- Preconditions.checkNotNull(extras);
+ Objects.requireNonNull(extras);
mStart = start;
mEnd = end;
mEntityScores = entityConfidence;
@@ -341,6 +342,7 @@
private final boolean mLegacyFallback;
@Nullable private String mCallingPackageName;
private final Bundle mExtras;
+ @Nullable private final ZonedDateTime mReferenceTime;
@UserIdInt
private int mUserId = UserHandle.USER_NULL;
@@ -349,11 +351,13 @@
LocaleList defaultLocales,
EntityConfig entityConfig,
boolean legacyFallback,
+ ZonedDateTime referenceTime,
Bundle extras) {
mText = text;
mDefaultLocales = defaultLocales;
mEntityConfig = entityConfig;
mLegacyFallback = legacyFallback;
+ mReferenceTime = referenceTime;
mExtras = extras;
}
@@ -394,6 +398,15 @@
}
/**
+ * @return reference time based on which relative dates (e.g. "tomorrow") should be
+ * interpreted.
+ */
+ @Nullable
+ public ZonedDateTime getReferenceTime() {
+ return mReferenceTime;
+ }
+
+ /**
* Sets the name of the package that is sending this request.
* <p>
* Package-private for SystemTextClassifier's use.
@@ -453,9 +466,10 @@
@Nullable private EntityConfig mEntityConfig;
private boolean mLegacyFallback = true; // Use legacy fall back by default.
@Nullable private Bundle mExtras;
+ @Nullable private ZonedDateTime mReferenceTime;
public Builder(@NonNull CharSequence text) {
- mText = Preconditions.checkNotNull(text);
+ mText = Objects.requireNonNull(text);
}
/**
@@ -510,13 +524,26 @@
}
/**
+ * @param referenceTime reference time based on which relative dates (e.g. "tomorrow"
+ * should be interpreted. This should usually be the time when the text was
+ * originally composed.
+ *
+ * @return this builder
+ */
+ @NonNull
+ public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
+ mReferenceTime = referenceTime;
+ return this;
+ }
+
+ /**
* Builds and returns the request object.
*/
@NonNull
public Request build() {
return new Request(
mText, mDefaultLocales, mEntityConfig,
- mLegacyFallback,
+ mLegacyFallback, mReferenceTime,
mExtras == null ? Bundle.EMPTY : mExtras);
}
}
@@ -534,6 +561,7 @@
dest.writeString(mCallingPackageName);
dest.writeInt(mUserId);
dest.writeBundle(mExtras);
+ dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString());
}
private static Request readFromParcel(Parcel in) {
@@ -543,9 +571,12 @@
final String callingPackageName = in.readString();
final int userId = in.readInt();
final Bundle extras = in.readBundle();
+ final String referenceTimeString = in.readString();
+ final ZonedDateTime referenceTime = referenceTimeString == null
+ ? null : ZonedDateTime.parse(referenceTimeString);
final Request request = new Request(text, defaultLocales, entityConfig,
- /* legacyFallback= */ true, extras);
+ /* legacyFallback= */ true, referenceTime, extras);
request.setCallingPackageName(callingPackageName);
request.setUserId(userId);
return request;
@@ -654,7 +685,7 @@
* @param fullText The full text to annotate with links
*/
public Builder(@NonNull String fullText) {
- mFullText = Preconditions.checkNotNull(fullText);
+ mFullText = Objects.requireNonNull(fullText);
mLinks = new ArrayList<>();
}
diff --git a/core/java/android/view/textclassifier/TextLinksParams.java b/core/java/android/view/textclassifier/TextLinksParams.java
index 8af4233..b7d63bf 100644
--- a/core/java/android/view/textclassifier/TextLinksParams.java
+++ b/core/java/android/view/textclassifier/TextLinksParams.java
@@ -25,10 +25,9 @@
import android.view.textclassifier.TextLinks.TextLink;
import android.view.textclassifier.TextLinks.TextLinkSpan;
-import com.android.internal.util.Preconditions;
-
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Function;
/**
@@ -103,8 +102,8 @@
*/
@TextLinks.Status
public int apply(@NonNull Spannable text, @NonNull TextLinks textLinks) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(textLinks);
+ Objects.requireNonNull(text);
+ Objects.requireNonNull(textLinks);
final String textString = text.toString();
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 0c89749..4a36cbf 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -36,6 +36,7 @@
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
/**
* Information about where text selection should be.
@@ -165,7 +166,7 @@
public Builder setEntityType(
@NonNull @EntityType String type,
@FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
- Preconditions.checkNotNull(type);
+ Objects.requireNonNull(type);
mEntityConfidence.put(type, confidenceScore);
return this;
}
diff --git a/core/java/android/view/textclassifier/intent/LabeledIntent.java b/core/java/android/view/textclassifier/intent/LabeledIntent.java
index 30fc20e..cbd9d1a 100644
--- a/core/java/android/view/textclassifier/intent/LabeledIntent.java
+++ b/core/java/android/view/textclassifier/intent/LabeledIntent.java
@@ -32,7 +32,8 @@
import android.view.textclassifier.TextClassifier;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
/**
* Helper class to store the information from which RemoteActions are built.
@@ -86,9 +87,9 @@
}
this.titleWithoutEntity = titleWithoutEntity;
this.titleWithEntity = titleWithEntity;
- this.description = Preconditions.checkNotNull(description);
+ this.description = Objects.requireNonNull(description);
this.descriptionWithAppName = descriptionWithAppName;
- this.intent = Preconditions.checkNotNull(intent);
+ this.intent = Objects.requireNonNull(intent);
this.requestCode = requestCode;
}
@@ -198,8 +199,8 @@
public final RemoteAction remoteAction;
public Result(Intent resolvedIntent, RemoteAction remoteAction) {
- this.resolvedIntent = Preconditions.checkNotNull(resolvedIntent);
- this.remoteAction = Preconditions.checkNotNull(remoteAction);
+ this.resolvedIntent = Objects.requireNonNull(resolvedIntent);
+ this.remoteAction = Objects.requireNonNull(remoteAction);
}
}
diff --git a/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java
index 111fc6a..aef4bd6 100644
--- a/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java
+++ b/core/java/android/view/textclassifier/intent/TemplateClassificationIntentFactory.java
@@ -30,6 +30,7 @@
import java.time.Instant;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Creates intents based on {@link RemoteActionTemplate} objects for a ClassificationResult.
@@ -44,8 +45,8 @@
public TemplateClassificationIntentFactory(TemplateIntentFactory templateIntentFactory,
ClassificationIntentFactory fallback) {
- mTemplateIntentFactory = Preconditions.checkNotNull(templateIntentFactory);
- mFallback = Preconditions.checkNotNull(fallback);
+ mTemplateIntentFactory = Objects.requireNonNull(templateIntentFactory);
+ mFallback = Objects.requireNonNull(fallback);
}
/**
diff --git a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 157c435..7dbcbf9 100644
--- a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.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.metrics.LogMaker;
import android.util.Log;
@@ -105,14 +105,14 @@
public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
mWidgetType = widgetType;
mWidgetVersion = null;
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
}
public SmartSelectionEventTracker(
@NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) {
mWidgetType = widgetType;
mWidgetVersion = widgetVersion;
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
}
/**
@@ -122,7 +122,7 @@
*/
@UnsupportedAppUsage(trackingBug = 136637107)
public void logEvent(@NonNull SelectionEvent event) {
- Preconditions.checkNotNull(event);
+ Objects.requireNonNull(event);
if (event.mEventType != SelectionEvent.EventType.SELECTION_STARTED && mSessionId == null
&& DEBUG_LOG_ENABLED) {
@@ -435,8 +435,8 @@
mStart = start;
mEnd = end;
mEventType = eventType;
- mEntityType = Preconditions.checkNotNull(entityType);
- mVersionTag = Preconditions.checkNotNull(versionTag);
+ mEntityType = Objects.requireNonNull(entityType);
+ mVersionTag = Objects.requireNonNull(versionTag);
}
/**
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index f553ca5..afddaa2 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -16,7 +16,7 @@
package android.view.textservice;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 9ff64d9..acb35d6 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index 7e06719..fafe813 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -17,7 +17,7 @@
package android.webkit;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.File;
import java.io.IOException;
diff --git a/core/java/android/webkit/ConsoleMessage.java b/core/java/android/webkit/ConsoleMessage.java
index e548497..5474557 100644
--- a/core/java/android/webkit/ConsoleMessage.java
+++ b/core/java/android/webkit/ConsoleMessage.java
@@ -16,7 +16,7 @@
package android.webkit;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
/**
diff --git a/core/java/android/webkit/JsResult.java b/core/java/android/webkit/JsResult.java
index 5bf6aab..448db58e 100644
--- a/core/java/android/webkit/JsResult.java
+++ b/core/java/android/webkit/JsResult.java
@@ -17,7 +17,7 @@
package android.webkit;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* An instance of this class is passed as a parameter in various {@link WebChromeClient} action
diff --git a/core/java/android/webkit/PluginData.java b/core/java/android/webkit/PluginData.java
index 8aeeb1c..c9a1960 100644
--- a/core/java/android/webkit/PluginData.java
+++ b/core/java/android/webkit/PluginData.java
@@ -16,7 +16,8 @@
package android.webkit;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.InputStream;
import java.util.Map;
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 5d704cb..844b156 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -17,7 +17,7 @@
package android.webkit;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.ParseException;
import android.net.Uri;
import android.net.WebAddress;
diff --git a/core/java/android/webkit/UrlInterceptHandler.java b/core/java/android/webkit/UrlInterceptHandler.java
index f23aae6b..a48e107 100644
--- a/core/java/android/webkit/UrlInterceptHandler.java
+++ b/core/java/android/webkit/UrlInterceptHandler.java
@@ -17,9 +17,8 @@
package android.webkit;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.webkit.CacheManager.CacheResult;
-import android.webkit.PluginData;
import java.util.Map;
diff --git a/core/java/android/webkit/UrlInterceptRegistry.java b/core/java/android/webkit/UrlInterceptRegistry.java
index eeb28d7..c9dee00 100644
--- a/core/java/android/webkit/UrlInterceptRegistry.java
+++ b/core/java/android/webkit/UrlInterceptRegistry.java
@@ -17,10 +17,8 @@
package android.webkit;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.webkit.CacheManager.CacheResult;
-import android.webkit.PluginData;
-import android.webkit.UrlInterceptHandler;
import java.util.Iterator;
import java.util.LinkedList;
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index 7c8f33e..219523b 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.InputStream;
import java.io.StringBufferInputStream;
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 2895621..2d27a78 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import java.lang.annotation.ElementType;
diff --git a/core/java/android/webkit/WebSyncManager.java b/core/java/android/webkit/WebSyncManager.java
index e44d6eb..7046c51 100644
--- a/core/java/android/webkit/WebSyncManager.java
+++ b/core/java/android/webkit/WebSyncManager.java
@@ -16,7 +16,7 @@
package android.webkit;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 4b47927..f9a713a 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,8 +21,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.Widget;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index f5657df..df86926 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -19,10 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.Application;
import android.app.ResourcesManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 678a252..941af6e 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -17,10 +17,10 @@
package android.webkit;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java
index 7e00cde..6629fdc4 100644
--- a/core/java/android/webkit/WebViewProviderInfo.java
+++ b/core/java/android/webkit/WebViewProviderInfo.java
@@ -17,7 +17,7 @@
package android.webkit;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.Signature;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/webkit/WebViewProviderResponse.java b/core/java/android/webkit/WebViewProviderResponse.java
index 5622abe..b58cc4bb 100644
--- a/core/java/android/webkit/WebViewProviderResponse.java
+++ b/core/java/android/webkit/WebViewProviderResponse.java
@@ -16,7 +16,7 @@
package android.webkit;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.PackageInfo;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/webkit/WebViewUpdateService.java b/core/java/android/webkit/WebViewUpdateService.java
index 12d3221..9152b43 100644
--- a/core/java/android/webkit/WebViewUpdateService.java
+++ b/core/java/android/webkit/WebViewUpdateService.java
@@ -17,7 +17,7 @@
package android.webkit;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.RemoteException;
/**
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6d60366..4752ead 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -20,7 +20,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
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.Configuration;
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index bbcba2e..11a6acf 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.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.ColorStateList;
import android.content.res.TypedArray;
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index cfb93ec..aa3590a 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -22,7 +22,7 @@
import android.animation.PropertyValuesHolder;
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.Configuration;
import android.content.res.Resources;
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 7e58622..3a74356 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -18,7 +18,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.content.res.Configuration;
import android.graphics.drawable.Drawable;
diff --git a/core/java/android/widget/ActivityChooserModel.java b/core/java/android/widget/ActivityChooserModel.java
index f5bf759..d87bdf4 100644
--- a/core/java/android/widget/ActivityChooserModel.java
+++ b/core/java/android/widget/ActivityChooserModel.java
@@ -16,8 +16,8 @@
package android.widget;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index 89ea074..aa18d57 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -17,7 +17,7 @@
package android.widget;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index c55f7d6..5265840 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.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.database.DataSetObserver;
import android.os.Build;
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 67a70b4..d165bd0 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -16,7 +16,7 @@
package android.widget;
-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/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index f7225d0..de9f76d 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -21,7 +21,7 @@
import android.annotation.LayoutRes;
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.util.Log;
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 8785251..8d9ae58 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -18,7 +18,7 @@
import android.annotation.DrawableRes;
import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
diff --git a/core/java/android/widget/BaseAdapter.java b/core/java/android/widget/BaseAdapter.java
index 7b9365b..27cf9a6 100644
--- a/core/java/android/widget/BaseAdapter.java
+++ b/core/java/android/widget/BaseAdapter.java
@@ -17,7 +17,7 @@
package android.widget;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.view.View;
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index b552aa6..4b2f738 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -23,8 +23,8 @@
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.Widget;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 8b70f41..422d2d3 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.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.content.res.ColorStateList;
import android.content.res.TypedArray;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index eb0d9bf..3b6a009 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -148,8 +148,11 @@
public class Editor {
private static final String TAG = "Editor";
private static final boolean DEBUG_UNDO = false;
- // Specifies whether to use or not the magnifier when pressing the insertion or selection
- // handles.
+
+ // Specifies whether to allow starting a cursor drag by dragging anywhere over the text.
+ @VisibleForTesting
+ public static boolean FLAG_ENABLE_CURSOR_DRAG = false;
+ // Specifies whether to use the magnifier when pressing the insertion or selection handles.
private static final boolean FLAG_USE_MAGNIFIER = true;
private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
@@ -204,7 +207,7 @@
private final MetricsLogger mMetricsLogger = new MetricsLogger();
// Cursor Controllers.
- private InsertionPointCursorController mInsertionPointCursorController;
+ InsertionPointCursorController mInsertionPointCursorController;
SelectionModifierCursorController mSelectionModifierCursorController;
// Action mode used when text is selected or when actions on an insertion cursor are triggered.
private ActionMode mTextActionMode;
@@ -1471,6 +1474,9 @@
mTouchState.update(event, viewConfiguration);
updateFloatingToolbarVisibility(event);
+ if (hasInsertionController()) {
+ getInsertionController().onTouchEvent(event);
+ }
if (hasSelectionController()) {
getSelectionController().onTouchEvent(event);
}
@@ -5179,15 +5185,11 @@
case MotionEvent.ACTION_UP:
if (!offsetHasBeenChanged()) {
- final float deltaX = mLastDownRawX - ev.getRawX();
- final float deltaY = mLastDownRawY - ev.getRawY();
- final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
-
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(
- mTextView.getContext());
- final int touchSlop = viewConfiguration.getScaledTouchSlop();
-
- if (distanceSquared < touchSlop * touchSlop) {
+ ViewConfiguration config = ViewConfiguration.get(mTextView.getContext());
+ boolean isWithinTouchSlop = EditorTouchState.isDistanceWithin(
+ mLastDownRawX, mLastDownRawY, ev.getRawX(), ev.getRawY(),
+ config.getScaledTouchSlop());
+ if (isWithinTouchSlop) {
// Tapping on the handle toggles the insertion action mode.
if (mTextActionMode != null) {
stopTextActionMode();
@@ -5237,6 +5239,10 @@
} else {
offset = -1;
}
+ if (TextView.DEBUG_CURSOR) {
+ logCursor("InsertionHandleView: updatePosition", "x=%f, y=%f, offset=%d, line=%d",
+ x, y, offset, mPreviousLineTouched);
+ }
positionAtCursorOffset(offset, false, fromTouchScreen);
if (mTextActionMode != null) {
invalidateActionMode();
@@ -5716,8 +5722,82 @@
}
}
- private class InsertionPointCursorController implements CursorController {
+ class InsertionPointCursorController implements CursorController {
private InsertionHandleView mHandle;
+ private boolean mIsDraggingCursor;
+
+ public void onTouchEvent(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mIsDraggingCursor = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mIsDraggingCursor) {
+ performCursorDrag(event);
+ } else if (FLAG_ENABLE_CURSOR_DRAG
+ && mTextView.getLayout() != null
+ && mTextView.isFocused()
+ && mTouchState.isMovedEnoughForDrag()) {
+ startCursorDrag(event);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsDraggingCursor) {
+ endCursorDrag(event);
+ }
+ break;
+ }
+ }
+
+ private void positionCursorDuringDrag(MotionEvent event) {
+ int line = mTextView.getLineAtCoordinate(event.getY());
+ int offset = mTextView.getOffsetAtCoordinate(line, event.getX());
+ int oldSelectionStart = mTextView.getSelectionStart();
+ int oldSelectionEnd = mTextView.getSelectionEnd();
+ if (offset == oldSelectionStart && offset == oldSelectionEnd) {
+ return;
+ }
+ Selection.setSelection((Spannable) mTextView.getText(), offset);
+ updateCursorPosition();
+ if (mHapticTextHandleEnabled) {
+ mTextView.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE);
+ }
+ }
+
+ private void startCursorDrag(MotionEvent event) {
+ if (TextView.DEBUG_CURSOR) {
+ logCursor("InsertionPointCursorController", "start cursor drag");
+ }
+ mIsDraggingCursor = true;
+ // We don't want the parent scroll/long-press handlers to take over while dragging.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(true);
+ mTextView.cancelLongPress();
+ // Update the cursor position.
+ positionCursorDuringDrag(event);
+ // Show the cursor handle and magnifier.
+ show();
+ getHandle().removeHiderCallback();
+ getHandle().updateMagnifier(event);
+ // TODO(b/146555651): Figure out if suspendBlink() should be called here.
+ }
+
+ private void performCursorDrag(MotionEvent event) {
+ positionCursorDuringDrag(event);
+ getHandle().updateMagnifier(event);
+ }
+
+ private void endCursorDrag(MotionEvent event) {
+ if (TextView.DEBUG_CURSOR) {
+ logCursor("InsertionPointCursorController", "end cursor drag");
+ }
+ mIsDraggingCursor = false;
+ // Hide the magnifier and set the handle to be hidden after a delay.
+ getHandle().dismissMagnifier();
+ getHandle().hideAfterDelay();
+ // We're no longer dragging, so let the parent receive events.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(false);
+ }
public void show() {
getHandle().show();
@@ -5725,15 +5805,19 @@
final long durationSinceCutOrCopy =
SystemClock.uptimeMillis() - TextView.sLastCutCopyOrTextChangedTime;
- // Cancel the single tap delayed runnable.
- if (mInsertionActionModeRunnable != null
- && (mTouchState.isMultiTap() || isCursorInsideEasyCorrectionSpan())) {
- mTextView.removeCallbacks(mInsertionActionModeRunnable);
+ if (mInsertionActionModeRunnable != null) {
+ if (mIsDraggingCursor
+ || mTouchState.isMultiTap()
+ || isCursorInsideEasyCorrectionSpan()) {
+ // Cancel the runnable for showing the floating toolbar.
+ mTextView.removeCallbacks(mInsertionActionModeRunnable);
+ }
}
- // Prepare and schedule the single tap runnable to run exactly after the double tap
- // timeout has passed.
- if (!mTouchState.isMultiTap()
+ // If the user recently performed a Cut or Copy action, we want to show the floating
+ // toolbar even for a single tap.
+ if (!mIsDraggingCursor
+ && !mTouchState.isMultiTap()
&& !isCursorInsideEasyCorrectionSpan()
&& (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION_MS)) {
if (mTextActionMode == null) {
@@ -5751,7 +5835,9 @@
}
}
- getHandle().hideAfterDelay();
+ if (!mIsDraggingCursor) {
+ getHandle().hideAfterDelay();
+ }
if (mSelectionModifierCursorController != null) {
mSelectionModifierCursorController.hide();
@@ -5797,7 +5883,7 @@
@Override
public boolean isCursorBeingModified() {
- return mHandle != null && mHandle.isDragging();
+ return mIsDraggingCursor || (mHandle != null && mHandle.isDragging());
}
@Override
@@ -5959,7 +6045,6 @@
case MotionEvent.ACTION_MOVE:
final ViewConfiguration viewConfig = ViewConfiguration.get(
mTextView.getContext());
- final int touchSlop = viewConfig.getScaledTouchSlop();
if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
final float deltaX = eventX - mTouchState.getLastDownX();
@@ -5973,6 +6058,7 @@
}
if (mHaventMovedEnoughToStartDrag) {
// We don't start dragging until the user has moved enough.
+ int touchSlop = viewConfig.getScaledTouchSlop();
mHaventMovedEnoughToStartDrag =
distanceSquared <= touchSlop * touchSlop;
}
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index f880939..3798d00 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -55,6 +55,8 @@
private int mMultiTapStatus = MultiTapStatus.NONE;
private boolean mMultiTapInSameArea;
+ private boolean mMovedEnoughForDrag;
+
public float getLastDownX() {
return mLastDownX;
}
@@ -88,10 +90,14 @@
return isMultiTap() && mMultiTapInSameArea;
}
+ public boolean isMovedEnoughForDrag() {
+ return mMovedEnoughForDrag;
+ }
+
/**
* Updates the state based on the new event.
*/
- public void update(MotionEvent event, ViewConfiguration viewConfiguration) {
+ public void update(MotionEvent event, ViewConfiguration config) {
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
@@ -105,11 +111,8 @@
} else {
mMultiTapStatus = MultiTapStatus.TRIPLE_CLICK;
}
- final float deltaX = event.getX() - mLastDownX;
- final float deltaY = event.getY() - mLastDownY;
- final int distanceSquared = (int) ((deltaX * deltaX) + (deltaY * deltaY));
- int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop();
- mMultiTapInSameArea = distanceSquared < doubleTapSlop * doubleTapSlop;
+ mMultiTapInSameArea = isDistanceWithin(mLastDownX, mLastDownY,
+ event.getX(), event.getY(), config.getScaledDoubleTapSlop());
if (TextView.DEBUG_CURSOR) {
String status = isDoubleTap() ? "double" : "triple";
String inSameArea = mMultiTapInSameArea ? "in same area" : "not in same area";
@@ -125,6 +128,7 @@
}
mLastDownX = event.getX();
mLastDownY = event.getY();
+ mMovedEnoughForDrag = false;
} else if (action == MotionEvent.ACTION_UP) {
if (TextView.DEBUG_CURSOR) {
logCursor("EditorTouchState", "ACTION_UP");
@@ -132,6 +136,23 @@
mLastUpX = event.getX();
mLastUpY = event.getY();
mLastUpMillis = event.getEventTime();
+ mMovedEnoughForDrag = false;
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ mMovedEnoughForDrag = !isDistanceWithin(mLastDownX, mLastDownY,
+ event.getX(), event.getY(), config.getScaledTouchSlop());
}
}
+
+ /**
+ * Returns true if the distance between the given coordinates is <= to the specified max.
+ * This is useful to be able to determine e.g. when the user's touch has moved enough in
+ * order to be considered a drag (no longer within touch slop).
+ */
+ public static boolean isDistanceWithin(float x1, float y1, float x2, float y2,
+ int maxDistance) {
+ float deltaX = x2 - x1;
+ float deltaY = y2 - y1;
+ float distanceSquared = (deltaX * deltaX) + (deltaY * deltaY);
+ return distanceSquared <= maxDistance * maxDistance;
+ }
}
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index d44fbd7..3e26f63 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityNodeInfo;
/**
@@ -38,19 +39,19 @@
* guide.</p>
*
* <p><strong>XML attributes</strong></p>
- * <p>
- * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
- * {@link android.R.styleable#Button Button Attributes},
- * {@link android.R.styleable#TextView TextView Attributes},
+ * <p>
+ * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
+ * {@link android.R.styleable#Button Button Attributes},
+ * {@link android.R.styleable#TextView TextView Attributes},
* {@link android.R.styleable#View View Attributes}
* </p>
*/
public class RadioButton extends CompoundButton {
-
+
public RadioButton(Context context) {
this(context, null);
}
-
+
public RadioButton(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
}
@@ -81,4 +82,20 @@
public CharSequence getAccessibilityClassName() {
return RadioButton.class.getName();
}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (getParent() instanceof RadioGroup) {
+ RadioGroup radioGroup = (RadioGroup) getParent();
+ if (radioGroup.getOrientation() == LinearLayout.HORIZONTAL) {
+ info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(0, 1,
+ radioGroup.getIndexWithinVisibleButtons(this), 1, false, isChecked()));
+ } else {
+ info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(
+ radioGroup.getIndexWithinVisibleButtons(this), 1, 0, 1,
+ false, isChecked()));
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index c62c16c..90bc0a3 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -18,6 +18,7 @@
import android.annotation.IdRes;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
@@ -26,6 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStructure;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -93,6 +95,7 @@
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
}
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
// retrieve selected radio button as requested by the user in the
// XML layout file
@@ -475,4 +478,50 @@
}
return null;
}
-}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (this.getOrientation() == HORIZONTAL) {
+ info.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(1,
+ getVisibleChildCount(), false,
+ AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+ } else {
+ info.setCollectionInfo(
+ AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildCount(),
+ 1, false,
+ AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+ }
+ }
+
+ private int getVisibleChildCount() {
+ int count = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (this.getChildAt(i) instanceof RadioButton) {
+ if (((RadioButton) this.getChildAt(i)).getVisibility() == VISIBLE) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ int getIndexWithinVisibleButtons(@Nullable View child) {
+ if (!(child instanceof RadioButton)) {
+ return -1;
+ }
+ int index = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (this.getChildAt(i) instanceof RadioButton) {
+ RadioButton radioButton = (RadioButton) this.getChildAt(i);
+ if (radioButton == child) {
+ return index;
+ }
+ if (radioButton.getVisibility() == VISIBLE) {
+ index++;
+ }
+ }
+ }
+ return -1;
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7cec440..a6304b1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3575,7 +3575,6 @@
@Override
public void onCancel() {
cancel(true);
- mCancelSignal.setOnCancelListener(null);
}
private CancellationSignal startTaskOnExecutor(Executor executor) {
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index ac2336c..fbd29ba 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -309,6 +309,9 @@
// Refresh display with current params
refreshDrawableState();
+ // Default state is derived from on/off-text, so state has to be updated when on/off-text
+ // are updated.
+ setDefaultStateDescritption();
setChecked(isChecked());
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ee169f2..43d9895 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10870,6 +10870,10 @@
if (mEditor != null) {
mEditor.onTouchEvent(event);
+ if (mEditor.mInsertionPointCursorController != null
+ && mEditor.mInsertionPointCursorController.isCursorBeingModified()) {
+ return true;
+ }
if (mEditor.mSelectionModifierCursorController != null
&& mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
return true;
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index bdc2f9a..8f2133f 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -401,6 +401,7 @@
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
+ params.setFitIgnoreVisibility(true);
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index d47405b..59e0c16 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -58,6 +58,9 @@
mTextOff = a.getText(com.android.internal.R.styleable.ToggleButton_textOff);
mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.ToggleButton_disabledAlpha, 0.5f);
syncTextState();
+ // Default state is derived from on/off-text, so state has to be updated when on/off-text
+ // are updated.
+ setDefaultStateDescritption();
a.recycle();
}
diff --git a/telephony/java/com/android/ims/internal/uce/common/CapInfo.aidl b/core/java/com/android/ims/internal/uce/common/CapInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/CapInfo.aidl
rename to core/java/com/android/ims/internal/uce/common/CapInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/CapInfo.java
rename to core/java/com/android/ims/internal/uce/common/CapInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/common/StatusCode.aidl b/core/java/com/android/ims/internal/uce/common/StatusCode.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/StatusCode.aidl
rename to core/java/com/android/ims/internal/uce/common/StatusCode.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/common/StatusCode.java b/core/java/com/android/ims/internal/uce/common/StatusCode.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/StatusCode.java
rename to core/java/com/android/ims/internal/uce/common/StatusCode.java
diff --git a/telephony/java/com/android/ims/internal/uce/common/UceLong.aidl b/core/java/com/android/ims/internal/uce/common/UceLong.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/UceLong.aidl
rename to core/java/com/android/ims/internal/uce/common/UceLong.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/common/UceLong.java b/core/java/com/android/ims/internal/uce/common/UceLong.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/UceLong.java
rename to core/java/com/android/ims/internal/uce/common/UceLong.java
diff --git a/telephony/java/com/android/ims/internal/uce/options/IOptionsListener.aidl b/core/java/com/android/ims/internal/uce/options/IOptionsListener.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/IOptionsListener.aidl
rename to core/java/com/android/ims/internal/uce/options/IOptionsListener.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/IOptionsService.aidl b/core/java/com/android/ims/internal/uce/options/IOptionsService.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/IOptionsService.aidl
rename to core/java/com/android/ims/internal/uce/options/IOptionsService.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCapInfo.aidl b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCapInfo.aidl
rename to core/java/com/android/ims/internal/uce/options/OptionsCapInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCapInfo.java b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
rename to core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCmdId.aidl b/core/java/com/android/ims/internal/uce/options/OptionsCmdId.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCmdId.aidl
rename to core/java/com/android/ims/internal/uce/options/OptionsCmdId.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCmdId.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdId.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCmdId.java
rename to core/java/com/android/ims/internal/uce/options/OptionsCmdId.java
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCmdStatus.aidl b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCmdStatus.aidl
rename to core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
rename to core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsSipResponse.aidl b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsSipResponse.aidl
rename to core/java/com/android/ims/internal/uce/options/OptionsSipResponse.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsSipResponse.java b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
rename to core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl b/core/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl
rename to core/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/IPresenceService.aidl b/core/java/com/android/ims/internal/uce/presence/IPresenceService.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/IPresenceService.aidl
rename to core/java/com/android/ims/internal/uce/presence/IPresenceService.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCapInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCapInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresCapInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCapInfo.java b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCapInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCmdId.aidl b/core/java/com/android/ims/internal/uce/presence/PresCmdId.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCmdId.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresCmdId.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCmdId.java b/core/java/com/android/ims/internal/uce/presence/PresCmdId.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCmdId.java
rename to core/java/com/android/ims/internal/uce/presence/PresCmdId.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCmdStatus.aidl b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCmdStatus.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresCmdStatus.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCmdStatus.java b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
rename to core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.aidl b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
rename to core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresResInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresResInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresResInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresResInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresResInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresResInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresResInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresRlmiInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresRlmiInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresServiceInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresServiceInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresServiceInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresServiceInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresServiceInfo.java b/core/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresSipResponse.aidl b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresSipResponse.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresSipResponse.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresSipResponse.java b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresSipResponse.java
rename to core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresSubscriptionState.aidl b/core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresSubscriptionState.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java b/core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
rename to core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresTupleInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresTupleInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresTupleInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresTupleInfo.java b/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl b/core/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl
rename to core/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl b/core/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
rename to core/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
rename to core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java b/core/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
rename to core/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 0b15cd0..3fdedc8 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -17,6 +17,7 @@
package com.android.internal.accessibility;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.util.ArrayUtils.convertToLongArray;
@@ -34,6 +35,7 @@
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.os.UserHandle;
import android.os.Vibrator;
@@ -52,11 +54,12 @@
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.Collections;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
- * Class to help manage the accessibility shortcut
+ * Class to help manage the accessibility shortcut key
*/
public class AccessibilityShortcutController {
private static final String TAG = "AccessibilityShortcutController";
@@ -66,6 +69,8 @@
new ComponentName("com.android.server.accessibility", "ColorInversion");
public static final ComponentName DALTONIZER_COMPONENT_NAME =
new ComponentName("com.android.server.accessibility", "Daltonizer");
+ public static final String MAGNIFICATION_CONTROLLER_NAME =
+ "com.android.server.accessibility.MagnificationController";
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -84,26 +89,6 @@
public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider();
/**
- * Get the component name string for the service or feature currently assigned to the
- * accessiblity shortcut
- *
- * @param context A valid context
- * @param userId The user ID of interest
- * @return The flattened component name string of the service selected by the user, or the
- * string for the default service if the user has not made a selection
- */
- public static String getTargetServiceComponentNameString(
- Context context, int userId) {
- final String currentShortcutServiceId = Settings.Secure.getStringForUser(
- context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- userId);
- if (currentShortcutServiceId != null) {
- return currentShortcutServiceId;
- }
- return context.getString(R.string.config_defaultAccessibilityService);
- }
-
- /**
* @return An immutable map from dummy component names to feature info for toggling a framework
* feature
*/
@@ -163,7 +148,7 @@
/**
* Check if the shortcut is available.
*
- * @param onLockScreen Whether or not the phone is currently locked.
+ * @param phoneLocked Whether or not the phone is currently locked.
*
* @return {@code true} if the shortcut is available
*/
@@ -172,8 +157,7 @@
}
public void onSettingsChanged() {
- final boolean haveValidService =
- !TextUtils.isEmpty(getTargetServiceComponentNameString(mContext, mUserId));
+ final boolean hasShortcutTarget = hasShortcutTarget();
final ContentResolver cr = mContext.getContentResolver();
final boolean enabled = Settings.Secure.getIntForUser(
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1, mUserId) == 1;
@@ -183,7 +167,7 @@
mEnabledOnLockScreen = Settings.Secure.getIntForUser(
cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
dialogAlreadyShown, mUserId) == 1;
- mIsShortcutEnabled = enabled && haveValidService;
+ mIsShortcutEnabled = enabled && hasShortcutTarget;
}
/**
@@ -205,7 +189,6 @@
vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES);
}
-
if (dialogAlreadyShown == 0) {
// The first time, we show a warning rather than toggle the service to give the user a
// chance to turn off this feature before stuff gets enabled.
@@ -229,32 +212,44 @@
mAlertDialog.dismiss();
mAlertDialog = null;
}
-
- // Show a toast alerting the user to what's happening
- final String serviceName = getShortcutFeatureDescription(false /* no summary */);
- if (serviceName == null) {
- Slog.e(TAG, "Accessibility shortcut set to invalid service");
- return;
- }
- // For accessibility services, show a toast explaining what we're doing.
- final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
- if (serviceInfo != null) {
- String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo)
- ? R.string.accessibility_shortcut_disabling_service
- : R.string.accessibility_shortcut_enabling_service);
- String toastMessage = String.format(toastMessageFormatString, serviceName);
- Toast warningToast = mFrameworkObjectProvider.makeToastFromText(
- mContext, toastMessage, Toast.LENGTH_LONG);
- warningToast.getWindowParams().privateFlags |=
- WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- warningToast.show();
- }
-
+ showToast();
mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext)
.performAccessibilityShortcut();
}
}
+ /**
+ * Show toast if current assigned shortcut target is an accessibility service and its target
+ * sdk version is less than or equal to Q, or greater than Q and does not request
+ * accessibility button.
+ */
+ private void showToast() {
+ final AccessibilityServiceInfo serviceInfo = getInfoForTargetService();
+ if (serviceInfo == null) {
+ return;
+ }
+ final String serviceName = getShortcutFeatureDescription(/* no summary */ false);
+ if (serviceName == 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;
+ }
+ // For accessibility services, show a toast explaining what we're doing.
+ String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo)
+ ? R.string.accessibility_shortcut_disabling_service
+ : R.string.accessibility_shortcut_enabling_service);
+ String toastMessage = String.format(toastMessageFormatString, serviceName);
+ Toast warningToast = mFrameworkObjectProvider.makeToastFromText(
+ mContext, toastMessage, Toast.LENGTH_LONG);
+ warningToast.getWindowParams().privateFlags |=
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ warningToast.show();
+ }
+
private AlertDialog createShortcutWarningDialog(int userId) {
final String serviceDescription = getShortcutFeatureDescription(true /* Include summary */);
@@ -288,25 +283,21 @@
}
private AccessibilityServiceInfo getInfoForTargetService() {
- final String currentShortcutServiceString = getTargetServiceComponentNameString(
- mContext, UserHandle.USER_CURRENT);
- if (currentShortcutServiceString == null) {
+ final ComponentName targetComponentName = getShortcutTargetComponentName();
+ if (targetComponentName == null) {
return null;
}
AccessibilityManager accessibilityManager =
mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext);
return accessibilityManager.getInstalledServiceInfoWithComponentName(
- ComponentName.unflattenFromString(currentShortcutServiceString));
+ targetComponentName);
}
private String getShortcutFeatureDescription(boolean includeSummary) {
- final String currentShortcutServiceString = getTargetServiceComponentNameString(
- mContext, UserHandle.USER_CURRENT);
- if (currentShortcutServiceString == null) {
+ final ComponentName targetComponentName = getShortcutTargetComponentName();
+ if (targetComponentName == null) {
return null;
}
- final ComponentName targetComponentName =
- ComponentName.unflattenFromString(currentShortcutServiceString);
final ToggleableFrameworkFeatureInfo frameworkFeatureInfo =
getFrameworkShortcutFeaturesMap().get(targetComponentName);
if (frameworkFeatureInfo != null) {
@@ -372,6 +363,36 @@
}
/**
+ * Returns {@code true} if any shortcut targets were assigned to accessibility shortcut key.
+ */
+ private boolean hasShortcutTarget() {
+ // AccessibilityShortcutController is initialized earlier than AccessibilityManagerService.
+ // AccessibilityManager#getAccessibilityShortcutTargets may not return correct shortcut
+ // targets during boot. Needs to read settings directly here.
+ String shortcutTargets = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, mUserId);
+ if (TextUtils.isEmpty(shortcutTargets)) {
+ shortcutTargets = mContext.getString(R.string.config_defaultAccessibilityService);
+ }
+ return !TextUtils.isEmpty(shortcutTargets);
+ }
+
+ /**
+ * Gets the component name of the shortcut target.
+ *
+ * @return The component name, or null if it's assigned by multiple targets.
+ */
+ private ComponentName getShortcutTargetComponentName() {
+ final List<String> shortcutTargets = mFrameworkObjectProvider
+ .getAccessibilityManagerInstance(mContext)
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY);
+ if (shortcutTargets.size() != 1) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(shortcutTargets.get(0));
+ }
+
+ /**
* Class to wrap TextToSpeech for shortcut dialog spoken feedback.
*/
private class TtsPrompt implements TextToSpeech.OnInitListener {
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
new file mode 100644
index 0000000..08022e9
--- /dev/null
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -0,0 +1,163 @@
+/*
+ * 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.app;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.UserHandle;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.PagerAdapter;
+import com.android.internal.widget.ViewPager;
+
+import java.util.Objects;
+
+/**
+ * Skeletal {@link PagerAdapter} implementation of a work or personal profile page for
+ * intent resolution (including share sheet).
+ */
+public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
+
+ static final int PROFILE_PERSONAL = 0;
+ static final int PROFILE_WORK = 1;
+ @IntDef({PROFILE_PERSONAL, PROFILE_WORK})
+ @interface Profile {}
+
+ private final Context mContext;
+ private int mCurrentPage;
+
+ AbstractMultiProfilePagerAdapter(Context context, int currentPage) {
+ mContext = Objects.requireNonNull(context);
+ mCurrentPage = currentPage;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Sets this instance of this class as {@link ViewPager}'s {@link PagerAdapter} and sets
+ * an {@link ViewPager.OnPageChangeListener} where it keeps track of the currently displayed
+ * page and rebuilds the list.
+ */
+ void setupViewPager(ViewPager viewPager) {
+ viewPager.setCurrentItem(mCurrentPage);
+ viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ mCurrentPage = position;
+ getActiveListAdapter().rebuildList();
+ }
+ });
+ viewPager.setAdapter(this);
+ }
+
+ @Override
+ public ViewGroup instantiateItem(ViewGroup container, int position) {
+ final ProfileDescriptor profileDescriptor = getItem(position);
+ setupListAdapter(position);
+ container.addView(profileDescriptor.rootView);
+ return profileDescriptor.rootView;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object view) {
+ container.removeView((View) view);
+ }
+
+ @Override
+ public int getCount() {
+ return getItemCount();
+ }
+
+ protected int getCurrentPage() {
+ return mCurrentPage;
+ }
+
+ UserHandle getCurrentUserHandle() {
+ return getActiveListAdapter().mResolverListController.getUserHandle();
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return null;
+ }
+
+ /**
+ * Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
+ * <ul>
+ * <li>For a device with only one user, <code>pageIndex</code> value of
+ * <code>0</code> would return the personal profile {@link ProfileDescriptor}.</li>
+ * <li>For a device with a work profile, <code>pageIndex</code> value of <code>0</code> would
+ * return the personal profile {@link ProfileDescriptor}, and <code>pageIndex</code> value of
+ * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li>
+ * </ul>
+ */
+ abstract ProfileDescriptor getItem(int pageIndex);
+
+ /**
+ * Returns the number of {@link ProfileDescriptor} objects.
+ * <p>For a normal consumer device with only one user returns <code>1</code>.
+ * <p>For a device with a work profile returns <code>2</code>.
+ */
+ abstract int getItemCount();
+
+ /**
+ * Responsible for assigning an adapter to the list view for the relevant page, specified by
+ * <code>pageIndex</code>, and other list view-related initialization procedures.
+ */
+ abstract void setupListAdapter(int pageIndex);
+
+ /**
+ * Returns the adapter of the list view for the relevant page specified by
+ * <code>pageIndex</code>.
+ * <p>This method is meant to be implemented with an implementation-specific return type
+ * depending on the adapter type.
+ */
+ abstract Object getAdapterForIndex(int pageIndex);
+
+ @VisibleForTesting
+ public abstract ResolverListAdapter getActiveListAdapter();
+
+ /**
+ * If this is a device with a work profile, returns the {@link ResolverListAdapter} instance
+ * of the profile that is not the active one. Otherwise returns {@code null}. For example,
+ * if the share sheet is launched in the work profile, this method returns the personal
+ * profile {@link ResolverListAdapter}.
+ * @see #getActiveListAdapter()
+ */
+ @VisibleForTesting
+ public abstract @Nullable ResolverListAdapter getInactiveListAdapter();
+
+ abstract Object getCurrentRootAdapter();
+
+ abstract ViewGroup getCurrentAdapterView();
+
+ protected class ProfileDescriptor {
+ final ViewGroup rootView;
+ ProfileDescriptor(ViewGroup rootView) {
+ this.rootView = rootView;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index bb7e4d5..eb74612 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -263,6 +263,7 @@
mHandler.removeMessages(RANKER_SERVICE_RESULT);
mHandler.removeMessages(RANKER_RESULT_TIMEOUT);
afterCompute();
+ mAfterCompute = null;
}
private boolean isDefaultBrowser(ResolveInfo ri) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 183e9a6..a86b566 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -98,6 +98,7 @@
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.WindowInsets;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
@@ -110,6 +111,7 @@
import com.android.internal.app.ResolverListAdapter.ViewHolder;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.MultiDisplayResolveInfo;
import com.android.internal.app.chooser.NotSelectableTargetInfo;
import com.android.internal.app.chooser.SelectableTargetInfo;
import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
@@ -118,7 +120,6 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.ImageUtils;
import com.android.internal.widget.GridLayoutManager;
import com.android.internal.widget.RecyclerView;
import com.android.internal.widget.ResolverDrawerLayout;
@@ -173,7 +174,6 @@
@VisibleForTesting
public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
- private static final int SINGLE_CELL_SPAN_SIZE = 1;
private boolean mIsAppPredictorComponentAvailable;
private AppPredictor mAppPredictor;
@@ -229,9 +229,6 @@
private long mQueriedTargetServicesTimeMs;
private long mQueriedSharingShortcutsTimeMs;
- private RecyclerView mRecyclerView;
- private ChooserListAdapter mChooserListAdapter;
- private ChooserGridAdapter mChooserGridAdapter;
private int mChooserRowServiceSpacing;
private int mCurrAvailableWidth = 0;
@@ -265,6 +262,9 @@
private ContentPreviewCoordinator mPreviewCoord;
+ @VisibleForTesting
+ protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
+
private class ContentPreviewCoordinator {
private static final int IMAGE_FADE_IN_MILLIS = 150;
private static final int IMAGE_LOAD_TIMEOUT = 1;
@@ -344,7 +344,9 @@
mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
- final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
+ int size = getResources().getDimensionPixelSize(
+ R.dimen.chooser_preview_image_max_dimen);
+ final Bitmap bmp = loadThumbnail(uri, new Size(size, size));
final Message msg = Message.obtain();
msg.what = IMAGE_LOAD_INTO_VIEW;
msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
@@ -362,9 +364,7 @@
Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
+ " within " + mImageLoadTimeoutMillis + "ms.");
collapseParentView();
- if (mChooserGridAdapter != null) {
- mChooserGridAdapter.hideContentPreview();
- }
+ hideContentPreview();
mHideParentOnFail = false;
}
}
@@ -431,13 +431,14 @@
logDirectShareTargetReceived(
MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
sendVoiceChoicesIfNeeded();
- mChooserListAdapter.completeServiceTargetLoading();
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter()
+ .completeServiceTargetLoading();
}
}
@Override
public void handleMessage(Message msg) {
- if (mChooserListAdapter == null || isDestroyed()) {
+ if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null || isDestroyed()) {
return;
}
@@ -452,8 +453,10 @@
break;
}
if (sri.resultTargets != null) {
- mChooserListAdapter.addServiceResults(sri.originalTarget,
- sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
+ // TODO(arangelov): Instead of using getCurrentListAdapter(), pass the
+ // profileId as part of the message.
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
+ sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
}
unbindService(sri.connection);
sri.connection.destroy();
@@ -476,15 +479,15 @@
Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
}
- mChooserListAdapter.refreshListView();
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().refreshListView();
break;
case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
if (resultInfo.resultTargets != null) {
- mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
- resultInfo.resultTargets, msg.arg1);
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
+ resultInfo.originalTarget, resultInfo.resultTargets, msg.arg1);
}
break;
@@ -504,6 +507,7 @@
protected void onCreate(Bundle savedInstanceState) {
final long intentReceivedTime = System.currentTimeMillis();
// This is the only place this value is being set. Effectively final.
+ //TODO(arangelov) - should there be a mIsAppPredictorComponentAvailable flag for work tab?
mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
mIsSuccessfullySelected = false;
@@ -638,19 +642,24 @@
if (appPredictor != null) {
mDirectShareAppTargetCache = new HashMap<>();
mAppPredictorCallback = resultList -> {
+ //TODO(arangelov) Take care of edge case when callback called after swiping tabs
if (isFinishing() || isDestroyed()) {
return;
}
- if (mChooserListAdapter.getCount() == 0) {
+ if (mChooserMultiProfilePagerAdapter.getActiveListAdapter().getCount() == 0) {
return;
}
if (resultList.isEmpty()) {
// APS may be disabled, so try querying targets ourselves.
- queryDirectShareTargets(mChooserListAdapter, true);
+ //TODO(arangelov) queryDirectShareTargets indirectly uses mIntents.
+ // Investigate implications for work tab.
+ queryDirectShareTargets(
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter(), true);
return;
}
final List<DisplayResolveInfo> driList =
- getDisplayResolveInfos(mChooserListAdapter);
+ getDisplayResolveInfos(
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter());
final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
new ArrayList<>();
for (AppTarget appTarget : resultList) {
@@ -684,21 +693,22 @@
final float chooserHeaderScrollElevation =
getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
- mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
- public void onScrollStateChanged(RecyclerView view, int scrollState) {
- }
-
- public void onScrolled(RecyclerView view, int dx, int dy) {
- if (view.getChildCount() > 0) {
- View child = view.getLayoutManager().findViewByPosition(0);
- if (child == null || child.getTop() < 0) {
- chooserHeader.setElevation(chooserHeaderScrollElevation);
- return;
+ mChooserMultiProfilePagerAdapter.getCurrentAdapterView().addOnScrollListener(
+ new RecyclerView.OnScrollListener() {
+ public void onScrollStateChanged(RecyclerView view, int scrollState) {
}
- }
- chooserHeader.setElevation(defaultElevation);
- }
+ public void onScrolled(RecyclerView view, int dx, int dy) {
+ if (view.getChildCount() > 0) {
+ View child = view.getLayoutManager().findViewByPosition(0);
+ if (child == null || child.getTop() < 0) {
+ chooserHeader.setElevation(chooserHeaderScrollElevation);
+ return;
+ }
+ }
+
+ chooserHeader.setElevation(defaultElevation);
+ }
});
mResolverDrawerLayout.setOnCollapsedChangedListener(
@@ -738,6 +748,71 @@
return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
}
+ @Override
+ protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed) {
+ if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
+ mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
+ initialIntents, rList, filterLastUsed);
+ } else {
+ mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
+ initialIntents, rList, filterLastUsed);
+ }
+ return mChooserMultiProfilePagerAdapter;
+ }
+
+ private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed) {
+ ChooserGridAdapter adapter = createChooserGridAdapter(
+ /* context */ this,
+ /* payloadIntents */ mIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ mUseLayoutForBrowsables,
+ /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+ return new ChooserMultiProfilePagerAdapter(
+ /* context */ this,
+ adapter);
+ }
+
+ private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed) {
+ ChooserGridAdapter personalAdapter = createChooserGridAdapter(
+ /* context */ this,
+ /* payloadIntents */ mIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ mUseLayoutForBrowsables,
+ /* userHandle */ getPersonalProfileUserHandle());
+ ChooserGridAdapter workAdapter = createChooserGridAdapter(
+ /* context */ this,
+ /* payloadIntents */ mIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ mUseLayoutForBrowsables,
+ /* userHandle */ getWorkProfileUserHandle());
+ return new ChooserMultiProfilePagerAdapter(
+ /* context */ this,
+ personalAdapter,
+ workAdapter,
+ /* defaultProfile */ getCurrentProfile());
+ }
+
+ @Override
+ protected boolean postRebuildList(boolean rebuildCompleted) {
+ updateContentPreview();
+ return postRebuildListInternal(rebuildCompleted);
+ }
+
/**
* Returns true if app prediction service is defined and the component exists on device.
*/
@@ -776,7 +851,7 @@
* set up)
*/
protected boolean isWorkProfile() {
- return ((UserManager) getSystemService(Context.USER_SERVICE))
+ return getSystemService(UserManager.class)
.getUserInfo(UserHandle.myUserId()).isManagedProfile();
}
@@ -785,12 +860,21 @@
return new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
- mAdapter.handlePackagesChanged();
- updateProfileViewButton();
+ handlePackagesChanged();
}
};
}
+ /**
+ * Update UI to reflect changes in data.
+ */
+ public void handlePackagesChanged() {
+ // TODO(arangelov): Dispatch this to all adapters when we have the helper methods
+ // in a follow-up CL
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
+ updateProfileViewButton();
+ }
+
private void onCopyButtonClicked(View v) {
Intent targetIntent = getTargetIntent();
if (targetIntent == null) {
@@ -875,6 +959,12 @@
}
}
+ private ViewGroup createContentPreviewView(ViewGroup parent) {
+ Intent targetIntent = getTargetIntent();
+ int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
+ return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
+ }
+
private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) {
ViewGroup layout = null;
@@ -1239,36 +1329,14 @@
}
@Override
- public void onPrepareAdapterView(ResolverListAdapter adapter, boolean isVisible) {
- mRecyclerView = findViewById(R.id.resolver_list);
- if (!isVisible) {
- mRecyclerView.setVisibility(View.GONE);
- return;
- }
- mRecyclerView.setVisibility(View.VISIBLE);
+ public void onPrepareAdapterView(ResolverListAdapter adapter) {
+ mChooserMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE);
if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
- mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
+ /* origTarget */ null,
+ Lists.newArrayList(mCallerChooserTargets),
TARGET_TYPE_DEFAULT);
}
- mChooserGridAdapter = new ChooserGridAdapter(mChooserListAdapter);
- GridLayoutManager glm = (GridLayoutManager) mRecyclerView.getLayoutManager();
- glm.setSpanCount(mChooserGridAdapter.getMaxTargetsPerRow());
- glm.setSpanSizeLookup(
- new GridLayoutManager.SpanSizeLookup() {
- @Override
- public int getSpanSize(int position) {
- return mChooserGridAdapter.getItemViewType(position)
- == ChooserGridAdapter.VIEW_TYPE_NORMAL
- ? SINGLE_CELL_SPAN_SIZE
- : glm.getSpanCount();
- }
- });
- }
-
- @Override
- protected boolean postRebuildList(boolean rebuildCompleted) {
- mChooserListAdapter = (ChooserListAdapter) mAdapter;
- return postRebuildListInternal(rebuildCompleted);
}
@Override
@@ -1293,17 +1361,30 @@
return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
}
- @Override
- public void showTargetDetails(ResolveInfo ri) {
- if (ri == null) {
+ void showTargetDetails(TargetInfo ti) {
+ if (ti == null) {
return;
}
-
- ComponentName name = ri.activityInfo.getComponentName();
+ ComponentName name = ti.getResolveInfo().activityInfo.getComponentName();
boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
- ResolverTargetActionsDialogFragment f =
- new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
- name, pinned);
+
+ ResolverTargetActionsDialogFragment f;
+
+ // For multiple targets, include info on all targets
+ if (ti instanceof MultiDisplayResolveInfo) {
+ MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti;
+ List<CharSequence> labels = new ArrayList<>();
+
+ for (TargetInfo innerInfo : mti.getTargets()) {
+ labels.add(innerInfo.getResolveInfo().loadLabel(getPackageManager()));
+ }
+ f = new ResolverTargetActionsDialogFragment(mti.getDisplayLabel(), name,
+ mti.getTargets(), labels);
+ } else {
+ f = new ResolverTargetActionsDialogFragment(
+ ti.getResolveInfo().loadLabel(getPackageManager()), name, pinned);
+ }
+
f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
}
@@ -1348,15 +1429,38 @@
@Override
public void startSelected(int which, boolean always, boolean filtered) {
- TargetInfo targetInfo = mChooserListAdapter.targetInfoForPosition(which, filtered);
+ ChooserListAdapter currentListAdapter =
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+ TargetInfo targetInfo = currentListAdapter
+ .targetInfoForPosition(which, filtered);
if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
return;
}
final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
+
+ if (targetInfo instanceof MultiDisplayResolveInfo) {
+ MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
+ if (!mti.hasSelected()) {
+ // Stacked apps get a disambiguation first
+ CharSequence[] labels = new CharSequence[mti.getTargets().size()];
+ int i = 0;
+ for (TargetInfo ti : mti.getTargets()) {
+ labels[i++] = ti.getResolveInfo().loadLabel(getPackageManager());
+ }
+ ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
+ targetInfo.getDisplayLabel(),
+ ((MultiDisplayResolveInfo) targetInfo), labels, which);
+
+ f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+ return;
+ }
+ }
+
super.startSelected(which, always, filtered);
- if (mChooserListAdapter.getCount() > 0) {
+
+ if (currentListAdapter.getCount() > 0) {
// Log the index of which type of target the user picked.
// Lower values mean the ranking was better.
int cat = 0;
@@ -1364,13 +1468,12 @@
int directTargetAlsoRanked = -1;
int numCallerProvided = 0;
HashedStringCache.HashResult directTargetHashed = null;
- switch (mChooserListAdapter.getPositionTargetType(which)) {
+ switch (currentListAdapter.getPositionTargetType(which)) {
case ChooserListAdapter.TARGET_SERVICE:
cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
// Log the package name + target name to answer the question if most users
// share to mostly the same person or to a bunch of different people.
- ChooserTarget target =
- mChooserListAdapter.getChooserTargetForValue(value);
+ ChooserTarget target = currentListAdapter.getChooserTargetForValue(value);
directTargetHashed = HashedStringCache.getInstance().hashString(
this,
TAG,
@@ -1386,8 +1489,8 @@
case ChooserListAdapter.TARGET_CALLER:
case ChooserListAdapter.TARGET_STANDARD:
cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
- value -= mChooserListAdapter.getSelectableServiceTargetCount();
- numCallerProvided = mChooserListAdapter.getCallerTargetCount();
+ value -= currentListAdapter.getSelectableServiceTargetCount();
+ numCallerProvided = currentListAdapter.getCallerTargetCount();
break;
case ChooserListAdapter.TARGET_STANDARD_AZ:
// A-Z targets are unranked standard targets; we use -1 to mark that they
@@ -1429,11 +1532,13 @@
private int getRankedPosition(SelectableTargetInfo targetInfo) {
String targetPackageName =
targetInfo.getChooserTarget().getComponentName().getPackageName();
- int maxRankedResults = Math.min(mChooserListAdapter.mDisplayList.size(),
- MAX_LOG_RANK_POSITION);
+ ChooserListAdapter currentListAdapter =
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+ int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
+ MAX_LOG_RANK_POSITION);
for (int i = 0; i < maxRankedResults; i++) {
- if (mChooserListAdapter.mDisplayList.get(i)
+ if (currentListAdapter.mDisplayList.get(i)
.getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
return i;
}
@@ -1577,6 +1682,7 @@
}
}
// Default to just querying ShortcutManager if AppPredictor not present.
+ //TODO(arangelov) we're using mIntents here, investicate possible implications on work tab
final IntentFilter filter = getTargetIntentFilter();
if (filter == null) {
return;
@@ -1584,6 +1690,7 @@
final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
AsyncTask.execute(() -> {
+ //TODO(arangelov) use the selected probile tab's ShortcutManager
ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
sendShareShortcutInfoList(resultList, driList, null);
@@ -1779,9 +1886,11 @@
final ResolveInfo ri = info.getResolveInfo();
Intent targetIntent = getTargetIntent();
if (ri != null && ri.activityInfo != null && targetIntent != null) {
- if (mAdapter != null) {
- mAdapter.updateModel(info.getResolvedComponentName());
- mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
+ ChooserListAdapter currentListAdapter =
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter();
+ if (currentListAdapter != null) {
+ currentListAdapter.updateModel(info.getResolvedComponentName());
+ currentListAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
targetIntent.getAction());
}
if (DEBUG) {
@@ -1956,8 +2065,9 @@
Intent targetIntent,
String referrerPackageName,
int launchedFromUid,
+ UserHandle userId,
AbstractResolverComparator resolverComparator) {
- super(context, pm, targetIntent, referrerPackageName, launchedFromUid,
+ super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
resolverComparator);
}
@@ -1980,17 +2090,18 @@
}
}
- @Override
- public ResolverListAdapter createAdapter(Context context, List<Intent> payloadIntents,
- Intent[] initialIntents, List<ResolveInfo> rList,
- boolean filterLastUsed, boolean useLayoutForBrowsables) {
- return new ChooserListAdapter(context, payloadIntents,
- initialIntents, rList, filterLastUsed, createListController(),
- useLayoutForBrowsables, this, this);
+ @VisibleForTesting
+ public ChooserGridAdapter createChooserGridAdapter(Context context,
+ List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
+ boolean filterLastUsed, boolean useLayoutForBrowsables, UserHandle userHandle) {
+ return new ChooserGridAdapter(
+ new ChooserListAdapter(context, payloadIntents, initialIntents, rList,
+ filterLastUsed, createListController(userHandle), useLayoutForBrowsables,
+ this, this));
}
@VisibleForTesting
- protected ResolverListController createListController() {
+ protected ResolverListController createListController(UserHandle userHandle) {
AppPredictor appPredictor = getAppPredictorForShareActivitesIfEnabled();
AbstractResolverComparator resolverComparator;
if (appPredictor != null) {
@@ -2008,6 +2119,7 @@
getTargetIntent(),
getReferrerPackageName(),
mLaunchedFromUid,
+ userHandle,
resolverComparator);
}
@@ -2018,7 +2130,7 @@
}
try {
- return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
+ return getContentResolver().loadThumbnail(uri, size, null);
} catch (IOException | NullPointerException | SecurityException ex) {
logContentPreviewWarning(uri);
}
@@ -2041,8 +2153,8 @@
}
private void handleScroll(View view, int x, int y, int oldx, int oldy) {
- if (mChooserGridAdapter != null) {
- mChooserGridAdapter.handleScroll(view, y, oldy);
+ if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
+ mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().handleScroll(view, y, oldy);
}
}
@@ -2053,53 +2165,61 @@
*/
private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
- if (mChooserGridAdapter == null || mRecyclerView == null) {
+ if (mChooserMultiProfilePagerAdapter == null) {
+ return;
+ }
+ RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getCurrentAdapterView();
+ ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
+ if (gridAdapter == null || recyclerView == null) {
return;
}
final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
- if (mChooserGridAdapter.consumeLayoutRequest()
- || mChooserGridAdapter.calculateChooserTargetWidth(availableWidth)
- || mRecyclerView.getAdapter() == null
+ if (gridAdapter.consumeLayoutRequest()
+ || gridAdapter.calculateChooserTargetWidth(availableWidth)
+ || recyclerView.getAdapter() == null
|| availableWidth != mCurrAvailableWidth) {
mCurrAvailableWidth = availableWidth;
- mRecyclerView.setAdapter(mChooserGridAdapter);
- ((GridLayoutManager) mRecyclerView.getLayoutManager())
- .setSpanCount(mChooserGridAdapter.getMaxTargetsPerRow());
+ recyclerView.setAdapter(gridAdapter);
+ ((GridLayoutManager) recyclerView.getLayoutManager())
+ .setSpanCount(gridAdapter.getMaxTargetsPerRow());
getMainThreadHandler().post(() -> {
- if (mResolverDrawerLayout == null || mChooserGridAdapter == null) {
+ if (mResolverDrawerLayout == null || gridAdapter == null) {
return;
}
final int bottomInset = mSystemWindowInsets != null
? mSystemWindowInsets.bottom : 0;
int offset = bottomInset;
- int rowsToShow = mChooserGridAdapter.getContentPreviewRowCount()
- + mChooserGridAdapter.getProfileRowCount()
- + mChooserGridAdapter.getServiceTargetRowCount()
- + mChooserGridAdapter.getCallerAndRankedTargetRowCount();
+ int rowsToShow = gridAdapter.getProfileRowCount()
+ + gridAdapter.getServiceTargetRowCount()
+ + gridAdapter.getCallerAndRankedTargetRowCount();
// then this is most likely not a SEND_* action, so check
// the app target count
if (rowsToShow == 0) {
- rowsToShow = mChooserGridAdapter.getRowCount();
+ rowsToShow = gridAdapter.getRowCount();
}
// still zero? then use a default height and leave, which
// can happen when there are no targets to show
- if (rowsToShow == 0) {
+ if (rowsToShow == 0 && !shouldShowContentPreview()) {
offset += getResources().getDimensionPixelSize(
R.dimen.chooser_max_collapsed_height);
mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
return;
}
+ if (shouldShowContentPreview()) {
+ offset += findViewById(R.id.content_preview_container).getHeight();
+ }
+
int directShareHeight = 0;
rowsToShow = Math.min(4, rowsToShow);
- for (int i = 0, childCount = mRecyclerView.getChildCount();
+ for (int i = 0, childCount = recyclerView.getChildCount();
i < childCount && rowsToShow > 0; i++) {
- View child = mRecyclerView.getChildAt(i);
+ View child = recyclerView.getChildAt(i);
if (((GridLayoutManager.LayoutParams)
child.getLayoutParams()).getSpanIndex() != 0) {
continue;
@@ -2107,9 +2227,9 @@
int height = child.getHeight();
offset += height;
- if (mChooserGridAdapter.getTargetType(
- mRecyclerView.getChildAdapterPosition(child))
- == mChooserListAdapter.TARGET_SERVICE) {
+ if (gridAdapter.getTargetType(
+ recyclerView.getChildAdapterPosition(child))
+ == ChooserListAdapter.TARGET_SERVICE) {
directShareHeight = height;
}
rowsToShow--;
@@ -2145,13 +2265,13 @@
@Override // ResolverListCommunicator
public void onHandlePackagesChanged() {
mServicesRequested.clear();
- mAdapter.notifyDataSetChanged();
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
super.onHandlePackagesChanged();
}
@Override // SelectableTargetInfoCommunicator
public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
- return mChooserListAdapter.makePresentationGetter(info);
+ return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info);
}
@Override // SelectableTargetInfoCommunicator
@@ -2161,9 +2281,9 @@
@Override // ChooserListCommunicator
public int getMaxRankedTargets() {
- return mChooserGridAdapter == null
+ return mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() == null
? ChooserGridAdapter.MAX_TARGETS_PER_ROW_PORTRAIT
- : mChooserGridAdapter.getMaxTargetsPerRow();
+ : mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().getMaxTargetsPerRow();
}
@Override // ChooserListCommunicator
@@ -2173,20 +2293,21 @@
}
@Override
- public void onListRebuilt() {
- if (mChooserListAdapter.mDisplayList == null
- || mChooserListAdapter.mDisplayList.isEmpty()) {
- mChooserListAdapter.notifyDataSetChanged();
+ public void onListRebuilt(ResolverListAdapter listAdapter) {
+ ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
+ if (chooserListAdapter.mDisplayList == null
+ || chooserListAdapter.mDisplayList.isEmpty()) {
+ chooserListAdapter.notifyDataSetChanged();
} else {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
- mChooserListAdapter.updateAlphabeticalList();
+ chooserListAdapter.updateAlphabeticalList();
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
- mChooserListAdapter.notifyDataSetChanged();
+ chooserListAdapter.notifyDataSetChanged();
}
}.execute();
}
@@ -2202,14 +2323,14 @@
Log.d(TAG, "querying direct share targets from ShortcutManager");
}
- queryDirectShareTargets(mChooserListAdapter, false);
+ queryDirectShareTargets(chooserListAdapter, false);
}
if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
if (DEBUG) {
Log.d(TAG, "List built querying services");
}
- queryTargetServices(mChooserListAdapter);
+ queryTargetServices(chooserListAdapter);
}
}
@@ -2231,10 +2352,43 @@
return false;
}
+ private boolean shouldShowContentPreview() {
+ return mMultiProfilePagerAdapter.getActiveListAdapter().getCount() > 0
+ && isSendAction(getTargetIntent());
+ }
+
+ private void updateContentPreview() {
+ if (shouldShowContentPreview()) {
+ showContentPreview();
+ } else {
+ hideContentPreview();
+ }
+ }
+
+ private void showContentPreview() {
+ ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
+ contentPreviewContainer.setVisibility(View.VISIBLE);
+ ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
+ contentPreviewContainer.addView(contentPreviewView);
+ logActionShareWithPreview();
+ }
+
+ private void hideContentPreview() {
+ ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
+ contentPreviewContainer.removeAllViews();
+ contentPreviewContainer.setVisibility(View.GONE);
+ }
+
+ private void logActionShareWithPreview() {
+ Intent targetIntent = getTargetIntent();
+ int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
+ getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
+ .setSubtype(previewType));
+ }
+
/**
* Used to bind types of individual item including
* {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
- * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
* {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
* and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
*/
@@ -2250,8 +2404,8 @@
false/* always */, true/* filterd */));
itemView.setOnLongClickListener(v -> {
showTargetDetails(
- mChooserListAdapter.resolveInfoForPosition(
- mListPosition, true/* filtered */));
+ mChooserMultiProfilePagerAdapter.getActiveListAdapter()
+ .targetInfoForPosition(mListPosition, /* filtered */ true));
return true;
});
}
@@ -2259,6 +2413,29 @@
}
/**
+ * Intentionally override the {@link ResolverActivity} implementation as we only need that
+ * implementation for the intent resolver case.
+ */
+ @Override
+ protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+ return insets.consumeSystemWindowInsets();
+ }
+
+ /**
+ * Intentionally override the {@link ResolverActivity} implementation as we only need that
+ * implementation for the intent resolver case.
+ */
+ @Override
+ public void onButtonClick(View v) {}
+
+ /**
+ * Intentionally override the {@link ResolverActivity} implementation as we only need that
+ * implementation for the intent resolver case.
+ */
+ @Override
+ protected void resetButtonBar() {}
+
+ /**
* Adapter for all types of items and targets in ShareSheet.
* Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
* row level by this adapter but not on the item level. Individual targets within the row are
@@ -2272,15 +2449,13 @@
private int mChooserTargetWidth = 0;
private boolean mShowAzLabelIfPoss;
- private boolean mHideContentPreview = false;
private boolean mLayoutRequested = false;
private static final int VIEW_TYPE_DIRECT_SHARE = 0;
private static final int VIEW_TYPE_NORMAL = 1;
- private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
- private static final int VIEW_TYPE_PROFILE = 3;
- private static final int VIEW_TYPE_AZ_LABEL = 4;
- private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
+ private static final int VIEW_TYPE_PROFILE = 2;
+ private static final int VIEW_TYPE_AZ_LABEL = 3;
+ private static final int VIEW_TYPE_CALLER_AND_RANK = 4;
private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
@@ -2329,21 +2504,14 @@
return false;
}
- private int getMaxTargetsPerRow() {
+ int getMaxTargetsPerRow() {
int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
}
-
return maxTargets;
}
- public void hideContentPreview() {
- mHideContentPreview = true;
- mLayoutRequested = true;
- notifyDataSetChanged();
- }
-
public boolean consumeLayoutRequest() {
boolean oldValue = mLayoutRequested;
mLayoutRequested = false;
@@ -2352,8 +2520,7 @@
public int getRowCount() {
return (int) (
- getContentPreviewRowCount()
- + getProfileRowCount()
+ getProfileRowCount()
+ getServiceTargetRowCount()
+ getCallerAndRankedTargetRowCount()
+ getAzLabelRowCount()
@@ -2363,19 +2530,6 @@
);
}
- public int getContentPreviewRowCount() {
- if (!isSendAction(getTargetIntent())) {
- return 0;
- }
-
- if (mHideContentPreview || mChooserListAdapter == null
- || mChooserListAdapter.getCount() == 0) {
- return 0;
- }
-
- return 1;
- }
-
public int getProfileRowCount() {
return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
}
@@ -2404,8 +2558,7 @@
@Override
public int getItemCount() {
return (int) (
- getContentPreviewRowCount()
- + getProfileRowCount()
+ getProfileRowCount()
+ getServiceTargetRowCount()
+ getCallerAndRankedTargetRowCount()
+ getAzLabelRowCount()
@@ -2416,8 +2569,6 @@
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
- case VIEW_TYPE_CONTENT_PREVIEW:
- return new ItemViewHolder(createContentPreviewView(parent), false);
case VIEW_TYPE_PROFILE:
return new ItemViewHolder(createProfileView(parent), false);
case VIEW_TYPE_AZ_LABEL:
@@ -2452,10 +2603,7 @@
public int getItemViewType(int position) {
int count;
- int countSum = (count = getContentPreviewRowCount());
- if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
-
- countSum += (count = getProfileRowCount());
+ int countSum = (count = getProfileRowCount());
if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
countSum += (count = getServiceTargetRowCount());
@@ -2474,16 +2622,6 @@
return mChooserListAdapter.getPositionTargetType(getListPosition(position));
}
- private ViewGroup createContentPreviewView(ViewGroup parent) {
- Intent targetIntent = getTargetIntent();
- int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
-
- getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
- .setSubtype(previewType));
-
- return displayContentPreview(previewType, targetIntent, mLayoutInflater, parent);
- }
-
private View createProfileView(ViewGroup parent) {
View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
profileRow.setBackground(
@@ -2519,7 +2657,7 @@
@Override
public boolean onLongClick(View v) {
showTargetDetails(
- mChooserListAdapter.resolveInfoForPosition(
+ mChooserListAdapter.targetInfoForPosition(
holder.getItemIndex(column), true));
return true;
}
@@ -2667,6 +2805,7 @@
for (int i = 0; i < columnCount; i++) {
final View v = holder.getView(i);
+
if (start + i <= end) {
holder.setViewVisibility(i, View.VISIBLE);
holder.setItemIndex(i, start + i);
@@ -2678,7 +2817,7 @@
}
int getListPosition(int position) {
- position -= getContentPreviewRowCount() + getProfileRowCount();
+ position -= getProfileRowCount();
final int serviceCount = mChooserListAdapter.getServiceTargetCount();
final int serviceRows = (int) Math.ceil((float) serviceCount
@@ -2712,9 +2851,19 @@
&& !isInMultiWindowMode();
if (mDirectShareViewHolder != null && canExpandDirectShare) {
- mDirectShareViewHolder.handleScroll(mRecyclerView, y, oldy, getMaxTargetsPerRow());
+ mDirectShareViewHolder.handleScroll(
+ mChooserMultiProfilePagerAdapter.getCurrentAdapterView(), y, oldy,
+ getMaxTargetsPerRow());
}
}
+
+ public ChooserListAdapter getListAdapter() {
+ return mChooserListAdapter;
+ }
+
+ boolean shouldCellSpan(int position) {
+ return getItemViewType(position) == VIEW_TYPE_NORMAL;
+ }
}
/**
@@ -2898,7 +3047,8 @@
// only expand if we have more than maxTargetsPerRow, and delay that decision
// until they start to scroll
- if (mChooserListAdapter.getSelectableServiceTargetCount() <= maxTargetsPerRow) {
+ if (mChooserMultiProfilePagerAdapter.getActiveListAdapter()
+ .getSelectableServiceTargetCount() <= maxTargetsPerRow) {
mHideDirectShareExpansion = true;
return;
}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 4eccf21..a8a676d 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -38,17 +38,22 @@
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.MultiDisplayResolveInfo;
import com.android.internal.app.chooser.SelectableTargetInfo;
import com.android.internal.app.chooser.TargetInfo;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class ChooserListAdapter extends ResolverListAdapter {
private static final String TAG = "ChooserListAdapter";
private static final boolean DEBUG = false;
+ private boolean mEnableStackedApps = true;
+
public static final int NO_POSITION = -1;
public static final int TARGET_BAD = -1;
public static final int TARGET_CALLER = 0;
@@ -218,7 +223,25 @@
void updateAlphabeticalList() {
mSortedList.clear();
- mSortedList.addAll(mDisplayList);
+ if (mEnableStackedApps) {
+ // Consolidate multiple targets from same app.
+ Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
+ for (DisplayResolveInfo info : mDisplayList) {
+ String packageName = info.getResolvedComponentName().getPackageName();
+ if (consolidated.get(packageName) != null) {
+ // create consolidated target
+ MultiDisplayResolveInfo multiDisplayResolveInfo =
+ new MultiDisplayResolveInfo(packageName, info);
+ multiDisplayResolveInfo.addTarget(consolidated.get(packageName));
+ consolidated.put(packageName, multiDisplayResolveInfo);
+ } else {
+ consolidated.put(packageName, info);
+ }
+ }
+ mSortedList.addAll(consolidated.values());
+ } else {
+ mSortedList.addAll(mDisplayList);
+ }
Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext));
}
@@ -270,7 +293,7 @@
}
int getAlphaTargetCount() {
- int standardCount = super.getCount();
+ int standardCount = mSortedList.size();
return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
}
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
new file mode 100644
index 0000000..7d856e1
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.GridLayoutManager;
+import com.android.internal.widget.PagerAdapter;
+import com.android.internal.widget.RecyclerView;
+
+/**
+ * A {@link PagerAdapter} which describes the work and personal profile share sheet screens.
+ */
+@VisibleForTesting
+public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter {
+ private static final int SINGLE_CELL_SPAN_SIZE = 1;
+
+ private final ChooserProfileDescriptor[] mItems;
+
+ ChooserMultiProfilePagerAdapter(Context context,
+ ChooserActivity.ChooserGridAdapter adapter) {
+ super(context, /* currentPage */ 0);
+ mItems = new ChooserProfileDescriptor[] {
+ createProfileDescriptor(adapter)
+ };
+ }
+
+ ChooserMultiProfilePagerAdapter(Context context,
+ ChooserActivity.ChooserGridAdapter personalAdapter,
+ ChooserActivity.ChooserGridAdapter workAdapter,
+ @Profile int defaultProfile) {
+ super(context, /* currentPage */ defaultProfile);
+ mItems = new ChooserProfileDescriptor[] {
+ createProfileDescriptor(personalAdapter),
+ createProfileDescriptor(workAdapter)
+ };
+ }
+
+ private ChooserProfileDescriptor createProfileDescriptor(
+ ChooserActivity.ChooserGridAdapter adapter) {
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ final ViewGroup rootView =
+ (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile, null, false);
+ return new ChooserProfileDescriptor(rootView, adapter);
+ }
+
+ RecyclerView getListViewForIndex(int index) {
+ return getItem(index).recyclerView;
+ }
+
+ @Override
+ ChooserProfileDescriptor getItem(int pageIndex) {
+ return mItems[pageIndex];
+ }
+
+ @Override
+ int getItemCount() {
+ return mItems.length;
+ }
+
+ @Override
+ ChooserActivity.ChooserGridAdapter getAdapterForIndex(int pageIndex) {
+ return mItems[pageIndex].chooserGridAdapter;
+ }
+
+ @Override
+ void setupListAdapter(int pageIndex) {
+ final RecyclerView recyclerView = getItem(pageIndex).recyclerView;
+ ChooserActivity.ChooserGridAdapter chooserGridAdapter =
+ getItem(pageIndex).chooserGridAdapter;
+ recyclerView.setAdapter(chooserGridAdapter);
+ GridLayoutManager glm = (GridLayoutManager) recyclerView.getLayoutManager();
+ glm.setSpanCount(chooserGridAdapter.getMaxTargetsPerRow());
+ glm.setSpanSizeLookup(
+ new GridLayoutManager.SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ return chooserGridAdapter.shouldCellSpan(position)
+ ? SINGLE_CELL_SPAN_SIZE
+ : glm.getSpanCount();
+ }
+ });
+ }
+
+ @Override
+ @VisibleForTesting
+ public ChooserListAdapter getActiveListAdapter() {
+ return getAdapterForIndex(getCurrentPage()).getListAdapter();
+ }
+
+ @Override
+ @VisibleForTesting
+ public ChooserListAdapter getInactiveListAdapter() {
+ if (getCount() == 1) {
+ return null;
+ }
+ return getAdapterForIndex(1 - getCurrentPage()).getListAdapter();
+ }
+
+ @Override
+ ChooserActivity.ChooserGridAdapter getCurrentRootAdapter() {
+ return getAdapterForIndex(getCurrentPage());
+ }
+
+ @Override
+ RecyclerView getCurrentAdapterView() {
+ return getListViewForIndex(getCurrentPage());
+ }
+
+ class ChooserProfileDescriptor extends ProfileDescriptor {
+ private ChooserActivity.ChooserGridAdapter chooserGridAdapter;
+ private RecyclerView recyclerView;
+ ChooserProfileDescriptor(ViewGroup rootView, ChooserActivity.ChooserGridAdapter adapter) {
+ super(rootView);
+ chooserGridAdapter = adapter;
+ recyclerView = rootView.findViewById(R.id.resolver_list);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
new file mode 100644
index 0000000..f4c69a5
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
@@ -0,0 +1,80 @@
+/*
+ * 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.app;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import com.android.internal.app.chooser.MultiDisplayResolveInfo;
+
+/**
+ * Shows individual actions for a "stacked" app target - such as an app with multiple posting
+ * streams represented in the Sharesheet.
+ */
+public class ChooserStackedAppDialogFragment extends DialogFragment
+ implements DialogInterface.OnClickListener {
+ private static final String TITLE_KEY = "title";
+ private static final String PINNED_KEY = "pinned";
+
+ private MultiDisplayResolveInfo mTargetInfos;
+ private CharSequence[] mLabels;
+ private int mParentWhich;
+
+ public ChooserStackedAppDialogFragment() {
+ }
+
+ public ChooserStackedAppDialogFragment(CharSequence title,
+ MultiDisplayResolveInfo targets, CharSequence[] labels, int parentWhich) {
+ Bundle args = new Bundle();
+ args.putCharSequence(TITLE_KEY, title);
+ mTargetInfos = targets;
+ mLabels = labels;
+ mParentWhich = parentWhich;
+ setArguments(args);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle args = getArguments();
+ return new Builder(getContext())
+ .setCancelable(true)
+ .setItems(mLabels, this)
+ .setTitle(args.getCharSequence(TITLE_KEY))
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Bundle args = getArguments();
+ mTargetInfos.setSelected(which);
+ ((ChooserActivity) getActivity()).startSelected(mParentWhich, false, true);
+ dismiss();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Dismiss on config changed (eg: rotation)
+ // TODO: Maintain state on config change
+ super.onConfigurationChanged(newConfig);
+ dismiss();
+ }
+}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 99bf93e..46025aa 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -32,14 +32,13 @@
// and not be reordered
int checkOperation(int code, int uid, String packageName);
int noteOperation(int code, int uid, String packageName, @nullable String featureId);
- int startOperation(IBinder token, int code, int uid, String packageName,
+ int startOperation(IBinder clientId, int code, int uid, String packageName,
@nullable String featureId, boolean startIfModeDefault);
@UnsupportedAppUsage
- void finishOperation(IBinder token, int code, int uid, String packageName,
+ void finishOperation(IBinder clientId, int code, int uid, String packageName,
@nullable String featureId);
void startWatchingMode(int op, String packageName, IAppOpsCallback callback);
void stopWatchingMode(IAppOpsCallback callback);
- IBinder getToken(IBinder clientToken);
int permissionToOpCode(String permission);
int checkAudioOperation(int code, int usage, int uid, String packageName);
void noteAsyncOp(@nullable String callingPackageName, int uid, @nullable String packageName,
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ea24d5f..d94294f 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -20,6 +20,7 @@
import android.content.ComponentName;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.ModelParams;
import android.os.Bundle;
import android.os.ParcelUuid;
@@ -56,4 +57,16 @@
int getModelState(in ParcelUuid soundModelId);
@nullable SoundTrigger.ModuleProperties getModuleProperties();
+
+ 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,
+ in ModelParams modelParam);
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 9e38f38..8dc3a07 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -18,11 +18,15 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static com.android.internal.app.AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
+import static com.android.internal.app.AbstractMultiProfilePagerAdapter.PROFILE_WORK;
+
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UiThread;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
@@ -71,6 +75,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.Profile;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.content.PackageMonitor;
@@ -98,10 +103,7 @@
public ResolverActivity() {
}
- @UnsupportedAppUsage
- protected ResolverListAdapter mAdapter;
private boolean mSafeForwardingMode;
- private AbsListView mAdapterView;
private Button mAlwaysButton;
private Button mOnceButton;
protected View mProfileView;
@@ -142,8 +144,16 @@
private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
private static final String OPEN_LINKS_COMPONENT_KEY = "app_link_state";
+ /**
+ * TODO(arangelov): Remove a couple of weeks after work/personal tabs are finalized.
+ */
+ static final boolean ENABLE_TABBED_VIEW = false;
+
private final PackageMonitor mPackageMonitor = createPackageMonitor();
+ @VisibleForTesting
+ protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter;
+
// Intent extra for connected audio devices
public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
@@ -229,7 +239,7 @@
return new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
- mAdapter.handlePackagesChanged();
+ mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
updateProfileViewButton();
}
@@ -324,15 +334,13 @@
mSupportsAlwaysUseOption = supportsAlwaysUseOption;
- // The last argument of createAdapter is whether to do special handling
+ // The last argument of createResolverListAdapter is whether to do special handling
// of the last used choice to highlight it in the list. We need to always
// turn this off when running under voice interaction, since it results in
// a more complicated UI that the current voice interaction flow is not able
// to handle.
boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction();
- mAdapter = createAdapter(this, mIntents, initialIntents, rList,
- filterLastUsed, mUseLayoutForBrowsables);
-
+ mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
if (configureContentView()) {
return;
}
@@ -363,15 +371,96 @@
}
final Set<String> categories = intent.getCategories();
- MetricsLogger.action(this, mAdapter.hasFilteredItem()
+ MetricsLogger.action(this, mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
: MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
intent.getAction() + ":" + intent.getType() + ":"
+ (categories != null ? Arrays.toString(categories.toArray()) : ""));
}
+ protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed) {
+ AbstractMultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null;
+ if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
+ resolverMultiProfilePagerAdapter =
+ createResolverMultiProfilePagerAdapterForTwoProfiles(
+ initialIntents, rList, filterLastUsed);
+ } else {
+ resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForOneProfile(
+ initialIntents, rList, filterLastUsed);
+ }
+ return resolverMultiProfilePagerAdapter;
+ }
+
+ private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
+ Intent[] initialIntents,
+ List<ResolveInfo> rList, boolean filterLastUsed) {
+ ResolverListAdapter adapter = createResolverListAdapter(
+ /* context */ this,
+ /* payloadIntents */ mIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ mUseLayoutForBrowsables,
+ /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+ return new ResolverMultiProfilePagerAdapter(
+ /* context */ this,
+ adapter);
+ }
+
+ private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
+ Intent[] initialIntents,
+ List<ResolveInfo> rList,
+ boolean filterLastUsed) {
+ ResolverListAdapter personalAdapter = createResolverListAdapter(
+ /* context */ this,
+ /* payloadIntents */ mIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ mUseLayoutForBrowsables,
+ /* userHandle */ getPersonalProfileUserHandle());
+ ResolverListAdapter workAdapter = createResolverListAdapter(
+ /* context */ this,
+ /* payloadIntents */ mIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ mUseLayoutForBrowsables,
+ /* userHandle */ getWorkProfileUserHandle());
+ return new ResolverMultiProfilePagerAdapter(
+ /* context */ this,
+ personalAdapter,
+ workAdapter,
+ /* defaultProfile */ getCurrentProfile());
+ }
+
+ protected @Profile int getCurrentProfile() {
+ return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK);
+ }
+
+ protected UserHandle getPersonalProfileUserHandle() {
+ return UserHandle.of(ActivityManager.getCurrentUser());
+ }
+ protected @Nullable UserHandle getWorkProfileUserHandle() {
+ UserManager userManager = getSystemService(UserManager.class);
+ for (final UserInfo userInfo : userManager.getProfiles(ActivityManager.getCurrentUser())) {
+ if (userInfo.isManagedProfile()) {
+ return userInfo.getUserHandle();
+ }
+ }
+ return null;
+ }
+
+ protected boolean hasWorkProfile() {
+ return getWorkProfileUserHandle() != null;
+ }
+
protected void onProfileClick(View v) {
- final DisplayResolveInfo dri = mAdapter.getOtherProfile();
+ final DisplayResolveInfo dri =
+ mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
if (dri == null) {
return;
}
@@ -394,11 +483,13 @@
if (mFooterSpacer == null) {
mFooterSpacer = new Space(getApplicationContext());
} else {
- ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+ ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
+ .getCurrentAdapterView().removeFooterView(mFooterSpacer);
}
mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
mSystemWindowInsets.bottom));
- ((ListView) mAdapterView).addFooterView(mFooterSpacer);
+ ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
+ .getCurrentAdapterView().addFooterView(mFooterSpacer);
} else {
View emptyView = findViewById(R.id.empty);
if (emptyView != null) {
@@ -416,7 +507,7 @@
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- mAdapter.handlePackagesChanged();
+ mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
if (mSystemWindowInsets != null) {
mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
@@ -431,9 +522,10 @@
return;
}
- final Option[] options = new Option[mAdapter.getCount()];
+ int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount();
+ final Option[] options = new Option[count];
for (int i = 0, N = options.length; i < N; i++) {
- TargetInfo target = mAdapter.getItem(i);
+ TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter().getItem(i);
if (target == null) {
// If this occurs, a new set of targets is being loaded. Let that complete,
// and have the next call to send voice choices proceed instead.
@@ -482,8 +574,9 @@
return;
}
- final DisplayResolveInfo dri = mAdapter.getOtherProfile();
- if (dri != null) {
+ final DisplayResolveInfo dri =
+ mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile();
+ if (dri != null && !ENABLE_TABBED_VIEW) {
mProfileView.setVisibility(View.VISIBLE);
View text = mProfileView.findViewById(R.id.profile_button);
if (!(text instanceof TextView)) {
@@ -534,7 +627,8 @@
// While there may already be a filtered item, we can only use it in the title if the list
// is already sorted and all information relevant to it is already in the list.
- final boolean named = mAdapter.getFilteredPosition() >= 0;
+ final boolean named =
+ mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0;
if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
return getString(defaultTitleRes);
} else if (isHttpSchemeAndViewAction(intent)) {
@@ -543,12 +637,14 @@
String dialogTitle = null;
if (named && !mUseLayoutForBrowsables) {
dialogTitle = getString(ActionTitle.BROWSABLE_APP_TITLE_RES,
- mAdapter.getFilteredItem().getDisplayLabel());
+ mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredItem()
+ .getDisplayLabel());
} else if (named && mUseLayoutForBrowsables) {
dialogTitle = getString(ActionTitle.BROWSABLE_HOST_APP_TITLE_RES,
intent.getData().getHost(),
- mAdapter.getFilteredItem().getDisplayLabel());
- } else if (mAdapter.areAllTargetsBrowsers()) {
+ mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredItem()
+ .getDisplayLabel());
+ } else if (mMultiProfilePagerAdapter.getActiveListAdapter().areAllTargetsBrowsers()) {
dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
} else {
dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES,
@@ -557,7 +653,8 @@
return dialogTitle;
} else {
return named
- ? getString(title.namedTitleRes, mAdapter.getFilteredItem().getDisplayLabel())
+ ? getString(title.namedTitleRes, mMultiProfilePagerAdapter
+ .getActiveListAdapter().getFilteredItem().getDisplayLabel())
: getString(title.titleRes);
}
}
@@ -575,7 +672,7 @@
mPackageMonitor.register(this, getMainLooper(), false);
mRegistered = true;
}
- mAdapter.handlePackagesChanged();
+ mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
updateProfileViewButton();
}
@@ -608,8 +705,8 @@
if (!isChangingConfigurations() && mPickOptionRequest != null) {
mPickOptionRequest.cancel();
}
- if (mAdapter != null) {
- mAdapter.onDestroy();
+ if (mMultiProfilePagerAdapter.getActiveListAdapter() != null) {
+ mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy();
}
}
@@ -657,8 +754,10 @@
private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
boolean filtered) {
boolean enabled = false;
+ ResolveInfo ri = null;
if (hasValidSelection) {
- ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos, filtered);
+ ri = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .resolveInfoForPosition(checkedPos, filtered);
if (ri == null) {
Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled");
return;
@@ -676,16 +775,33 @@
.getString(R.string.activity_resolver_use_always));
}
}
+
+ ActivityInfo activityInfo = ri.activityInfo;
+
+ boolean hasRecordPermission =
+ mPm.checkPermission(android.Manifest.permission.RECORD_AUDIO,
+ activityInfo.packageName)
+ == android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+ if (!hasRecordPermission) {
+ // OK, we know the record permission, is this a capture device
+ boolean hasAudioCapture =
+ getIntent().getBooleanExtra(
+ ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
+ enabled = !hasAudioCapture;
+ }
mAlwaysButton.setEnabled(enabled);
}
public void onButtonClick(View v) {
final int id = v.getId();
- int which = mAdapter.hasFilteredItem()
- ? mAdapter.getFilteredPosition()
- : mAdapterView.getCheckedItemPosition();
- boolean hasIndexBeenFiltered = !mAdapter.hasFilteredItem();
- ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
+ ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+ ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
+ int which = currentListAdapter.hasFilteredItem()
+ ? currentListAdapter.getFilteredPosition()
+ : listView.getCheckedItemPosition();
+ boolean hasIndexBeenFiltered = !currentListAdapter.hasFilteredItem();
+ ResolveInfo ri = currentListAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
if (mUseLayoutForBrowsables
&& !ri.handleAllWebDataURI && id == R.id.button_always) {
showSettingsForSelected(ri);
@@ -716,7 +832,8 @@
if (isFinishing()) {
return;
}
- ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
+ ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .resolveInfoForPosition(which, hasIndexBeenFiltered);
if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
Toast.makeText(this, String.format(getResources().getString(
com.android.internal.R.string.activity_resolver_work_profiles_support),
@@ -725,7 +842,8 @@
return;
}
- TargetInfo target = mAdapter.targetInfoForPosition(which, hasIndexBeenFiltered);
+ TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .targetInfoForPosition(which, hasIndexBeenFiltered);
if (target == null) {
return;
}
@@ -740,7 +858,8 @@
MetricsLogger.action(
this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
}
- MetricsLogger.action(this, mAdapter.hasFilteredItem()
+ MetricsLogger.action(this,
+ mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()
? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
: MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
finish();
@@ -756,17 +875,17 @@
}
@Override // ResolverListCommunicator
- public void onPostListReady() {
+ public void onPostListReady(ResolverListAdapter listAdapter) {
setHeader();
resetButtonBar();
- onListRebuilt();
+ onListRebuilt(listAdapter);
}
- protected void onListRebuilt() {
- int count = mAdapter.getUnfilteredCount();
- if (count == 1 && mAdapter.getOtherProfile() == null) {
+ protected void onListRebuilt(ResolverListAdapter listAdapter) {
+ int count = listAdapter.getUnfilteredCount();
+ if (count == 1 && listAdapter.getOtherProfile() == null) {
// Only one target, so we're a candidate to auto-launch!
- final TargetInfo target = mAdapter.targetInfoForPosition(0, false);
+ final TargetInfo target = listAdapter.targetInfoForPosition(0, false);
if (shouldAutoLaunchSingleChoice(target)) {
safelyStartActivity(target);
finish();
@@ -778,8 +897,9 @@
final ResolveInfo ri = target.getResolveInfo();
final Intent intent = target != null ? target.getResolvedIntent() : null;
- if (intent != null && (mSupportsAlwaysUseOption || mAdapter.hasFilteredItem())
- && mAdapter.mUnfilteredResolveList != null) {
+ if (intent != null && (mSupportsAlwaysUseOption
+ || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem())
+ && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
Intent filterIntent;
@@ -864,13 +984,14 @@
}
if (filter != null) {
- final int N = mAdapter.mUnfilteredResolveList.size();
+ final int N = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .getUnfilteredResolveList().size();
ComponentName[] set;
// If we don't add back in the component for forwarding the intent to a managed
// profile, the preferred activity may not be updated correctly (as the set of
// components we tell it we knew about will have changed).
final boolean needToAddBackProfileForwardingComponent =
- mAdapter.getOtherProfile() != null;
+ mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null;
if (!needToAddBackProfileForwardingComponent) {
set = new ComponentName[N];
} else {
@@ -879,15 +1000,18 @@
int bestMatch = 0;
for (int i=0; i<N; i++) {
- ResolveInfo r = mAdapter.mUnfilteredResolveList.get(i).getResolveInfoAt(0);
+ ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .getUnfilteredResolveList().get(i).getResolveInfoAt(0);
set[i] = new ComponentName(r.activityInfo.packageName,
r.activityInfo.name);
if (r.match > bestMatch) bestMatch = r.match;
}
if (needToAddBackProfileForwardingComponent) {
- set[N] = mAdapter.getOtherProfile().getResolvedComponentName();
- final int otherProfileMatch = mAdapter.getOtherProfile().getResolveInfo().match;
+ set[N] = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .getOtherProfile().getResolvedComponentName();
+ final int otherProfileMatch = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .getOtherProfile().getResolveInfo().match;
if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
}
@@ -926,7 +1050,8 @@
}
} else {
try {
- mAdapter.mResolverListController.setLastChosen(intent, filter, bestMatch);
+ mMultiProfilePagerAdapter.getActiveListAdapter()
+ .mResolverListController.setLastChosen(intent, filter, bestMatch);
} catch (RemoteException re) {
Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
}
@@ -964,14 +1089,15 @@
if (mProfileSwitchMessageId != -1) {
Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show();
}
+ UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
if (!mSafeForwardingMode) {
- if (cti.start(this, null)) {
+ if (cti.startAsUser(this, null, currentUserHandle)) {
onActivityStarted(cti);
}
return;
}
try {
- if (cti.startAsCaller(this, null, UserHandle.USER_NULL)) {
+ if (cti.startAsCaller(this, null, currentUserHandle.getIdentifier())) {
onActivityStarted(cti);
}
} catch (RuntimeException e) {
@@ -1034,34 +1160,34 @@
return !target.isSuspended();
}
- public void showTargetDetails(ResolveInfo ri) {
+ void showTargetDetails(ResolveInfo ri) {
Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
startActivity(in);
}
- public ResolverListAdapter createAdapter(Context context, List<Intent> payloadIntents,
- Intent[] initialIntents, List<ResolveInfo> rList,
- boolean filterLastUsed, boolean useLayoutForBrowsables) {
-
+ @VisibleForTesting
+ protected ResolverListAdapter createResolverListAdapter(Context context,
+ List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
+ boolean filterLastUsed, boolean useLayoutForBrowsables, UserHandle userHandle) {
Intent startIntent = getIntent();
boolean isAudioCaptureDevice =
startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false);
-
return new ResolverListAdapter(context, payloadIntents, initialIntents, rList,
- filterLastUsed, createListController(), useLayoutForBrowsables, this,
+ filterLastUsed, createListController(userHandle), useLayoutForBrowsables, this,
isAudioCaptureDevice);
}
@VisibleForTesting
- protected ResolverListController createListController() {
+ protected ResolverListController createListController(UserHandle userHandle) {
return new ResolverListController(
this,
mPm,
getTargetIntent(),
getReferrerPackageName(),
- mLaunchedFromUid);
+ mLaunchedFromUid,
+ userHandle);
}
/**
@@ -1069,16 +1195,21 @@
* @return <code>true</code> if the activity is finishing and creation should halt.
*/
private boolean configureContentView() {
- if (mAdapter == null) {
- throw new IllegalStateException("mAdapter cannot be null.");
+ if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) {
+ throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() "
+ + "cannot be null.");
}
- boolean rebuildCompleted = mAdapter.rebuildList();
+ boolean rebuildCompleted = mMultiProfilePagerAdapter.getActiveListAdapter().rebuildList();
+ if (mMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
+ mMultiProfilePagerAdapter.getInactiveListAdapter().rebuildList();
+ }
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
} else {
mLayoutId = getLayoutResource();
}
setContentView(mLayoutId);
+ mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager));
return postRebuildList(rebuildCompleted);
}
@@ -1099,14 +1230,16 @@
*/
final boolean postRebuildListInternal(boolean rebuildCompleted) {
- int count = mAdapter.getUnfilteredCount();
+ int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
// We only rebuild asynchronously when we have multiple elements to sort. In the case where
// we're already done, we can check if we should auto-launch immediately.
if (rebuildCompleted) {
- if (count == 1 && mAdapter.getOtherProfile() == null) {
+ if (count == 1
+ && mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() == null) {
// Only one target, so we're a candidate to auto-launch!
- final TargetInfo target = mAdapter.targetInfoForPosition(0, false);
+ final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .targetInfoForPosition(0, false);
if (shouldAutoLaunchSingleChoice(target)) {
safelyStartActivity(target);
mPackageMonitor.unregister();
@@ -1117,37 +1250,32 @@
}
}
- boolean isAdapterViewVisible = true;
- if (count == 0 && mAdapter.getPlaceholderCount() == 0) {
+ setupViewVisibilities(count);
+ return false;
+ }
+
+ private void setupViewVisibilities(int count) {
+ if (count == 0
+ && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) {
final TextView emptyView = findViewById(R.id.empty);
emptyView.setVisibility(View.VISIBLE);
- isAdapterViewVisible = false;
+ findViewById(R.id.profile_pager).setVisibility(View.GONE);
+ } else {
+ onPrepareAdapterView(mMultiProfilePagerAdapter.getActiveListAdapter());
}
-
- onPrepareAdapterView(mAdapter, isAdapterViewVisible);
- return false;
}
/**
* Prepare the scrollable view which consumes data in the list adapter.
* @param adapter The adapter used to provide data to item views.
- * @param isVisible True if the scrollable view should be visible; false, otherwise.
*/
- public void onPrepareAdapterView(ResolverListAdapter adapter, boolean isVisible) {
- mAdapterView = findViewById(R.id.resolver_list);
- if (!isVisible) {
- mAdapterView.setVisibility(View.GONE);
- return;
- }
- mAdapterView.setVisibility(View.VISIBLE);
+ public void onPrepareAdapterView(ResolverListAdapter adapter) {
+ mMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE);
final boolean useHeader = adapter.hasFilteredItem();
- final ListView listView = mAdapterView instanceof ListView ? (ListView) mAdapterView : null;
-
- mAdapterView.setAdapter(mAdapter);
-
+ final ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
final ItemClickListener listener = new ItemClickListener();
- mAdapterView.setOnItemClickListener(listener);
- mAdapterView.setOnItemLongClickListener(listener);
+ listView.setOnItemClickListener(listener);
+ listView.setOnItemLongClickListener(listener);
if (mSupportsAlwaysUseOption || mUseLayoutForBrowsables) {
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
@@ -1166,7 +1294,8 @@
* Configure the area above the app selection list (title, content preview, etc).
*/
public void setHeader() {
- if (mAdapter.getCount() == 0 && mAdapter.getPlaceholderCount() == 0) {
+ if (mMultiProfilePagerAdapter.getActiveListAdapter().getCount() == 0
+ && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) {
final TextView titleView = findViewById(R.id.title);
if (titleView != null) {
titleView.setVisibility(View.GONE);
@@ -1187,11 +1316,11 @@
final ImageView iconView = findViewById(R.id.icon);
if (iconView != null) {
- mAdapter.loadFilteredItemIconTaskAsync(iconView);
+ mMultiProfilePagerAdapter.getActiveListAdapter().loadFilteredItemIconTaskAsync(iconView);
}
}
- private void resetButtonBar() {
+ protected void resetButtonBar() {
if (!mSupportsAlwaysUseOption && !mUseLayoutForBrowsables) {
return;
}
@@ -1215,24 +1344,27 @@
}
private void resetAlwaysOrOnceButtonBar() {
- if (useLayoutWithDefault()
- && mAdapter.getFilteredPosition() != ListView.INVALID_POSITION) {
- setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
+ int filteredPosition = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .getFilteredPosition();
+ if (useLayoutWithDefault() && filteredPosition != ListView.INVALID_POSITION) {
+ setAlwaysButtonEnabled(true, filteredPosition, false);
mOnceButton.setEnabled(true);
return;
}
// When the items load in, if an item was already selected, enable the buttons
- if (mAdapterView != null
- && mAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
- setAlwaysButtonEnabled(true, mAdapterView.getCheckedItemPosition(), true);
+ ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+ if (currentAdapterView != null
+ && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
+ setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true);
mOnceButton.setEnabled(true);
}
}
@Override // ResolverListCommunicator
public boolean useLayoutWithDefault() {
- return mSupportsAlwaysUseOption && mAdapter.hasFilteredItem();
+ return mSupportsAlwaysUseOption
+ && mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem();
}
/**
@@ -1256,7 +1388,9 @@
@Override // ResolverListCommunicator
public void onHandlePackagesChanged() {
- if (mAdapter.getCount() == 0) {
+ ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
+ activeListAdapter.rebuildList();
+ if (activeListAdapter.getCount() == 0) {
// We no longer have any items... just finish the activity.
finish();
}
@@ -1331,11 +1465,14 @@
return;
}
// If we're still loading, we can't yet enable the buttons.
- if (mAdapter.resolveInfoForPosition(position, true) == null) {
+ if (mMultiProfilePagerAdapter.getActiveListAdapter()
+ .resolveInfoForPosition(position, true) == null) {
return;
}
- final int checkedPos = mAdapterView.getCheckedItemPosition();
+ ListView currentAdapterView =
+ (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+ final int checkedPos = currentAdapterView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
if (!useLayoutWithDefault()
&& (!hasValidSelection || mLastSelected != checkedPos)
@@ -1343,7 +1480,7 @@
setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
mOnceButton.setEnabled(hasValidSelection);
if (hasValidSelection) {
- mAdapterView.smoothScrollToPosition(checkedPos);
+ currentAdapterView.smoothScrollToPosition(checkedPos);
}
mLastSelected = checkedPos;
} else {
@@ -1361,7 +1498,8 @@
// Header views don't count.
return false;
}
- ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
+ ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter()
+ .resolveInfoForPosition(position, true);
showTargetDetails(ri);
return true;
}
@@ -1401,7 +1539,8 @@
final ResolverActivity ra = (ResolverActivity) getActivity();
if (ra != null) {
- final TargetInfo ti = ra.mAdapter.getItem(selections[0].getIndex());
+ final TargetInfo ti = ra.mMultiProfilePagerAdapter.getActiveListAdapter()
+ .getItem(selections[0].getIndex());
if (ra.onTargetSelected(ti, false)) {
ra.mPickOptionRequest = null;
ra.finish();
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index bb7ca35..ef7a347 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -37,7 +37,6 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,7 +80,7 @@
// This one is the list that the Adapter will actually present.
List<DisplayResolveInfo> mDisplayList;
- List<ResolvedComponentInfo> mUnfilteredResolveList;
+ private List<ResolvedComponentInfo> mUnfilteredResolveList;
private int mLastChosenPosition = -1;
private boolean mFilterLastUsed;
@@ -114,7 +113,6 @@
}
public void handlePackagesChanged() {
- rebuildList();
mResolverListCommunicator.onHandlePackagesChanged();
}
@@ -162,6 +160,10 @@
mResolverListController.updateChooserCounts(packageName, userId, action);
}
+ List<ResolvedComponentInfo> getUnfilteredResolveList() {
+ return mUnfilteredResolveList;
+ }
+
/**
* @return true if all items in the display list are defined as browsers by
* ResolveInfo.handleAllWebDataURI
@@ -348,12 +350,12 @@
* determine the layout known. We therefore can't update the UI inline and post to the
* handler thread to update after the current task is finished.
*/
- private void postListReadyRunnable() {
+ void postListReadyRunnable() {
if (mPostListReadyRunnable == null) {
mPostListReadyRunnable = new Runnable() {
@Override
public void run() {
- mResolverListCommunicator.onPostListReady();
+ mResolverListCommunicator.onPostListReady(ResolverListAdapter.this);
mPostListReadyRunnable = null;
}
};
@@ -576,7 +578,7 @@
Drawable loadIconForResolveInfo(ResolveInfo ri) {
// Load icons based on the current process. If in work profile icons should be badged.
- return makePresentationGetter(ri).getIcon(Process.myUserHandle());
+ return makePresentationGetter(ri).getIcon(mResolverListController.getUserHandle());
}
void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
@@ -596,7 +598,7 @@
Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent);
- void onPostListReady();
+ void onPostListReady(ResolverListAdapter listAdapter);
void sendVoiceChoicesIfNeeded();
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index b456ca0..abd3eb2 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -28,6 +28,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -55,6 +56,7 @@
private static final String TAG = "ResolverListController";
private static final boolean DEBUG = false;
+ private final UserHandle mUserHandle;
private AbstractResolverComparator mResolverComparator;
private boolean isComputed = false;
@@ -64,8 +66,9 @@
PackageManager pm,
Intent targetIntent,
String referrerPackage,
- int launchedFromUid) {
- this(context, pm, targetIntent, referrerPackage, launchedFromUid,
+ int launchedFromUid,
+ UserHandle userHandle) {
+ this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle,
new ResolverRankerServiceResolverComparator(
context, targetIntent, referrerPackage, null));
}
@@ -76,12 +79,14 @@
Intent targetIntent,
String referrerPackage,
int launchedFromUid,
+ UserHandle userHandle,
AbstractResolverComparator resolverComparator) {
mContext = context;
mpm = pm;
mLaunchedFromUid = launchedFromUid;
mTargetIntent = targetIntent;
mReferrerPackage = referrerPackage;
+ mUserHandle = userHandle;
mResolverComparator = resolverComparator;
}
@@ -116,7 +121,8 @@
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
flags |= PackageManager.MATCH_INSTANT;
}
- final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags);
+ final List<ResolveInfo> infos = mpm.queryIntentActivitiesAsUser(intent, flags,
+ mUserHandle);
if (infos != null) {
if (resolvedComponents == null) {
resolvedComponents = new ArrayList<>();
@@ -127,6 +133,10 @@
return resolvedComponents;
}
+ UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
@VisibleForTesting
public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into,
Intent intent,
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
new file mode 100644
index 0000000..d72c52b
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -0,0 +1,122 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.PagerAdapter;
+
+/**
+ * A {@link PagerAdapter} which describes the work and personal profile intent resolver screens.
+ */
+@VisibleForTesting
+public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter {
+
+ private final ResolverProfileDescriptor[] mItems;
+
+ ResolverMultiProfilePagerAdapter(Context context,
+ ResolverListAdapter adapter) {
+ super(context, /* currentPage */ 0);
+ mItems = new ResolverProfileDescriptor[] {
+ createProfileDescriptor(adapter)
+ };
+ }
+
+ ResolverMultiProfilePagerAdapter(Context context,
+ ResolverListAdapter personalAdapter,
+ ResolverListAdapter workAdapter,
+ @Profile int defaultProfile) {
+ super(context, /* currentPage */ defaultProfile);
+ mItems = new ResolverProfileDescriptor[] {
+ createProfileDescriptor(personalAdapter),
+ createProfileDescriptor(workAdapter)
+ };
+ }
+
+ private ResolverProfileDescriptor createProfileDescriptor(
+ ResolverListAdapter adapter) {
+ final LayoutInflater inflater = LayoutInflater.from(getContext());
+ final ViewGroup rootView =
+ (ViewGroup) inflater.inflate(R.layout.resolver_list_per_profile, null, false);
+ return new ResolverProfileDescriptor(rootView, adapter);
+ }
+
+ ListView getListViewForIndex(int index) {
+ return getItem(index).listView;
+ }
+
+ @Override
+ ResolverProfileDescriptor getItem(int pageIndex) {
+ return mItems[pageIndex];
+ }
+
+ @Override
+ int getItemCount() {
+ return mItems.length;
+ }
+
+ @Override
+ void setupListAdapter(int pageIndex) {
+ final ListView listView = getItem(pageIndex).listView;
+ listView.setAdapter(getItem(pageIndex).resolverListAdapter);
+ }
+
+ @Override
+ ResolverListAdapter getAdapterForIndex(int pageIndex) {
+ return mItems[pageIndex].resolverListAdapter;
+ }
+
+ @Override
+ @VisibleForTesting
+ public ResolverListAdapter getActiveListAdapter() {
+ return getAdapterForIndex(getCurrentPage());
+ }
+
+ @Override
+ @VisibleForTesting
+ public ResolverListAdapter getInactiveListAdapter() {
+ if (getCount() == 1) {
+ return null;
+ }
+ return getAdapterForIndex(1 - getCurrentPage());
+ }
+
+ @Override
+ ResolverListAdapter getCurrentRootAdapter() {
+ return getActiveListAdapter();
+ }
+
+ @Override
+ ListView getCurrentAdapterView() {
+ return getListViewForIndex(getCurrentPage());
+ }
+
+ class ResolverProfileDescriptor extends ProfileDescriptor {
+ private ResolverListAdapter resolverListAdapter;
+ final ListView listView;
+ ResolverProfileDescriptor(ViewGroup rootView, ResolverListAdapter adapter) {
+ super(rootView);
+ resolverListAdapter = adapter;
+ listView = rootView.findViewById(R.id.resolver_list);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
index df91c4a..21efc78 100644
--- a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -24,14 +24,19 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import com.android.internal.R;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+
+import java.util.ArrayList;
+import java.util.List;
/**
- * Shows a dialog with actions to take on a chooser target
+ * Shows a dialog with actions to take on a chooser target.
*/
public class ResolverTargetActionsDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
@@ -43,6 +48,10 @@
private static final int TOGGLE_PIN_INDEX = 0;
private static final int APP_INFO_INDEX = 1;
+ private List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+ private List<CharSequence> mLabels = new ArrayList<>();
+ private boolean[] mPinned;
+
public ResolverTargetActionsDialogFragment() {
}
@@ -55,15 +64,43 @@
setArguments(args);
}
+ public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
+ List<DisplayResolveInfo> targets, List<CharSequence> labels) {
+ Bundle args = new Bundle();
+ args.putCharSequence(TITLE_KEY, title);
+ args.putParcelable(NAME_KEY, name);
+ mTargetInfos = targets;
+ mLabels = labels;
+ setArguments(args);
+ }
+
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle args = getArguments();
final int itemRes = args.getBoolean(PINNED_KEY, false)
? R.array.resolver_target_actions_unpin
: R.array.resolver_target_actions_pin;
+ String[] defaultActions = getResources().getStringArray(itemRes);
+ CharSequence[] items;
+
+ if (mTargetInfos == null || mTargetInfos.size() < 2) {
+ items = defaultActions;
+ } else {
+ // Pin item for each sub-item
+ items = new CharSequence[mTargetInfos.size() + 1];
+ for (int i = 0; i < mTargetInfos.size(); i++) {
+ items[i] = mTargetInfos.get(i).isPinned()
+ ? getResources().getString(R.string.unpin_specific_target, mLabels.get(i))
+ : getResources().getString(R.string.pin_specific_target, mLabels.get(i));
+ }
+ // "App info"
+ items[mTargetInfos.size()] = defaultActions[1];
+ }
+
+
return new Builder(getContext())
.setCancelable(true)
- .setItems(itemRes, this)
+ .setItems(items, this)
.setTitle(args.getCharSequence(TITLE_KEY))
.create();
}
@@ -72,27 +109,41 @@
public void onClick(DialogInterface dialog, int which) {
final Bundle args = getArguments();
ComponentName name = args.getParcelable(NAME_KEY);
- switch (which) {
- case TOGGLE_PIN_INDEX:
- SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
- final String key = name.flattenToString();
- boolean currentVal = sp.getBoolean(name.flattenToString(), false);
- if (currentVal) {
- sp.edit().remove(key).apply();
- } else {
- sp.edit().putBoolean(key, true).apply();
- }
-
- // Force the chooser to requery and resort things
- getActivity().recreate();
- break;
- case APP_INFO_INDEX:
- Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
- .setData(Uri.fromParts("package", name.getPackageName(), null))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
- startActivity(in);
- break;
+ if (which == 0 || (mTargetInfos.size() > 0 && which < mTargetInfos.size())) {
+ if (mTargetInfos == null || mTargetInfos.size() == 0) {
+ pinComponent(name);
+ } else {
+ pinComponent(mTargetInfos.get(which).getResolvedComponentName());
+ }
+ // Force the chooser to requery and resort things
+ ((ChooserActivity) getActivity()).handlePackagesChanged();
+ } else {
+ // Last item in dialog is App Info
+ Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(Uri.fromParts("package", name.getPackageName(), null))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ startActivity(in);
}
dismiss();
}
+
+ private void pinComponent(ComponentName name) {
+ SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
+ final String key = name.flattenToString();
+ boolean currentVal = sp.getBoolean(name.flattenToString(), false);
+ if (currentVal) {
+ sp.edit().remove(key).apply();
+ } else {
+ sp.edit().putBoolean(key, true).apply();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Dismiss on config changed (eg: rotation)
+ // TODO: Maintain state on config change
+ super.onConfigurationChanged(newConfig);
+ dismiss();
+ }
+
}
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 33b2113..c610ac4 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -45,12 +45,14 @@
public static final String EXTRA_SUSPENDING_PACKAGE =
PACKAGE_NAME + ".extra.SUSPENDING_PACKAGE";
public static final String EXTRA_DIALOG_INFO = PACKAGE_NAME + ".extra.DIALOG_INFO";
+ public static final String EXTRA_ACTIVITY_OPTIONS = PACKAGE_NAME + ".extra.ACTIVITY_OPTIONS";
private Intent mMoreDetailsIntent;
private int mUserId;
private PackageManager mPm;
private Resources mSuspendingAppResources;
private SuspendDialogInfo mSuppliedDialogInfo;
+ private Bundle mOptions;
private CharSequence getAppLabel(String packageName) {
try {
@@ -143,6 +145,7 @@
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
final Intent intent = getIntent();
+ mOptions = intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS);
mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
if (mUserId < 0) {
Slog.wtf(TAG, "Invalid user: " + mUserId);
@@ -178,20 +181,22 @@
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case AlertDialog.BUTTON_NEUTRAL:
- startActivityAsUser(mMoreDetailsIntent, UserHandle.of(mUserId));
- Slog.i(TAG, "Started more details activity");
+ startActivityAsUser(mMoreDetailsIntent, mOptions, UserHandle.of(mUserId));
+ Slog.i(TAG, "Started activity: " + mMoreDetailsIntent.getAction()
+ + " in user " + mUserId);
break;
}
finish();
}
public static Intent createSuspendedAppInterceptIntent(String suspendedPackage,
- String suspendingPackage, SuspendDialogInfo dialogInfo, int userId) {
+ String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options, int userId) {
return new Intent()
.setClassName("android", SuspendedAppActivity.class.getName())
.putExtra(EXTRA_SUSPENDED_PACKAGE, suspendedPackage)
.putExtra(EXTRA_DIALOG_INFO, dialogInfo)
.putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage)
+ .putExtra(EXTRA_ACTIVITY_OPTIONS, options)
.putExtra(Intent.EXTRA_USER_ID, userId)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
diff --git a/core/java/com/android/internal/app/WrapHeightViewPager.java b/core/java/com/android/internal/app/WrapHeightViewPager.java
new file mode 100644
index 0000000..b017bb4
--- /dev/null
+++ b/core/java/com/android/internal/app/WrapHeightViewPager.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 com.android.internal.app;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.internal.widget.ViewPager;
+
+/**
+ * A {@link ViewPager} which wraps around its first child's height.
+ * <p>Normally {@link ViewPager} instances expand their height to cover all remaining space in
+ * the layout.
+ * <p>This class is used for the intent resolver picker's tabbed view to maintain
+ * consistency with the previous behavior.
+ */
+public class WrapHeightViewPager extends ViewPager {
+
+ public WrapHeightViewPager(Context context) {
+ super(context);
+ }
+
+ public WrapHeightViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public WrapHeightViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public WrapHeightViewPager(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ // TODO(arangelov): When we have multiple pages, the height should wrap to the currently
+ // displayed page. Investigate whether onMeasure is called when changing a page, and instead
+ // of getChildAt(0), use the currently displayed one.
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.AT_MOST) {
+ return;
+ }
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
+ int height = getMeasuredHeight();
+ if (getChildCount() > 0) {
+ View firstChild = getChildAt(0);
+ firstChild.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ height = firstChild.getMeasuredHeight();
+ }
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+}
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index f92637c..86a9af3 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -91,6 +91,16 @@
mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
}
+ DisplayResolveInfo(DisplayResolveInfo other) {
+ mSourceIntents.addAll(other.getAllSourceIntents());
+ mResolveInfo = other.mResolveInfo;
+ mDisplayLabel = other.mDisplayLabel;
+ mDisplayIcon = other.mDisplayIcon;
+ mExtendedInfo = other.mExtendedInfo;
+ mResolvedIntent = other.mResolvedIntent;
+ mResolveInfoPresentationGetter = other.mResolveInfoPresentationGetter;
+ }
+
public ResolveInfo getResolveInfo() {
return mResolveInfo;
}
@@ -189,4 +199,5 @@
public void setPinned(boolean pinned) {
mPinned = pinned;
}
+
}
diff --git a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
new file mode 100644
index 0000000..e582583
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.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.internal.app.chooser;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.internal.app.ResolverActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a "stack" of chooser targets for various activities within the same component.
+ */
+public class MultiDisplayResolveInfo extends DisplayResolveInfo {
+
+ List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+ // We'll use this DRI for basic presentation info - eg icon, name.
+ final DisplayResolveInfo mBaseInfo;
+ // Index of selected target
+ private int mSelected = -1;
+
+ /**
+ * @param firstInfo A representative DRI to use for the main icon, title, etc for this Info.
+ */
+ public MultiDisplayResolveInfo(String packageName, DisplayResolveInfo firstInfo) {
+ super(firstInfo);
+ mBaseInfo = firstInfo;
+ mTargetInfos.add(firstInfo);
+ }
+
+ @Override
+ public CharSequence getExtendedInfo() {
+ // Never show subtitle for stacked apps
+ return null;
+ }
+
+ /**
+ * Add another DisplayResolveInfo to the list included for this target.
+ */
+ public void addTarget(DisplayResolveInfo target) {
+ mTargetInfos.add(target);
+ }
+
+ /**
+ * List of all DisplayResolveInfos included in this target.
+ */
+ public List<DisplayResolveInfo> getTargets() {
+ return mTargetInfos;
+ }
+
+ public void setSelected(int selected) {
+ mSelected = selected;
+ }
+
+ /**
+ * Whether or not the user has selected a specific target for this MultiInfo.
+ */
+ public boolean hasSelected() {
+ return mSelected >= 0;
+ }
+
+ @Override
+ public boolean start(Activity activity, Bundle options) {
+ return mTargetInfos.get(mSelected).start(activity, options);
+ }
+
+ @Override
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+ return mTargetInfos.get(mSelected).startAsCaller(activity, options, userId);
+ }
+
+ @Override
+ public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+ return mTargetInfos.get(mSelected).startAsUser(activity, options, user);
+ }
+
+}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index f5708a5c..221cd6d 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -66,6 +66,7 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.regex.Pattern;
/**
* A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -259,7 +260,7 @@
throw new IllegalStateException("Failed to touch " + file + ": " + e);
}
}
- MediaStore.scanFile(getContext(), file);
+ MediaStore.scanFile(getContext().getContentResolver(), file);
return childId;
}
@@ -316,10 +317,10 @@
private void moveInMediaStore(@Nullable File oldVisibleFile, @Nullable File newVisibleFile) {
if (oldVisibleFile != null) {
- MediaStore.scanFile(getContext(), oldVisibleFile);
+ MediaStore.scanFile(getContext().getContentResolver(), oldVisibleFile);
}
if (newVisibleFile != null) {
- MediaStore.scanFile(getContext(), newVisibleFile);
+ MediaStore.scanFile(getContext().getContentResolver(), newVisibleFile);
}
}
@@ -388,7 +389,9 @@
resolveProjection(projection), parentDocumentId, parent);
if (parent.isDirectory()) {
for (File file : FileUtils.listFilesOrEmpty(parent)) {
- includeFile(result, null, file);
+ if (!shouldHide(file)) {
+ includeFile(result, null, file);
+ }
}
} else {
Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
@@ -422,6 +425,8 @@
pending.add(folder);
while (!pending.isEmpty() && result.getCount() < 24) {
final File file = pending.removeFirst();
+ if (shouldHide(file)) continue;
+
if (file.isDirectory()) {
for (File child : file.listFiles()) {
pending.add(child);
@@ -540,6 +545,7 @@
} else {
file = getFileForDocId(docId);
}
+
final String mimeType = getDocumentType(docId, file);
row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_MIME_TYPE, mimeType);
@@ -598,6 +604,17 @@
return row;
}
+ private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile(
+ "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$");
+
+ /**
+ * In a scoped storage world, access to "Android/data" style directories are
+ * hidden for privacy reasons.
+ */
+ protected boolean shouldHide(@NonNull File file) {
+ return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
+ }
+
protected boolean shouldBlockFromTree(@NonNull String docId) {
return false;
}
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index f5fae19..22fe31e 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -444,7 +444,7 @@
return sum;
}
- /**
+ /**
* Configure the native library files managed by Incremental Service. Makes sure Incremental
* Service will create native library directories and set up native library binary files in the
* same structure as they are in non-incremental installations.
@@ -458,8 +458,20 @@
*/
public static int configureNativeBinariesForSupportedAbi(AndroidPackage pkg, Handle handle,
File libraryRoot, String[] abiList, boolean useIsaSubdir) {
- // TODO(b/136132412): Implement this.
- return -1;
+ int abi = findSupportedAbi(handle, abiList);
+ if (abi < 0) {
+ Slog.e(TAG, "Failed to find find matching ABI.");
+ return abi;
+ }
+
+ // Currently only support installations that have pre-configured native library files
+ // TODO(b/136132412): implement this after incfs supports file mapping
+ if (!libraryRoot.exists()) {
+ Slog.e(TAG, "Incremental installation currently does not configure native libs");
+ return INSTALL_FAILED_NO_MATCHING_ABIS;
+ }
+
+ return abi;
}
// We don't care about the other return values for now.
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 6e9c4c3..6b76a0f 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -28,9 +28,9 @@
import android.util.Slog;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.Preconditions;
import java.util.HashSet;
+import java.util.Objects;
/**
* Helper class for monitoring the state of packages: adding, removing,
@@ -93,7 +93,7 @@
throw new IllegalStateException("Already registered");
}
mRegisteredContext = context;
- mRegisteredHandler = Preconditions.checkNotNull(handler);
+ mRegisteredHandler = Objects.requireNonNull(handler);
if (user != null) {
context.registerReceiverAsUser(this, user, sPackageFilt, null, mRegisteredHandler);
context.registerReceiverAsUser(this, user, sNonDataFilt, null, mRegisteredHandler);
diff --git a/core/java/com/android/internal/infra/AndroidFuture.aidl b/core/java/com/android/internal/infra/AndroidFuture.aidl
index b19aab8..5f623b1 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.aidl
+++ b/core/java/com/android/internal/infra/AndroidFuture.aidl
@@ -17,4 +17,4 @@
package com.android.internal.infra;
/** @hide */
-parcelable AndroidFuture;
+parcelable AndroidFuture<T>;
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index c8929e9..b250578 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -513,6 +513,11 @@
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();
+ }
dest.writeBoolean(true);
dest.writeException(t);
return;
@@ -526,6 +531,12 @@
try {
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);
}
}
diff --git a/core/java/com/android/internal/infra/PerUser.java b/core/java/com/android/internal/infra/PerUser.java
new file mode 100644
index 0000000..560ca8c
--- /dev/null
+++ b/core/java/com/android/internal/infra/PerUser.java
@@ -0,0 +1,59 @@
+/*
+ * 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.infra;
+
+import android.annotation.NonNull;
+import android.util.SparseArray;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A {@link SparseArray} customized for a common use-case of storing state per-user.
+ *
+ * Unlike a normal {@link SparseArray} this will always create a value on {@link #get} if one is
+ * not present instead of returning null.
+ *
+ * @param <T> user state type
+ */
+public abstract class PerUser<T> extends SparseArray<T> {
+
+ /**
+ * Initialize state for the given user
+ */
+ protected abstract @NonNull T create(int userId);
+
+ /**
+ * Same as {@link #get(int)}, renamed for readability.
+ *
+ * This will never return null, deferring to {@link #create} instead
+ * when called for the first time.
+ */
+ public @NonNull T forUser(int userId) {
+ return get(userId);
+ }
+
+ @Override
+ public @NonNull T get(int userId) {
+ T userState = super.get(userId);
+ if (userState != null) {
+ return userState;
+ } else {
+ userState = Preconditions.checkNotNull(create(userId));
+ put(userId, userState);
+ return userState;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java
index 98d679e..857377a 100644
--- a/core/java/com/android/internal/infra/ServiceConnector.java
+++ b/core/java/com/android/internal/infra/ServiceConnector.java
@@ -32,7 +32,6 @@
import android.util.DebugUtils;
import android.util.Log;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.PrintWriter;
@@ -40,6 +39,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -352,7 +352,7 @@
@Override
public <R> CompletionAwareJob<I, R> postForResult(@NonNull Job<I, R> job) {
CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
- task.mDelegate = Preconditions.checkNotNull(job);
+ task.mDelegate = Objects.requireNonNull(job);
enqueue(task);
return task;
}
@@ -360,7 +360,7 @@
@Override
public <R> AndroidFuture<R> postAsync(@NonNull Job<I, CompletableFuture<R>> job) {
CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
- task.mDelegate = Preconditions.checkNotNull((Job) job);
+ task.mDelegate = Objects.requireNonNull((Job) job);
task.mAsync = true;
enqueue(task);
return task;
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index 3781d63..3de107e 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -6,10 +6,18 @@
{
"name": "CtsPermissionTestCases",
"options": [
- {
- "include-filter": "android.permission.cts.PermissionControllerTest"
- }
+ {
+ "include-filter": "android.permission.cts.PermissionControllerTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "com.android.internal.infra."
+ }
]
}
]
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/internal/infra/WhitelistHelper.java b/core/java/com/android/internal/infra/WhitelistHelper.java
index 9d653ba..b1d85f7 100644
--- a/core/java/com/android/internal/infra/WhitelistHelper.java
+++ b/core/java/com/android/internal/infra/WhitelistHelper.java
@@ -23,10 +23,9 @@
import android.util.ArraySet;
import android.util.Log;
-import com.android.internal.util.Preconditions;
-
import java.io.PrintWriter;
import java.util.List;
+import java.util.Objects;
/**
* Helper class for keeping track of whitelisted packages/activities.
@@ -107,7 +106,7 @@
* Returns {@code true} if the entire package is whitelisted.
*/
public boolean isWhitelisted(@NonNull String packageName) {
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
if (mWhitelistedPackages == null) return false;
@@ -119,7 +118,7 @@
* Returns {@code true} if the specified activity is whitelisted.
*/
public boolean isWhitelisted(@NonNull ComponentName componentName) {
- Preconditions.checkNotNull(componentName);
+ Objects.requireNonNull(componentName);
final String packageName = componentName.getPackageName();
final ArraySet<ComponentName> whitelistedComponents = getWhitelistedComponents(packageName);
@@ -136,7 +135,7 @@
*/
@Nullable
public ArraySet<ComponentName> getWhitelistedComponents(@NonNull String packageName) {
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
return mWhitelistedPackages == null ? null : mWhitelistedPackages.get(packageName);
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index bc44fcf..d0a83c4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,6 +16,9 @@
package com.android.internal.os;
+import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
+import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
@@ -5267,7 +5270,7 @@
// Unknown is included in DATA_CONNECTION_OTHER.
int bin = DATA_CONNECTION_OUT_OF_SERVICE;
if (hasData) {
- if (dataType > 0 && dataType <= TelephonyManager.MAX_NETWORK_TYPE) {
+ if (dataType > 0 && dataType <= TelephonyManager.getAllNetworkTypes().length) {
bin = dataType;
} else {
switch (serviceType) {
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index d08930b..a22615b 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -32,6 +32,7 @@
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ThreadFactory;
public class FuseAppLoop implements Handler.Callback {
@@ -92,8 +93,8 @@
public int registerCallback(@NonNull ProxyFileDescriptorCallback callback,
@NonNull Handler handler) throws FuseUnavailableMountException {
synchronized (mLock) {
- Preconditions.checkNotNull(callback);
- Preconditions.checkNotNull(handler);
+ Objects.requireNonNull(callback);
+ Objects.requireNonNull(handler);
Preconditions.checkState(
mCallbackMap.size() < Integer.MAX_VALUE - MIN_INODE, "Too many opened files.");
Preconditions.checkArgument(
@@ -333,8 +334,8 @@
boolean opened;
CallbackEntry(ProxyFileDescriptorCallback callback, Handler handler) {
- this.callback = Preconditions.checkNotNull(callback);
- this.handler = Preconditions.checkNotNull(handler);
+ this.callback = Objects.requireNonNull(callback);
+ this.handler = Objects.requireNonNull(handler);
}
long getThreadId() {
@@ -368,7 +369,7 @@
void stopUsing(long threadId) {
final BytesMapEntry entry = mEntries.get(threadId);
- Preconditions.checkNotNull(entry);
+ Objects.requireNonNull(entry);
entry.counter--;
if (entry.counter <= 0) {
mEntries.remove(threadId);
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index a211871..fa823c4 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -367,8 +367,8 @@
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
- protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
- ClassLoader classLoader) {
+ protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
+ String[] argv, ClassLoader classLoader) {
// If the application calls System.exit(), terminate the process
// immediately without running any shutdown hooks. It is not possible to
// shutdown an Android application gracefully. Among other things, the
@@ -377,6 +377,7 @@
nativeSetExitWithoutCleanup(true);
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
+ VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges);
final Arguments args = new Arguments(argv);
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index f0e7796..790d7f7 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -23,16 +23,18 @@
import android.system.OsConstants;
import android.system.StructCapUserData;
import android.system.StructCapUserHeader;
-import android.util.TimingsTraceLog;
import android.util.Slog;
+import android.util.TimingsTraceLog;
+
import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+
import java.io.DataOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
-import libcore.io.IoUtils;
-
/**
* Startup class for the wrapper process.
* @hide
@@ -166,10 +168,10 @@
System.arraycopy(argv, 2, removedArgs, 0, argv.length - 2);
argv = removedArgs;
}
-
// Perform the same initialization that would happen after the Zygote forks.
Zygote.nativePreApplicationInit();
- return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+ return RuntimeInit.applicationInit(targetSdkVersion, /*disabledCompatChanges*/ null,
+ argv, classLoader);
}
/**
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index fbacdd7..2248b88 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -151,6 +151,9 @@
/** Make the new process have top application priority. */
public static final String START_AS_TOP_APP_ARG = "--is-top-app";
+ /** List of packages with the same uid, and its app data info: volume uuid and inode. */
+ public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
+
/**
* An extraArg passed when a zygote process is forking a child-zygote, specifying a name
* in the abstract socket namespace. This socket name is what the new child zygote
@@ -254,6 +257,8 @@
* @param instructionSet null-ok the instruction set to use.
* @param appDataDir null-ok the data directory of the app.
* @param isTopApp true if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * info: volume uuid and inode.
*
* @return 0 if this is the child, pid of the child
* if this is the parent, or -1 on error.
@@ -261,12 +266,13 @@
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- int targetSdkVersion, boolean isTopApp) {
+ int targetSdkVersion, boolean isTopApp, String[] pkgDataInfoList) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
- fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp);
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Zygote.disableExecuteOnly(targetSdkVersion);
@@ -286,7 +292,7 @@
private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
- String appDataDir, boolean isTopApp);
+ String appDataDir, boolean isTopApp, String[] pkgDataInfoList);
/**
* Specialize an unspecialized app process. The current VM must have been started
@@ -309,12 +315,18 @@
* @param instructionSet null-ok The instruction set to use.
* @param appDataDir null-ok The data directory of the app.
* @param isTopApp True if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
+ * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
+ * app_b_ce_inode, ...];
*/
private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp) {
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList) {
nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
- niceName, startChildZygote, instructionSet, appDataDir, isTopApp);
+ niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList);
// Enable tracing as soon as possible for the child process.
Trace.setTracingEnabled(true, runtimeFlags);
@@ -336,7 +348,8 @@
private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp);
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList);
/**
* Called to do any initialization before starting an application.
@@ -665,13 +678,15 @@
specializeAppProcess(args.mUid, args.mGid, args.mGids,
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
- args.mInstructionSet, args.mAppDataDir, args.mIsTopApp);
+ args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
+ args.mPkgDataInfoList);
disableExecuteOnly(args.mTargetSdkVersion);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
+ args.mDisabledCompatChanges,
args.mRemainingArgs,
null /* classLoader */);
} finally {
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index a23e659..d349954 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -215,6 +215,18 @@
boolean mIsTopApp;
/**
+ * A set of disabled app compatibility changes for the running app. From
+ * --disabled-compat-changes.
+ */
+ long[] mDisabledCompatChanges = null;
+
+ /**
+ * A list that stores all related packages and its data info: volume uuid and inode.
+ * Null if it does need to do app data isolation.
+ */
+ String[] mPkgDataInfoList;
+
+ /**
* Constructs instance and parses args
*
* @param args zygote command-line args
@@ -421,6 +433,18 @@
expectRuntimeArgs = false;
} else if (arg.startsWith(Zygote.START_AS_TOP_APP_ARG)) {
mIsTopApp = true;
+ } else if (arg.startsWith("--disabled-compat-changes=")) {
+ if (mDisabledCompatChanges != null) {
+ throw new IllegalArgumentException("Duplicate arg specified");
+ }
+ final String[] params = getAssignmentList(arg);
+ final int length = params.length;
+ mDisabledCompatChanges = new long[length];
+ for (int i = 0; i < length; i++) {
+ mDisabledCompatChanges[i] = Long.parseLong(params[i]);
+ }
+ } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
+ mPkgDataInfoList = getAssignmentList(arg);
} else {
break;
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index a14b093..9c6a288 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,7 @@
parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion,
- parsedArgs.mIsTopApp);
+ parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList);
try {
if (pid == 0) {
@@ -501,6 +501,7 @@
} else {
if (!isZygote) {
return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mDisabledCompatChanges,
parsedArgs.mRemainingArgs, null /* classLoader */);
} else {
return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion,
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9ee79ea..49b4cf8 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -553,6 +553,7 @@
* Pass the remaining arguments to SystemServer.
*/
return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
+ parsedArgs.mDisabledCompatChanges,
parsedArgs.mRemainingArgs, cl);
}
@@ -988,14 +989,16 @@
*
* Current recognized args:
* <ul>
- * <li> <code> [--] <start class name> <args>
+ * <li> <code> [--] <start class name> <args>
* </ul>
*
* @param targetSdkVersion target SDK version
- * @param argv arg strings
+ * @param disabledCompatChanges set of disabled compat changes for the process (all others
+ * are enabled)
+ * @param argv arg strings
*/
- public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
- ClassLoader classLoader) {
+ public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
+ String[] argv, ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
@@ -1005,7 +1008,8 @@
RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit();
- return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+ return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
+ classLoader);
}
/**
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index c33b6dc..29b148c 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -22,6 +22,8 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.getMode;
@@ -74,6 +76,8 @@
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.InputQueue;
+import android.view.InsetsState;
+import android.view.InsetsState.InternalInsetsType;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.LayoutInflater;
@@ -90,6 +94,9 @@
import android.view.Window;
import android.view.WindowCallbacks;
import android.view.WindowInsets;
+import android.view.WindowInsets.Side;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -134,7 +141,7 @@
Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.statusBarBackground,
- FLAG_FULLSCREEN);
+ FLAG_FULLSCREEN, ITYPE_STATUS_BAR);
public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(
@@ -142,7 +149,7 @@
Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
com.android.internal.R.id.navigationBarBackground,
- 0 /* hideWindowFlag */);
+ 0 /* hideWindowFlag */, ITYPE_NAVIGATION_BAR);
// This is used to workaround an issue where the PiP shadow can be transparent if the window
// background is transparent
@@ -1085,6 +1092,9 @@
WindowManager.LayoutParams attrs = mWindow.getAttributes();
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+ final WindowInsetsController controller = getWindowInsetsController();
+ final InsetsState state = controller != null ? controller.getState() : null;
+
// IME is an exceptional floating window that requires color view.
final boolean isImeWindow =
mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -1133,7 +1143,7 @@
calculateNavigationBarColor(), mWindow.mNavigationBarDividerColor, navBarSize,
navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
0 /* sideInset */, animate && !disallowAnimate,
- mForceWindowDrawsBarBackgrounds);
+ mForceWindowDrawsBarBackgrounds, state);
boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground;
mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
&& (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
@@ -1154,7 +1164,7 @@
calculateStatusBarColor(), 0, mLastTopInset,
false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
animate && !disallowAnimate,
- mForceWindowDrawsBarBackgrounds);
+ mForceWindowDrawsBarBackgrounds, state);
}
// When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
@@ -1164,16 +1174,19 @@
// Note: We don't need to check for IN_SCREEN or INSET_DECOR because unlike the status bar,
// these flags wouldn't make the window draw behind the navigation bar, unless
// LAYOUT_HIDE_NAVIGATION was set.
- boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+ boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
+ || !(state == null || state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds
&& (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+ && (attrs.getFitWindowInsetsTypes() & Type.navigationBars()) != 0
&& !hideNavigation)
|| (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
boolean consumingNavBar =
((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+ && (attrs.getFitWindowInsetsTypes() & Type.navigationBars()) != 0
&& !hideNavigation)
|| forceConsumingNavBar;
@@ -1182,18 +1195,21 @@
// If we should always consume system bars, only consume that if the app wanted to go to
// fullscreen, as othrewise we can expect the app to handle it.
boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
- || (attrs.flags & FLAG_FULLSCREEN) != 0;
+ || (attrs.flags & FLAG_FULLSCREEN) != 0
+ || !(state == null || state.getSource(ITYPE_STATUS_BAR).isVisible());
boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
&& (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
&& (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
+ && (attrs.getFitWindowInsetsTypes() & Type.statusBars()) != 0
&& mForceWindowDrawsBarBackgrounds
&& mLastTopInset != 0
|| (mLastShouldAlwaysConsumeSystemBars && fullscreen);
- int consumedTop = consumingStatusBar ? mLastTopInset : 0;
- int consumedRight = consumingNavBar ? mLastRightInset : 0;
- int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
- int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
+ int sides = attrs.getFitWindowInsetsSides();
+ int consumedTop = consumingStatusBar && (sides & Side.TOP) != 0 ? mLastTopInset : 0;
+ int consumedRight = consumingNavBar && (sides & Side.RIGHT) != 0 ? mLastRightInset : 0;
+ int consumedBottom = consumingNavBar && (sides & Side.BOTTOM) != 0 ? mLastBottomInset : 0;
+ int consumedLeft = consumingNavBar && (sides & Side.LEFT) != 0 ? mLastLeftInset : 0;
if (mContentRoot != null
&& mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
@@ -1325,8 +1341,10 @@
*/
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
- boolean animate, boolean force) {
- state.present = state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force);
+ boolean animate, boolean force, InsetsState insetsState) {
+ state.present = ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL
+ ? state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force)
+ : state.attributes.isPresent(insetsState, mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
boolean showView = show && !isResizing() && size > 0;
@@ -2536,10 +2554,11 @@
final int seascapeGravity;
final String transitionName;
final int hideWindowFlag;
+ final @InternalInsetsType int insetsType;
private ColorViewAttributes(int systemUiHideFlag, int translucentFlag, int verticalGravity,
int horizontalGravity, int seascapeGravity, String transitionName, int id,
- int hideWindowFlag) {
+ int hideWindowFlag, @InternalInsetsType int insetsType) {
this.id = id;
this.systemUiHideFlag = systemUiHideFlag;
this.translucentFlag = translucentFlag;
@@ -2548,8 +2567,10 @@
this.seascapeGravity = seascapeGravity;
this.transitionName = transitionName;
this.hideWindowFlag = hideWindowFlag;
+ this.insetsType = insetsType;
}
+ // TODO(b/118118435): remove after migration
public boolean isPresent(int sysUiVis, int windowFlags, boolean force) {
return (sysUiVis & systemUiHideFlag) == 0
&& (windowFlags & hideWindowFlag) == 0
@@ -2557,16 +2578,27 @@
|| force);
}
+ public boolean isPresent(InsetsState state, int windowFlags, boolean force) {
+ return (state == null || state.getSource(insetsType).isVisible())
+ && ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || force);
+ }
+
public boolean isVisible(boolean present, int color, int windowFlags, boolean force) {
return present
&& (color & Color.BLACK) != 0
&& ((windowFlags & translucentFlag) == 0 || force);
}
+ // TODO(b/118118435): remove after migration
public boolean isVisible(int sysUiVis, int color, int windowFlags, boolean force) {
final boolean present = isPresent(sysUiVis, windowFlags, force);
return isVisible(present, color, windowFlags, force);
}
+
+ public boolean isVisible(InsetsState state, int color, int windowFlags, boolean force) {
+ final boolean present = isPresent(state, windowFlags, force);
+ return isVisible(present, color, windowFlags, force);
+ }
}
/**
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 764d4a59..9aa56f0 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -22,7 +22,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
@@ -2423,19 +2422,8 @@
final Context context = getContext();
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
- final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
- final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
- final boolean targetHcNeedsOptions = context.getResources().getBoolean(
- R.bool.target_honeycomb_needs_options_menu);
- final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
-
- if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
- setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
- } else {
- setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
- }
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 01f5743..6c7e3dc 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -16,8 +16,8 @@
package com.android.internal.telephony;
-import android.os.Bundle;
import android.telephony.CallAttributes;
+import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.PhoneCapability;
@@ -37,13 +37,12 @@
void onMessageWaitingIndicatorChanged(boolean mwi);
void onCallForwardingIndicatorChanged(boolean cfi);
- // we use bundle here instead of CellLocation so it can get the right subclass
- void onCellLocationChanged(in Bundle location);
+ // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
+ void onCellLocationChanged(in CellIdentity location);
void onCallStateChanged(int state, String incomingNumber);
void onDataConnectionStateChanged(int state, int networkType);
void onDataActivity(int direction);
void onSignalStrengthsChanged(in SignalStrength signalStrength);
- void onOtaspChanged(in int otaspMode);
void onCellInfoChanged(in List<CellInfo> cellInfo);
void onPreciseCallStateChanged(in PreciseCallState callState);
void onPreciseDataConnectionStateChanged(in PreciseDataConnectionState dataConnectionState);
@@ -64,4 +63,3 @@
void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause);
void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
}
-
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 2f34aa0..4e40503 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -19,11 +19,13 @@
import android.content.Intent;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
-import android.os.Bundle;
import android.telephony.CallQuality;
+import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.PhoneCapability;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.PreciseDataConnectionState;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
@@ -60,21 +62,13 @@
@UnsupportedAppUsage(maxTargetSdk = 28)
void notifyDataActivity(int state);
void notifyDataActivityForSubscriber(in int subId, int state);
- void notifyDataConnection(int state, boolean isDataConnectivityPossible,
- String apn, String apnType, in LinkProperties linkProperties,
- in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
- void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
- boolean isDataConnectivityPossible,
- String apn, String apnType, in LinkProperties linkProperties,
- in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
+ void notifyDataConnectionForSubscriber(
+ int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState);
@UnsupportedAppUsage
void notifyDataConnectionFailed(String apnType);
- void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType);
- @UnsupportedAppUsage(maxTargetSdk = 28)
- void notifyCellLocation(in Bundle cellLocation);
- void notifyCellLocationForSubscriber(in int subId, in Bundle cellLocation);
- @UnsupportedAppUsage(maxTargetSdk = 28)
- void notifyOtaspChanged(in int subId, in int otaspMode);
+ // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
+ void notifyCellLocation(in CellIdentity cellLocation);
+ void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation);
@UnsupportedAppUsage
void notifyCellInfo(in List<CellInfo> cellInfo);
void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index bc80197..c7ec2cd 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -136,6 +136,13 @@
}
/**
+ * Returns the same array or an empty one if it's null.
+ */
+ public static @NonNull <T> T[] emptyIfNull(@Nullable T[] items, Class<T> kind) {
+ return items != null ? items : emptyArray(kind);
+ }
+
+ /**
* Checks if given array is null or has zero elements.
*/
public static boolean isEmpty(@Nullable Collection<?> array) {
@@ -751,6 +758,42 @@
return result;
}
+ /**
+ * Returns an array containing elements from the given one that match the given predicate.
+ */
+ public static @Nullable <T> T[] filter(@Nullable T[] items,
+ @NonNull IntFunction<T[]> arrayConstructor,
+ @NonNull java.util.function.Predicate<T> predicate) {
+ if (isEmpty(items)) {
+ return items;
+ }
+
+ int matchesCount = 0;
+ int size = size(items);
+ for (int i = 0; i < size; i++) {
+ if (predicate.test(items[i])) {
+ matchesCount++;
+ }
+ }
+ if (matchesCount == 0) {
+ return items;
+ }
+ if (matchesCount == items.length) {
+ return items;
+ }
+ if (matchesCount == 0) {
+ return null;
+ }
+ T[] result = arrayConstructor.apply(matchesCount);
+ int outIdx = 0;
+ for (int i = 0; i < size; i++) {
+ if (predicate.test(items[i])) {
+ result[outIdx++] = items[i];
+ }
+ }
+ return result;
+ }
+
public static boolean startsWith(byte[] cur, byte[] val) {
if (cur == null || val == null) return false;
if (cur.length < val.length) return false;
diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java
index f8885a2..3ca3320 100644
--- a/core/java/com/android/internal/util/FileRotator.java
+++ b/core/java/com/android/internal/util/FileRotator.java
@@ -27,6 +27,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -96,8 +97,8 @@
* may be deleted.
*/
public FileRotator(File basePath, String prefix, long rotateAgeMillis, long deleteAgeMillis) {
- mBasePath = Preconditions.checkNotNull(basePath);
- mPrefix = Preconditions.checkNotNull(prefix);
+ mBasePath = Objects.requireNonNull(basePath);
+ mPrefix = Objects.requireNonNull(prefix);
mRotateAgeMillis = rotateAgeMillis;
mDeleteAgeMillis = deleteAgeMillis;
@@ -406,7 +407,7 @@
public long endMillis;
public FileInfo(String prefix) {
- this.prefix = Preconditions.checkNotNull(prefix);
+ this.prefix = Objects.requireNonNull(prefix);
}
/**
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index b955f67..3c97917 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -19,6 +19,7 @@
import android.os.RemoteException;
import android.util.ExceptionUtils;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -54,6 +55,13 @@
/**
* @see #uncheckExceptions(ThrowingConsumer)
*/
+ public static <A, B> BiConsumer<A, B> uncheckExceptions(ThrowingBiConsumer<A, B> action) {
+ return action;
+ }
+
+ /**
+ * @see #uncheckExceptions(ThrowingConsumer)
+ */
public static <T> Supplier<T> uncheckExceptions(ThrowingSupplier<T> action) {
return action;
}
@@ -185,4 +193,29 @@
}
}
}
+
+ /**
+ * A {@link BiConsumer} that allows throwing checked exceptions from its single abstract method.
+ *
+ * Can be used together with {@link #uncheckExceptions} to effectively turn a lambda expression
+ * that throws a checked exception into a regular {@link Function}
+ *
+ * @param <A> see {@link BiConsumer}
+ * @param <B> see {@link BiConsumer}
+ */
+ @FunctionalInterface
+ @SuppressWarnings("FunctionalInterfaceMethodChanged")
+ public interface ThrowingBiConsumer<A, B> extends BiConsumer<A, B> {
+ /** @see ThrowingFunction */
+ void acceptOrThrow(A a, B b) throws Exception;
+
+ @Override
+ default void accept(A a, B b) {
+ try {
+ acceptOrThrow(a, b);
+ } catch (Exception ex) {
+ throw ExceptionUtils.propagate(ex);
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/util/MemInfoReader.java b/core/java/com/android/internal/util/MemInfoReader.java
index c1d129b..362bc92 100644
--- a/core/java/com/android/internal/util/MemInfoReader.java
+++ b/core/java/com/android/internal/util/MemInfoReader.java
@@ -91,7 +91,15 @@
* that are mapped in to processes.
*/
public long getCachedSizeKb() {
- return mInfos[Debug.MEMINFO_BUFFERS] + mInfos[Debug.MEMINFO_SLAB_RECLAIMABLE]
+ long kReclaimable = mInfos[Debug.MEMINFO_KRECLAIMABLE];
+
+ // Note: MEMINFO_KRECLAIMABLE includes MEMINFO_SLAB_RECLAIMABLE and ION pools.
+ // Fall back to using MEMINFO_SLAB_RECLAIMABLE in case of older kernels that do
+ // not include KReclaimable meminfo field.
+ if (kReclaimable == 0) {
+ kReclaimable = mInfos[Debug.MEMINFO_SLAB_RECLAIMABLE];
+ }
+ return mInfos[Debug.MEMINFO_BUFFERS] + kReclaimable
+ mInfos[Debug.MEMINFO_CACHED] - mInfos[Debug.MEMINFO_MAPPED];
}
diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java
index a477688..5568d91 100644
--- a/core/java/com/android/internal/util/ObjectUtils.java
+++ b/core/java/com/android/internal/util/ObjectUtils.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import java.util.Objects;
/** @hide */
public class ObjectUtils {
@@ -32,7 +33,7 @@
*/
@NonNull
public static <T> T firstNotNull(@Nullable T a, @NonNull T b) {
- return a != null ? a : Preconditions.checkNotNull(b);
+ return a != null ? a : Objects.requireNonNull(b);
}
/**
diff --git a/core/java/com/android/internal/util/ScreenRecordHelper.java b/core/java/com/android/internal/util/ScreenRecordHelper.java
index 64d0898..ec7ed4e 100644
--- a/core/java/com/android/internal/util/ScreenRecordHelper.java
+++ b/core/java/com/android/internal/util/ScreenRecordHelper.java
@@ -24,10 +24,6 @@
* Helper class to initiate a screen recording
*/
public class ScreenRecordHelper {
- 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;
/**
@@ -42,8 +38,9 @@
* Show dialog of screen recording options to user.
*/
public void launchRecordPrompt() {
- final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
- SYSUI_SCREENRECORD_LAUNCHER);
+ final ComponentName launcherComponent = ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_screenRecorderComponent));
final Intent intent = new Intent();
intent.setComponent(launcherComponent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index f6f187f..8cad5a0 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -20,12 +20,6 @@
public class ScreenshotHelper {
private static final String TAG = "ScreenshotHelper";
- private static final String SYSUI_PACKAGE = "com.android.systemui";
- private static final String SYSUI_SCREENSHOT_SERVICE =
- "com.android.systemui.screenshot.TakeScreenshotService";
- private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER =
- "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver";
-
// Time until we give up on the screenshot & show an error instead.
private final int SCREENSHOT_TIMEOUT_MS = 10000;
@@ -94,8 +88,9 @@
if (mScreenshotConnection != null) {
return;
}
- final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
- SYSUI_SCREENSHOT_SERVICE);
+ final ComponentName serviceComponent = ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_screenshotServiceComponent));
final Intent serviceIntent = new Intent();
final Runnable mScreenshotTimeout = new Runnable() {
@@ -181,8 +176,9 @@
*/
private void notifyScreenshotError() {
// If the service process is killed, then ask it to clean up after itself
- final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE,
- SYSUI_SCREENSHOT_ERROR_RECEIVER);
+ final ComponentName errorComponent = ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_screenshotErrorReceiverComponent));
// Broadcast needs to have a valid action. We'll just pick
// a generic one, since the receiver here doesn't care.
Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
index c7502ef..8446bbd 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
@@ -25,7 +25,6 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.function.DecConsumer;
import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.DecPredicate;
@@ -545,7 +544,7 @@
+ ", k = " + k
+ ")");
}
- r.mFunc = Preconditions.checkNotNull(func);
+ r.mFunc = Objects.requireNonNull(func);
r.setFlags(MASK_FUNC_TYPE, LambdaType.encode(fNumArgs, fReturnType));
r.setFlags(MASK_EXPOSED_AS, LambdaType.encode(numPlaceholders, fReturnType));
if (ArrayUtils.size(r.mArgs) < fNumArgs) r.mArgs = new Object[fNumArgs];
diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
index 54dede6..f9e98e7 100644
--- a/core/java/com/android/internal/view/FloatingActionMode.java
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -33,11 +33,11 @@
import android.widget.PopupWindow;
import com.android.internal.R;
-import com.android.internal.util.Preconditions;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.FloatingToolbar;
import java.util.Arrays;
+import java.util.Objects;
public final class FloatingActionMode extends ActionMode {
@@ -84,8 +84,8 @@
public FloatingActionMode(
Context context, ActionMode.Callback2 callback,
View originatingView, FloatingToolbar floatingToolbar) {
- mContext = Preconditions.checkNotNull(context);
- mCallback = Preconditions.checkNotNull(callback);
+ mContext = Objects.requireNonNull(context);
+ mCallback = Objects.requireNonNull(callback);
mMenu = new MenuBuilder(context).setDefaultShowAsAction(
MenuItem.SHOW_AS_ACTION_IF_ROOM);
setType(ActionMode.TYPE_FLOATING);
@@ -107,14 +107,14 @@
mViewRectOnScreen = new Rect();
mPreviousViewRectOnScreen = new Rect();
mScreenRect = new Rect();
- mOriginatingView = Preconditions.checkNotNull(originatingView);
+ mOriginatingView = Objects.requireNonNull(originatingView);
mOriginatingView.getLocationOnScreen(mViewPositionOnScreen);
// Allow the content rect to overshoot a little bit beyond the
// bottom view bound if necessary.
mBottomAllowance = context.getResources()
.getDimensionPixelSize(R.dimen.content_rect_bottom_clip_allowance);
mDisplaySize = new Point();
- setFloatingToolbar(Preconditions.checkNotNull(floatingToolbar));
+ setFloatingToolbar(Objects.requireNonNull(floatingToolbar));
}
private void setFloatingToolbar(FloatingToolbar floatingToolbar) {
@@ -328,7 +328,7 @@
private long mLastShowTime;
public FloatingToolbarVisibilityHelper(FloatingToolbar toolbar) {
- mToolbar = Preconditions.checkNotNull(toolbar);
+ mToolbar = Objects.requireNonNull(toolbar);
}
public void activate() {
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 2ee902a..58aaa80 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -16,13 +16,16 @@
package com.android.internal.view;
+import android.content.ComponentName;
import android.os.IBinder;
import android.os.ResultReceiver;
+import android.view.autofill.AutofillId;
import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.IInputSessionCallback;
@@ -35,6 +38,9 @@
oneway interface IInputMethod {
void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps);
+ void onCreateInlineSuggestionsRequest(in ComponentName componentName, in AutofillId autofillId,
+ in IInlineSuggestionsRequestCallback cb);
+
void bindInput(in InputBinding binding);
void unbindInput();
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index d618f67..1979e4f 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -35,7 +35,7 @@
import android.widget.AdapterView.OnItemClickListener;
import android.widget.PopupWindow.OnDismissListener;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
* A standard menu popup in which when a submenu is opened, it replaces its parent menu in the
@@ -113,7 +113,7 @@
public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
int popupStyleRes, boolean overflowOnly) {
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
mMenu = menu;
mOverflowOnly = overflowOnly;
final LayoutInflater inflater = LayoutInflater.from(context);
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index bb7423a..d7611dc 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -155,7 +155,7 @@
// TODO(b/65172902): Pass context in constructor when DecorView (and other callers)
// supports multi-display.
mContext = applyDefaultTheme(window.getContext());
- mWindow = Preconditions.checkNotNull(window);
+ mWindow = Objects.requireNonNull(window);
mPopup = new FloatingToolbarPopup(mContext, window.getDecorView());
}
@@ -165,7 +165,7 @@
* toolbar.
*/
public FloatingToolbar setMenu(Menu menu) {
- mMenu = Preconditions.checkNotNull(menu);
+ mMenu = Objects.requireNonNull(menu);
return this;
}
@@ -189,7 +189,7 @@
* toolbar.
*/
public FloatingToolbar setContentRect(Rect rect) {
- mContentRect.set(Preconditions.checkNotNull(rect));
+ mContentRect.set(Objects.requireNonNull(rect));
return this;
}
@@ -457,8 +457,8 @@
* from.
*/
public FloatingToolbarPopup(Context context, View parent) {
- mParent = Preconditions.checkNotNull(parent);
- mContext = Preconditions.checkNotNull(context);
+ mParent = Objects.requireNonNull(parent);
+ mContext = Objects.requireNonNull(context);
mContentContainer = createContentContainer(context);
mPopupWindow = createPopupWindow(mContentContainer);
mMarginHorizontal = parent.getResources()
@@ -578,7 +578,7 @@
* The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
*/
public void show(Rect contentRectOnScreen) {
- Preconditions.checkNotNull(contentRectOnScreen);
+ Objects.requireNonNull(contentRectOnScreen);
if (isShowing()) {
return;
@@ -650,7 +650,7 @@
* This is a no-op if this popup is not showing.
*/
public void updateCoordinates(Rect contentRectOnScreen) {
- Preconditions.checkNotNull(contentRectOnScreen);
+ Objects.requireNonNull(contentRectOnScreen);
if (!isShowing() || !mPopupWindow.isShowing()) {
return;
@@ -1134,11 +1134,11 @@
* Sets the touchable region of this popup to be the area occupied by its content.
*/
private void setContentAreaAsTouchableSurface() {
- Preconditions.checkNotNull(mMainPanelSize);
+ Objects.requireNonNull(mMainPanelSize);
final int width;
final int height;
if (mIsOverflowOpen) {
- Preconditions.checkNotNull(mOverflowPanelSize);
+ Objects.requireNonNull(mOverflowPanelSize);
width = mOverflowPanelSize.getWidth();
height = mOverflowPanelSize.getHeight();
} else {
@@ -1183,7 +1183,7 @@
*/
public List<MenuItem> layoutMainPanelItems(
List<MenuItem> menuItems, final int toolbarWidth) {
- Preconditions.checkNotNull(menuItems);
+ Objects.requireNonNull(menuItems);
int availableWidth = toolbarWidth;
@@ -1555,7 +1555,7 @@
private final FloatingToolbarPopup mPopup;
OverflowPanel(FloatingToolbarPopup popup) {
- super(Preconditions.checkNotNull(popup).mContext);
+ super(Objects.requireNonNull(popup).mContext);
this.mPopup = popup;
setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
@@ -1616,7 +1616,7 @@
private final Context mContext;
public OverflowPanelViewHelper(Context context, int iconTextSpacing) {
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
mIconTextSpacing = iconTextSpacing;
mSidePadding = context.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding);
@@ -1624,7 +1624,7 @@
}
public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
- Preconditions.checkNotNull(menuItem);
+ Objects.requireNonNull(menuItem);
if (convertView != null) {
updateMenuItemButton(
convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index f456349..9b87dd2 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -33,6 +33,7 @@
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
/**
* A class representing a lockscreen credential. It can be either an empty password, a pattern
@@ -67,7 +68,7 @@
* minimize the number of extra copies introduced.
*/
private LockscreenCredential(int type, byte[] credential) {
- Preconditions.checkNotNull(credential);
+ Objects.requireNonNull(credential);
if (type == CREDENTIAL_TYPE_NONE) {
Preconditions.checkArgument(credential.length == 0);
} else {
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index e6232e85..8a59c99 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
@@ -222,6 +223,8 @@
private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
+ private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>();
+
/**
* Map of system pre-defined, uniquely named actors; keys are namespace,
* value maps actor name to package name.
@@ -382,6 +385,10 @@
return mBugreportWhitelistedPackages;
}
+ public Set<String> getRollbackWhitelistedPackages() {
+ return mRollbackWhitelistedPackages;
+ }
+
/**
* Gets map of packagesNames to userTypes, dictating on which user types each package should be
* initially installed, and then removes this map from SystemConfig.
@@ -492,6 +499,19 @@
Environment.getSystemExtDirectory(), "etc", "sysconfig"), ALLOW_ALL);
readPermissions(Environment.buildPath(
Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);
+
+ // Skip loading configuration from apex if it is not a system process.
+ if (!isSystemProcess()) {
+ return;
+ }
+ // Read configuration of libs from apex module.
+ // TODO: Use a solid way to filter apex module folders?
+ for (File f: FileUtils.listFilesOrEmpty(Environment.getApexDirectory())) {
+ if (f.isFile() || f.getPath().contains("@")) {
+ continue;
+ }
+ readPermissions(Environment.buildPath(f, "etc", "permissions"), ALLOW_LIBS);
+ }
}
@VisibleForTesting
@@ -1078,6 +1098,16 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "rollback-whitelisted-app": {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mRollbackWhitelistedPackages.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
default: {
Slog.w(TAG, "Tag " + name + " is unknown in "
+ permFile + " at " + parser.getPositionDescription());
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 148b0a2..98ce8b0 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -110,7 +110,6 @@
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
"android_view_InputQueue.cpp",
- "android_view_FrameMetricsObserver.cpp",
"android_view_KeyCharacterMap.cpp",
"android_view_KeyEvent.cpp",
"android_view_MotionEvent.cpp",
@@ -142,8 +141,10 @@
"android_os_UEventObserver.cpp",
"android_os_VintfObject.cpp",
"android_os_VintfRuntimeInfo.cpp",
+ "android_os_incremental_IncrementalManager.cpp",
"android_net_LocalSocketImpl.cpp",
"android_net_NetUtils.cpp",
+ "android_service_DataLoaderService.cpp",
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
"android_util_StatsLog.cpp",
@@ -152,6 +153,7 @@
"android_util_StringBlock.cpp",
"android_util_XmlBlock.cpp",
"android_util_jar_StrictJarFile.cpp",
+ "android_media_AudioDeviceAddress.cpp",
"android_media_AudioEffectDescriptor.cpp",
"android_media_AudioRecord.cpp",
"android_media_AudioSystem.cpp",
@@ -175,7 +177,6 @@
"android_hardware_HardwareBuffer.cpp",
"android_hardware_SensorManager.cpp",
"android_hardware_SerialPort.cpp",
- "android_hardware_SoundTrigger.cpp",
"android_hardware_UsbDevice.cpp",
"android_hardware_UsbDeviceConnection.cpp",
"android_hardware_UsbRequest.cpp",
@@ -207,6 +208,7 @@
static_libs: [
"libasync_safe",
+ "libdmabufinfo",
"libgif",
"libseccomp_policy",
"libgrallocusage",
@@ -239,6 +241,8 @@
"libGLESv1_CM",
"libGLESv2",
"libGLESv3",
+ "libincfs",
+ "libdataloader",
"libvulkan",
"libETC1",
"libhardware",
@@ -255,7 +259,6 @@
"libpdfium",
"libimg_utils",
"libnetd_client",
- "libsoundtrigger",
"libprocessgroup",
"libnativebridge_lazy",
"libnativeloader_lazy",
@@ -349,6 +352,7 @@
"android_graphics_ColorSpace.cpp",
"android_graphics_drawable_AnimatedVectorDrawable.cpp",
"android_graphics_drawable_VectorDrawable.cpp",
+ "android_graphics_HardwareRendererObserver.cpp",
"android_graphics_Picture.cpp",
"android_nio_utils.cpp",
"android_view_DisplayListCanvas.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 97451a2..c41b19e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -80,12 +80,12 @@
extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
extern int register_android_hardware_SerialPort(JNIEnv *env);
-extern int register_android_hardware_SoundTrigger(JNIEnv *env);
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
+extern int register_android_media_AudioDeviceAddress(JNIEnv *env);
extern int register_android_media_AudioEffectDescriptor(JNIEnv *env);
extern int register_android_media_AudioRecord(JNIEnv *env);
extern int register_android_media_AudioSystem(JNIEnv *env);
@@ -129,6 +129,7 @@
extern int register_android_database_SQLiteConnection(JNIEnv* env);
extern int register_android_database_SQLiteGlobal(JNIEnv* env);
extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_media_MediaMetrics(JNIEnv *env);
extern int register_android_os_Debug(JNIEnv* env);
extern int register_android_os_GraphicsEnvironment(JNIEnv* env);
extern int register_android_os_HidlSupport(JNIEnv* env);
@@ -150,6 +151,8 @@
extern int register_android_os_HidlMemory(JNIEnv* env);
extern int register_android_os_MemoryFile(JNIEnv* env);
extern int register_android_os_SharedMemory(JNIEnv* env);
+extern int register_android_service_DataLoaderService(JNIEnv* env);
+extern int register_android_os_incremental_IncrementalManager(JNIEnv* env);
extern int register_android_net_LocalSocketImpl(JNIEnv* env);
extern int register_android_net_NetworkUtils(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv *env);
@@ -1451,6 +1454,7 @@
REG_JNI(register_android_os_NativeHandle),
REG_JNI(register_android_os_VintfObject),
REG_JNI(register_android_os_VintfRuntimeInfo),
+ REG_JNI(register_android_service_DataLoaderService),
REG_JNI(register_android_view_DisplayEventReceiver),
REG_JNI(register_android_view_RenderNodeAnimator),
REG_JNI(register_android_view_InputApplicationHandle),
@@ -1493,6 +1497,7 @@
REG_JNI(register_android_net_NetworkUtils),
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_android_os_SharedMemory),
+ REG_JNI(register_android_os_incremental_IncrementalManager),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
REG_JNI(register_com_android_internal_os_Zygote),
REG_JNI(register_com_android_internal_os_ZygoteInit),
@@ -1505,11 +1510,11 @@
REG_JNI(register_android_hardware_HardwareBuffer),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_hardware_SerialPort),
- REG_JNI(register_android_hardware_SoundTrigger),
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
+ REG_JNI(register_android_media_AudioDeviceAddress),
REG_JNI(register_android_media_AudioEffectDescriptor),
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioRecord),
@@ -1518,6 +1523,7 @@
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+ REG_JNI(register_android_media_MediaMetrics),
REG_JNI(register_android_media_MicrophoneInfo),
REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index 1290026..6c2a5a3 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -25,6 +25,7 @@
#include <SkPicture.h>
#include <SkPictureRecorder.h>
#include <hwui/AnimatedImageDrawable.h>
+#include <hwui/ImageDecoder.h>
#include <hwui/Canvas.h>
#include <utils/Looper.h>
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 3f05c3b..fa64fd1 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -7,7 +7,6 @@
#include "SkAndroidCodec.h"
#include "SkBRDAllocator.h"
#include "SkFrontBufferedStream.h"
-#include "SkMakeUnique.h"
#include "SkMath.h"
#include "SkPixelRef.h"
#include "SkStream.h"
@@ -586,7 +585,7 @@
Asset* asset = reinterpret_cast<Asset*>(native_asset);
// since we know we'll be done with the asset when we return, we can
// just use a simple wrapper
- return doDecode(env, skstd::make_unique<AssetStreamAdaptor>(asset), padding, options,
+ return doDecode(env, std::make_unique<AssetStreamAdaptor>(asset), padding, options,
inBitmapHandle, colorSpaceHandle);
}
@@ -594,7 +593,7 @@
jint offset, jint length, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle) {
AutoJavaByteArray ar(env, byteArray);
- return doDecode(env, skstd::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false),
+ return doDecode(env, std::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false),
nullptr, options, inBitmapHandle, colorSpaceHandle);
}
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 4d907f6..627f8f5 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -20,10 +20,12 @@
#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
#include "ImageDecoder.h"
+#include "NinePatchPeeker.h"
#include "Utils.h"
#include "core_jni_helpers.h"
#include <hwui/Bitmap.h>
+#include <hwui/ImageDecoder.h>
#include <HardwareBitmapUploader.h>
#include <SkAndroidCodec.h>
@@ -49,6 +51,28 @@
static jmethodID gCanvas_constructorMethodID;
static jmethodID gCanvas_releaseMethodID;
+// These need to stay in sync with ImageDecoder.java's Allocator constants.
+enum Allocator {
+ kDefault_Allocator = 0,
+ kSoftware_Allocator = 1,
+ kSharedMemory_Allocator = 2,
+ kHardware_Allocator = 3,
+};
+
+// These need to stay in sync with ImageDecoder.java's Error constants.
+enum Error {
+ kSourceException = 1,
+ kSourceIncomplete = 2,
+ kSourceMalformedData = 3,
+};
+
+// These need to stay in sync with PixelFormat.java's Format constants.
+enum PixelFormat {
+ kUnknown = 0,
+ kTranslucent = -3,
+ kOpaque = -1,
+};
+
// Clear and return any pending exception for handling other than throwing directly.
static jthrowable get_and_clear_exception(JNIEnv* env) {
jthrowable jexception = env->ExceptionOccurred();
@@ -59,7 +83,7 @@
}
// Throw a new ImageDecoder.DecodeException. Returns null for convenience.
-static jobject throw_exception(JNIEnv* env, ImageDecoder::Error error, const char* msg,
+static jobject throw_exception(JNIEnv* env, Error error, const char* msg,
jthrowable cause, jobject source) {
jstring jstr = nullptr;
if (msg) {
@@ -81,27 +105,27 @@
static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream,
jobject source, jboolean preferAnimation) {
if (!stream.get()) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to create a stream",
+ return throw_exception(env, kSourceMalformedData, "Failed to create a stream",
nullptr, source);
}
- std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
+ sk_sp<NinePatchPeeker> peeker(new NinePatchPeeker);
SkCodec::Result result;
auto codec = SkCodec::MakeFromStream(
- std::move(stream), &result, decoder->mPeeker.get(),
+ std::move(stream), &result, peeker.get(),
preferAnimation ? SkCodec::SelectionPolicy::kPreferAnimation
: SkCodec::SelectionPolicy::kPreferStillImage);
if (jthrowable jexception = get_and_clear_exception(env)) {
- return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source);
+ return throw_exception(env, kSourceException, "", jexception, source);
}
if (!codec) {
switch (result) {
case SkCodec::kIncompleteInput:
- return throw_exception(env, ImageDecoder::kSourceIncomplete, "", nullptr, source);
+ return throw_exception(env, kSourceIncomplete, "", nullptr, source);
default:
SkString msg;
msg.printf("Failed to create image decoder with message '%s'",
SkCodec::ResultToString(result));
- return throw_exception(env, ImageDecoder::kSourceMalformedData, msg.c_str(),
+ return throw_exception(env, kSourceMalformedData, msg.c_str(),
nullptr, source);
}
@@ -109,21 +133,22 @@
const bool animated = codec->getFrameCount() > 1;
if (jthrowable jexception = get_and_clear_exception(env)) {
- return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source);
+ return throw_exception(env, kSourceException, "", jexception, source);
}
- decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
+ auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
SkAndroidCodec::ExifOrientationBehavior::kRespect);
- if (!decoder->mCodec.get()) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "", nullptr, source);
+ if (!androidCodec.get()) {
+ return throw_exception(env, kSourceMalformedData, "", nullptr, source);
}
- const auto& info = decoder->mCodec->getInfo();
+ const auto& info = androidCodec->getInfo();
const int width = info.width();
const int height = info.height();
- const bool isNinePatch = decoder->mPeeker->mPatch != nullptr;
+ const bool isNinePatch = peeker->mPatch != nullptr;
+ ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker));
return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
- reinterpret_cast<jlong>(decoder.release()), width, height,
+ reinterpret_cast<jlong>(decoder), width, height,
animated, isNinePatch);
}
@@ -133,7 +158,7 @@
struct stat fdStat;
if (fstat(descriptor, &fdStat) == -1) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData,
+ return throw_exception(env, kSourceMalformedData,
"broken file descriptor; fstat returned -1", nullptr, source);
}
@@ -141,7 +166,7 @@
FILE* file = fdopen(dupDescriptor, "r");
if (file == NULL) {
close(dupDescriptor);
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "Could not open file",
+ return throw_exception(env, kSourceMalformedData, "Could not open file",
nullptr, source);
}
@@ -154,7 +179,7 @@
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false));
if (!stream.get()) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to create a stream",
+ return throw_exception(env, kSourceMalformedData, "Failed to create a stream",
nullptr, source);
}
@@ -177,7 +202,7 @@
std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
initialPosition, limit);
if (!stream) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to read ByteBuffer",
+ return throw_exception(env, kSourceMalformedData, "Failed to read ByteBuffer",
nullptr, source);
}
return native_create(env, std::move(stream), source, preferAnimation);
@@ -195,7 +220,7 @@
reinterpret_cast<jlong>(canvas.get()));
if (!jcanvas) {
doThrowOOME(env, "Failed to create Java Canvas for PostProcess!");
- return ImageDecoder::kUnknown;
+ return kUnknown;
}
// jcanvas now owns canvas.
@@ -206,43 +231,23 @@
static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jobject jdecoder, jboolean jpostProcess,
- jint desiredWidth, jint desiredHeight, jobject jsubset,
+ jint targetWidth, jint targetHeight, jobject jsubset,
jboolean requireMutable, jint allocator,
jboolean requireUnpremul, jboolean preferRamOverQuality,
jboolean asAlphaMask, jlong colorSpaceHandle,
jboolean extended) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- SkAndroidCodec* codec = decoder->mCodec.get();
- const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight);
- SkISize decodeSize = desiredSize;
- const int sampleSize = codec->computeSampleSize(&decodeSize);
- const bool scale = desiredSize != decodeSize;
- SkImageInfo decodeInfo = codec->getInfo().makeWH(decodeSize.width(), decodeSize.height());
- if (scale && requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) {
+ if (!decoder->setTargetSize(targetWidth, targetHeight)) {
+ doThrowISE(env, "Could not scale to target size!");
+ return nullptr;
+ }
+ if (requireUnpremul && !decoder->setOutAlphaType(kUnpremul_SkAlphaType)) {
doThrowISE(env, "Cannot scale unpremultiplied pixels!");
return nullptr;
}
- switch (decodeInfo.alphaType()) {
- case kUnpremul_SkAlphaType:
- if (!requireUnpremul) {
- decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
- }
- break;
- case kPremul_SkAlphaType:
- if (requireUnpremul) {
- decodeInfo = decodeInfo.makeAlphaType(kUnpremul_SkAlphaType);
- }
- break;
- case kOpaque_SkAlphaType:
- break;
- case kUnknown_SkAlphaType:
- doThrowIOE(env, "Unknown alpha type");
- return nullptr;
- }
-
SkColorType colorType = kN32_SkColorType;
- if (asAlphaMask && decodeInfo.colorType() == kGray_8_SkColorType) {
+ if (asAlphaMask && decoder->gray()) {
// We have to trick Skia to decode this to a single channel.
colorType = kGray_8_SkColorType;
} else if (preferRamOverQuality) {
@@ -250,12 +255,12 @@
// result incorrect. If we call the postProcess before now and record
// to a picture, we can know whether alpha was added, and if not, we
// can still use 565.
- if (decodeInfo.alphaType() == kOpaque_SkAlphaType && !jpostProcess) {
+ if (decoder->opaque() && !jpostProcess) {
// If the final result will be hardware, decoding to 565 and then
// uploading to the gpu as 8888 will not save memory. This still
// may save us from using F16, but do not go down to 565.
- if (allocator != ImageDecoder::kHardware_Allocator &&
- (allocator != ImageDecoder::kDefault_Allocator || requireMutable)) {
+ if (allocator != kHardware_Allocator &&
+ (allocator != kDefault_Allocator || requireMutable)) {
colorType = kRGB_565_SkColorType;
}
}
@@ -263,12 +268,12 @@
} else if (extended) {
colorType = kRGBA_F16_SkColorType;
} else {
- colorType = codec->computeOutputColorType(colorType);
+ colorType = decoder->mCodec->computeOutputColorType(colorType);
}
const bool isHardware = !requireMutable
- && (allocator == ImageDecoder::kDefault_Allocator ||
- allocator == ImageDecoder::kHardware_Allocator)
+ && (allocator == kDefault_Allocator ||
+ allocator == kHardware_Allocator)
&& colorType != kGray_8_SkColorType;
if (colorType == kRGBA_F16_SkColorType && isHardware &&
@@ -276,12 +281,28 @@
colorType = kN32_SkColorType;
}
- sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
- colorSpace = codec->computeOutputColorSpace(colorType, colorSpace);
- decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
+ if (!decoder->setOutColorType(colorType)) {
+ doThrowISE(env, "Failed to set out color type!");
+ return nullptr;
+ }
+
+ {
+ sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
+ colorSpace = decoder->mCodec->computeOutputColorSpace(colorType, colorSpace);
+ decoder->setOutColorSpace(std::move(colorSpace));
+ }
+
+ if (jsubset) {
+ SkIRect subset;
+ GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
+ if (!decoder->setCropRect(&subset)) {
+ doThrowISE(env, "Invalid crop rect!");
+ return nullptr;
+ }
+ }
SkBitmap bm;
- auto bitmapInfo = decodeInfo;
+ SkImageInfo bitmapInfo = decoder->getOutputInfo();
if (asAlphaMask && colorType == kGray_8_SkColorType) {
bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
}
@@ -291,10 +312,7 @@
}
sk_sp<Bitmap> nativeBitmap;
- // If we are going to scale or subset, we will create a new bitmap later on,
- // so use the heap for the temporary.
- // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
- if (allocator == ImageDecoder::kSharedMemory_Allocator && !scale && !jsubset) {
+ if (allocator == kSharedMemory_Allocator) {
nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
} else {
nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
@@ -302,16 +320,14 @@
if (!nativeBitmap) {
SkString msg;
msg.printf("OOM allocating Bitmap with dimensions %i x %i",
- decodeInfo.width(), decodeInfo.height());
+ bitmapInfo.width(), bitmapInfo.height());
doThrowOOME(env, msg.c_str());
return nullptr;
}
- SkAndroidCodec::AndroidOptions options;
- options.fSampleSize = sampleSize;
- auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options);
+ SkCodec::Result result = decoder->decode(bm.getPixels(), bm.rowBytes());
jthrowable jexception = get_and_clear_exception(env);
- int onPartialImageError = jexception ? ImageDecoder::kSourceException
+ int onPartialImageError = jexception ? kSourceException
: 0; // No error.
switch (result) {
case SkCodec::kSuccess:
@@ -321,12 +337,12 @@
break;
case SkCodec::kIncompleteInput:
if (!jexception) {
- onPartialImageError = ImageDecoder::kSourceIncomplete;
+ onPartialImageError = kSourceIncomplete;
}
break;
case SkCodec::kErrorInInput:
if (!jexception) {
- onPartialImageError = ImageDecoder::kSourceMalformedData;
+ onPartialImageError = kSourceMalformedData;
}
break;
default:
@@ -350,20 +366,21 @@
// Ignore ninepatch when post-processing.
if (!jpostProcess) {
// FIXME: Share more code with BitmapFactory.cpp.
- if (decoder->mPeeker->mPatch != nullptr) {
- size_t ninePatchArraySize = decoder->mPeeker->mPatch->serializedSize();
+ auto* peeker = reinterpret_cast<NinePatchPeeker*>(decoder->mPeeker.get());
+ if (peeker->mPatch != nullptr) {
+ size_t ninePatchArraySize = peeker->mPatch->serializedSize();
ninePatchChunk = env->NewByteArray(ninePatchArraySize);
if (ninePatchChunk == nullptr) {
doThrowOOME(env, "Failed to allocate nine patch chunk.");
return nullptr;
}
- env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize,
- reinterpret_cast<jbyte*>(decoder->mPeeker->mPatch));
+ env->SetByteArrayRegion(ninePatchChunk, 0, peeker->mPatchSize,
+ reinterpret_cast<jbyte*>(peeker->mPatch));
}
- if (decoder->mPeeker->mHasInsets) {
- ninePatchInsets = decoder->mPeeker->createNinePatchInsets(env, 1.0f);
+ if (peeker->mHasInsets) {
+ ninePatchInsets = peeker->createNinePatchInsets(env, 1.0f);
if (ninePatchInsets == nullptr) {
doThrowOOME(env, "Failed to allocate nine patch insets.");
return nullptr;
@@ -371,58 +388,6 @@
}
}
- if (scale || jsubset) {
- int translateX = 0;
- int translateY = 0;
- SkImageInfo scaledInfo;
- if (jsubset) {
- SkIRect subset;
- GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
-
- translateX = -subset.fLeft;
- translateY = -subset.fTop;
- scaledInfo = bitmapInfo.makeWH(subset.width(), subset.height());
- } else {
- scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
- }
- SkBitmap scaledBm;
- if (!scaledBm.setInfo(scaledInfo)) {
- doThrowIOE(env, "Failed scaled setInfo");
- return nullptr;
- }
-
- sk_sp<Bitmap> scaledPixelRef;
- if (allocator == ImageDecoder::kSharedMemory_Allocator) {
- scaledPixelRef = Bitmap::allocateAshmemBitmap(&scaledBm);
- } else {
- scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm);
- }
- if (!scaledPixelRef) {
- SkString msg;
- msg.printf("OOM allocating scaled Bitmap with dimensions %i x %i",
- desiredWidth, desiredHeight);
- doThrowOOME(env, msg.c_str());
- return nullptr;
- }
-
- SkPaint paint;
- paint.setBlendMode(SkBlendMode::kSrc);
- paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
-
- SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
- canvas.translate(translateX, translateY);
- if (scale) {
- float scaleX = (float) desiredWidth / decodeInfo.width();
- float scaleY = (float) desiredHeight / decodeInfo.height();
- canvas.scale(scaleX, scaleY);
- }
-
- canvas.drawBitmap(bm, 0.0f, 0.0f, &paint);
-
- bm.swap(scaledBm);
- nativeBitmap = std::move(scaledPixelRef);
- }
-
if (jpostProcess) {
std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
@@ -433,12 +398,12 @@
SkAlphaType newAlphaType = bm.alphaType();
switch (pixelFormat) {
- case ImageDecoder::kUnknown:
+ case kUnknown:
break;
- case ImageDecoder::kTranslucent:
+ case kTranslucent:
newAlphaType = kPremul_SkAlphaType;
break;
- case ImageDecoder::kOpaque:
+ case kOpaque:
newAlphaType = kOpaque_SkAlphaType;
break;
default:
@@ -477,7 +442,7 @@
return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets);
}
- if (allocator == ImageDecoder::kHardware_Allocator) {
+ if (allocator == kHardware_Allocator) {
doThrowOOME(env, "failed to allocate hardware Bitmap!");
return nullptr;
}
@@ -501,7 +466,7 @@
static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jobject outPadding) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- decoder->mPeeker->getPadding(env, outPadding);
+ reinterpret_cast<NinePatchPeeker*>(decoder->mPeeker.get())->getPadding(env, outPadding);
}
static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
diff --git a/core/jni/android/graphics/ImageDecoder.h b/core/jni/android/graphics/ImageDecoder.h
index fd9827b..8a7fa79 100644
--- a/core/jni/android/graphics/ImageDecoder.h
+++ b/core/jni/android/graphics/ImageDecoder.h
@@ -14,48 +14,12 @@
* limitations under the License.
*/
-#include "NinePatchPeeker.h"
-
#include <hwui/Canvas.h>
#include <jni.h>
-class SkAndroidCodec;
-
-using namespace android;
-
-struct ImageDecoder {
- // These need to stay in sync with ImageDecoder.java's Allocator constants.
- enum Allocator {
- kDefault_Allocator = 0,
- kSoftware_Allocator = 1,
- kSharedMemory_Allocator = 2,
- kHardware_Allocator = 3,
- };
-
- // These need to stay in sync with ImageDecoder.java's Error constants.
- enum Error {
- kSourceException = 1,
- kSourceIncomplete = 2,
- kSourceMalformedData = 3,
- };
-
- // These need to stay in sync with PixelFormat.java's Format constants.
- enum PixelFormat {
- kUnknown = 0,
- kTranslucent = -3,
- kOpaque = -1,
- };
-
- std::unique_ptr<SkAndroidCodec> mCodec;
- sk_sp<NinePatchPeeker> mPeeker;
-
- ImageDecoder()
- :mPeeker(new NinePatchPeeker)
- {}
-};
-
// Creates a Java Canvas object from canvas, calls jimageDecoder's PostProcess on it, and then
// releases the Canvas.
// Caller needs to check for exceptions.
-jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas);
+jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder,
+ std::unique_ptr<android::Canvas> canvas);
diff --git a/core/jni/android/graphics/apex/jni_runtime.cpp b/core/jni/android/graphics/apex/jni_runtime.cpp
index 7f9bac0..1f66153 100644
--- a/core/jni/android/graphics/apex/jni_runtime.cpp
+++ b/core/jni/android/graphics/apex/jni_runtime.cpp
@@ -52,6 +52,7 @@
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
@@ -71,7 +72,6 @@
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
-extern int register_android_view_FrameMetricsObserver(JNIEnv* env);
extern int register_android_view_RenderNode(JNIEnv* env);
extern int register_android_view_TextureLayer(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
@@ -105,6 +105,7 @@
REG_JNI(register_android_graphics_ColorFilter),
REG_JNI(register_android_graphics_DrawFilter),
REG_JNI(register_android_graphics_FontFamily),
+ REG_JNI(register_android_graphics_HardwareRendererObserver),
REG_JNI(register_android_graphics_ImageDecoder),
REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
REG_JNI(register_android_graphics_Interpolator),
@@ -135,7 +136,6 @@
REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_view_RenderNode),
REG_JNI(register_android_view_DisplayListCanvas),
- REG_JNI(register_android_view_FrameMetricsObserver),
REG_JNI(register_android_view_TextureLayer),
REG_JNI(register_android_view_ThreadedRenderer),
};
diff --git a/core/jni/android_graphics_HardwareRendererObserver.cpp b/core/jni/android_graphics_HardwareRendererObserver.cpp
new file mode 100644
index 0000000..89b77b0
--- /dev/null
+++ b/core/jni/android_graphics_HardwareRendererObserver.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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 "android_graphics_HardwareRendererObserver.h"
+
+#include "core_jni_helpers.h"
+#include "nativehelper/jni_macros.h"
+
+#include <array>
+
+namespace android {
+
+struct {
+ jmethodID callback;
+} gHardwareRendererObserverClassInfo;
+
+static JNIEnv* getenv(JavaVM* vm) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
+ }
+ return env;
+}
+
+HardwareRendererObserver::HardwareRendererObserver(JavaVM *vm, jobject observer) : mVm(vm) {
+ mObserverWeak = getenv(mVm)->NewWeakGlobalRef(observer);
+ LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
+ "unable to create frame stats observer reference");
+}
+
+HardwareRendererObserver::~HardwareRendererObserver() {
+ JNIEnv* env = getenv(mVm);
+ env->DeleteWeakGlobalRef(mObserverWeak);
+}
+
+bool HardwareRendererObserver::getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount) {
+ jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(metrics));
+ LOG_ALWAYS_FATAL_IF(bufferSize != HardwareRendererObserver::kBufferSize,
+ "Mismatched Java/Native FrameMetrics data format.");
+
+ FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
+ if (elem.hasData.load()) {
+ env->SetLongArrayRegion(metrics, 0, kBufferSize, elem.buffer);
+ *dropCount = elem.dropCount;
+ mNextInQueue = (mNextInQueue + 1) % kRingSize;
+ elem.hasData = false;
+ return true;
+ }
+
+ return false;
+}
+
+void HardwareRendererObserver::notify(const int64_t* stats) {
+ FrameMetricsNotification& elem = mRingBuffer[mNextFree];
+
+ if (!elem.hasData.load()) {
+ memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
+
+ elem.dropCount = mDroppedReports;
+ mDroppedReports = 0;
+ mNextFree = (mNextFree + 1) % kRingSize;
+ elem.hasData = true;
+
+ JNIEnv* env = getenv(mVm);
+ jobject target = env->NewLocalRef(mObserverWeak);
+ if (target != nullptr) {
+ env->CallVoidMethod(target, gHardwareRendererObserverClassInfo.callback);
+ env->DeleteLocalRef(target);
+ }
+ } else {
+ mDroppedReports++;
+ }
+}
+
+static jlong android_graphics_HardwareRendererObserver_createObserver(JNIEnv* env,
+ jobject observerObj) {
+ JavaVM* vm = nullptr;
+ if (env->GetJavaVM(&vm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Unable to get Java VM");
+ return 0;
+ }
+
+ HardwareRendererObserver* observer = new HardwareRendererObserver(vm, observerObj);
+ return reinterpret_cast<jlong>(observer);
+}
+
+static jint android_graphics_HardwareRendererObserver_getNextBuffer(JNIEnv* env, jobject,
+ jlong observerPtr,
+ jlongArray metrics) {
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
+ int dropCount = 0;
+ if (observer->getNextBuffer(env, metrics, &dropCount)) {
+ return dropCount;
+ } else {
+ return -1;
+ }
+}
+
+static const std::array gMethods = {
+ MAKE_JNI_NATIVE_METHOD("nCreateObserver", "()J",
+ android_graphics_HardwareRendererObserver_createObserver),
+ MAKE_JNI_NATIVE_METHOD("nGetNextBuffer", "(J[J)I",
+ android_graphics_HardwareRendererObserver_getNextBuffer),
+};
+
+int register_android_graphics_HardwareRendererObserver(JNIEnv* env) {
+
+ jclass observerClass = FindClassOrDie(env, "android/graphics/HardwareRendererObserver");
+ gHardwareRendererObserverClassInfo.callback = GetMethodIDOrDie(env, observerClass,
+ "notifyDataAvailable", "()V");
+
+ return RegisterMethodsOrDie(env, "android/graphics/HardwareRendererObserver",
+ gMethods.data(), gMethods.size());
+
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_graphics_HardwareRendererObserver.h b/core/jni/android_graphics_HardwareRendererObserver.h
new file mode 100644
index 0000000..62111fd
--- /dev/null
+++ b/core/jni/android_graphics_HardwareRendererObserver.h
@@ -0,0 +1,75 @@
+/*
+ * 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 "jni.h"
+
+#include <FrameInfo.h>
+#include <FrameMetricsObserver.h>
+
+namespace android {
+
+/*
+ * Implements JNI layer for hwui frame metrics reporting.
+ */
+class HardwareRendererObserver : public uirenderer::FrameMetricsObserver {
+public:
+ HardwareRendererObserver(JavaVM *vm, jobject observer);
+ ~HardwareRendererObserver();
+
+ /**
+ * Retrieves frame metrics for the oldest frame that the renderer has retained. The renderer
+ * will retain a buffer until it has been retrieved, via this method, or its internal storage
+ * is exhausted at which point it informs the caller of how many frames it has failed to store
+ * since the last time this method was invoked.
+ * @param env java env required to populate the provided buffer array
+ * @param metrics output parameter that represents the buffer of metrics that is to be filled
+ * @param dropCount output parameter that is updated to reflect the number of buffers that were
+ discarded since the last successful invocation of this method.
+ * @return true if there was data to populate the array and false otherwise. If false then
+ * neither the metrics buffer or dropCount will be modified.
+ */
+ bool getNextBuffer(JNIEnv* env, jlongArray metrics, int* dropCount);
+
+ void notify(const int64_t* stats) override;
+
+private:
+ static constexpr int kBufferSize = static_cast<int>(uirenderer::FrameInfoIndex::NumIndexes);
+ static constexpr int kRingSize = 3;
+
+ class FrameMetricsNotification {
+ public:
+ FrameMetricsNotification() {}
+
+ std::atomic_bool hasData = false;
+ int64_t buffer[kBufferSize];
+ int dropCount = 0;
+ private:
+ // non-copyable
+ FrameMetricsNotification(const FrameMetricsNotification&) = delete;
+ FrameMetricsNotification& operator=(const FrameMetricsNotification& ) = delete;
+ };
+
+ JavaVM* const mVm;
+ jweak mObserverWeak;
+
+ int mNextFree = 0;
+ int mNextInQueue = 0;
+ FrameMetricsNotification mRingBuffer[kRingSize];
+
+ int mDroppedReports = 0;
+};
+
+} // namespace android
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
deleted file mode 100644
index 0002f8b..0000000
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ /dev/null
@@ -1,967 +0,0 @@
-/*
-**
-** Copyright 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.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SoundTrigger-JNI"
-#include <utils/Log.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include "core_jni_helpers.h"
-#include <system/sound_trigger.h>
-#include <soundtrigger/SoundTriggerCallback.h>
-#include <soundtrigger/SoundTrigger.h>
-#include <utils/RefBase.h>
-#include <utils/Vector.h>
-#include <binder/IMemory.h>
-#include <binder/MemoryDealer.h>
-#include "android_media_AudioFormat.h"
-
-using namespace android;
-
-static jclass gArrayListClass;
-static struct {
- jmethodID add;
-} gArrayListMethods;
-
-static jclass gUUIDClass;
-static struct {
- jmethodID toString;
-} gUUIDMethods;
-
-static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger";
-static jclass gSoundTriggerClass;
-
-static const char* const kModuleClassPathName = "android/hardware/soundtrigger/SoundTriggerModule";
-static jclass gModuleClass;
-static struct {
- jfieldID mNativeContext;
- jfieldID mId;
-} gModuleFields;
-static jmethodID gPostEventFromNative;
-
-static const char* const kModulePropertiesClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ModuleProperties";
-static jclass gModulePropertiesClass;
-static jmethodID gModulePropertiesCstor;
-
-static const char* const kSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$SoundModel";
-static jclass gSoundModelClass;
-static struct {
- jfieldID uuid;
- jfieldID vendorUuid;
- jfieldID data;
-} gSoundModelFields;
-
-static const char* const kGenericSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$GenericSoundModel";
-static jclass gGenericSoundModelClass;
-
-static const char* const kKeyphraseClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$Keyphrase";
-static jclass gKeyphraseClass;
-static struct {
- jfieldID id;
- jfieldID recognitionModes;
- jfieldID locale;
- jfieldID text;
- jfieldID users;
-} gKeyphraseFields;
-
-static const char* const kKeyphraseSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseSoundModel";
-static jclass gKeyphraseSoundModelClass;
-static struct {
- jfieldID keyphrases;
-} gKeyphraseSoundModelFields;
-
-static const char* const kRecognitionConfigClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$RecognitionConfig";
-static jclass gRecognitionConfigClass;
-static struct {
- jfieldID captureRequested;
- jfieldID keyphrases;
- jfieldID data;
-} gRecognitionConfigFields;
-
-static const char* const kRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$RecognitionEvent";
-static jclass gRecognitionEventClass;
-static jmethodID gRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionEvent";
-static jclass gKeyphraseRecognitionEventClass;
-static jmethodID gKeyphraseRecognitionEventCstor;
-
-static const char* const kGenericRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$GenericRecognitionEvent";
-static jclass gGenericRecognitionEventClass;
-static jmethodID gGenericRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionExtraClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra";
-static jclass gKeyphraseRecognitionExtraClass;
-static jmethodID gKeyphraseRecognitionExtraCstor;
-static struct {
- jfieldID id;
- jfieldID recognitionModes;
- jfieldID coarseConfidenceLevel;
- jfieldID confidenceLevels;
-} gKeyphraseRecognitionExtraFields;
-
-static const char* const kConfidenceLevelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ConfidenceLevel";
-static jclass gConfidenceLevelClass;
-static jmethodID gConfidenceLevelCstor;
-static struct {
- jfieldID userId;
- jfieldID confidenceLevel;
-} gConfidenceLevelFields;
-
-static const char* const kAudioFormatClassPathName =
- "android/media/AudioFormat";
-static jclass gAudioFormatClass;
-static jmethodID gAudioFormatCstor;
-
-static const char* const kSoundModelEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$SoundModelEvent";
-static jclass gSoundModelEventClass;
-static jmethodID gSoundModelEventCstor;
-
-static Mutex gLock;
-
-enum {
- SOUNDTRIGGER_STATUS_OK = 0,
- SOUNDTRIGGER_STATUS_ERROR = INT_MIN,
- SOUNDTRIGGER_PERMISSION_DENIED = -1,
- SOUNDTRIGGER_STATUS_NO_INIT = -19,
- SOUNDTRIGGER_STATUS_BAD_VALUE = -22,
- SOUNDTRIGGER_STATUS_DEAD_OBJECT = -32,
- SOUNDTRIGGER_INVALID_OPERATION = -38,
-};
-
-enum {
- SOUNDTRIGGER_EVENT_RECOGNITION = 1,
- SOUNDTRIGGER_EVENT_SERVICE_DIED = 2,
- SOUNDTRIGGER_EVENT_SOUNDMODEL = 3,
- SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE = 4,
-};
-
-// ----------------------------------------------------------------------------
-// ref-counted object for callbacks
-class JNISoundTriggerCallback: public SoundTriggerCallback
-{
-public:
- JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
- ~JNISoundTriggerCallback();
-
- virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event);
- virtual void onSoundModelEvent(struct sound_trigger_model_event *event);
- virtual void onServiceStateChange(sound_trigger_service_state_t state);
- virtual void onServiceDied();
-
-private:
- jclass mClass; // Reference to SoundTrigger class
- jobject mObject; // Weak ref to SoundTrigger Java object to call on
-};
-
-JNISoundTriggerCallback::JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
-{
-
- // Hold onto the SoundTriggerModule class for use in calling the static method
- // that posts events to the application thread.
- jclass clazz = env->GetObjectClass(thiz);
- if (clazz == NULL) {
- ALOGE("Can't find class %s", kModuleClassPathName);
- return;
- }
- mClass = (jclass)env->NewGlobalRef(clazz);
-
- // We use a weak reference so the SoundTriggerModule object can be garbage collected.
- // The reference is only used as a proxy for callbacks.
- mObject = env->NewGlobalRef(weak_thiz);
-}
-
-JNISoundTriggerCallback::~JNISoundTriggerCallback()
-{
- // remove global references
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->DeleteGlobalRef(mObject);
- env->DeleteGlobalRef(mClass);
-}
-
-void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject jEvent = NULL;
- jbyteArray jData = NULL;
-
- if (event->data_size) {
- jData = env->NewByteArray(event->data_size);
- jbyte *nData = env->GetByteArrayElements(jData, NULL);
- memcpy(nData, (char *)event + event->data_offset, event->data_size);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
-
- jobject jAudioFormat = NULL;
- if (event->trigger_in_data || event->capture_available) {
- jint channelMask = (jint)audio_channel_mask_get_bits(event->audio_config.channel_mask);
- jint channelIndexMask = (jint)AUDIO_CHANNEL_NONE;
-
- switch (audio_channel_mask_get_representation(event->audio_config.channel_mask)) {
- case AUDIO_CHANNEL_REPRESENTATION_INDEX:
- channelIndexMask = channelMask;
- channelMask = (jint)AUDIO_CHANNEL_NONE;
- break;
- default:
- break;
- }
- jAudioFormat = env->NewObject(gAudioFormatClass,
- gAudioFormatCstor,
- audioFormatFromNative(event->audio_config.format),
- event->audio_config.sample_rate,
- channelMask,
- channelIndexMask);
-
- }
- if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_recognition_event *phraseEvent =
- (struct sound_trigger_phrase_recognition_event *)event;
-
- jobjectArray jExtras = env->NewObjectArray(phraseEvent->num_phrases,
- gKeyphraseRecognitionExtraClass, NULL);
- if (jExtras == NULL) {
- return;
- }
-
- for (size_t i = 0; i < phraseEvent->num_phrases; i++) {
- jobjectArray jConfidenceLevels = env->NewObjectArray(
- phraseEvent->phrase_extras[i].num_levels,
- gConfidenceLevelClass, NULL);
-
- if (jConfidenceLevels == NULL) {
- return;
- }
- for (size_t j = 0; j < phraseEvent->phrase_extras[i].num_levels; j++) {
- jobject jConfidenceLevel = env->NewObject(gConfidenceLevelClass,
- gConfidenceLevelCstor,
- phraseEvent->phrase_extras[i].levels[j].user_id,
- phraseEvent->phrase_extras[i].levels[j].level);
- env->SetObjectArrayElement(jConfidenceLevels, j, jConfidenceLevel);
- env->DeleteLocalRef(jConfidenceLevel);
- }
-
- jobject jNewExtra = env->NewObject(gKeyphraseRecognitionExtraClass,
- gKeyphraseRecognitionExtraCstor,
- phraseEvent->phrase_extras[i].id,
- phraseEvent->phrase_extras[i].recognition_modes,
- phraseEvent->phrase_extras[i].confidence_level,
- jConfidenceLevels);
-
- if (jNewExtra == NULL) {
- return;
- }
- env->SetObjectArrayElement(jExtras, i, jNewExtra);
- env->DeleteLocalRef(jNewExtra);
- env->DeleteLocalRef(jConfidenceLevels);
- }
- jEvent = env->NewObject(gKeyphraseRecognitionEventClass, gKeyphraseRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData, jExtras);
- env->DeleteLocalRef(jExtras);
- } else if (event->type == SOUND_MODEL_TYPE_GENERIC) {
- jEvent = env->NewObject(gGenericRecognitionEventClass, gGenericRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData);
- } else {
- jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData);
- }
-
- if (jAudioFormat != NULL) {
- env->DeleteLocalRef(jAudioFormat);
- }
- if (jData != NULL) {
- env->DeleteLocalRef(jData);
- }
-
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent);
-
- env->DeleteLocalRef(jEvent);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onSoundModelEvent(struct sound_trigger_model_event *event)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject jEvent = NULL;
- jbyteArray jData = NULL;
-
- if (event->data_size) {
- jData = env->NewByteArray(event->data_size);
- jbyte *nData = env->GetByteArrayElements(jData, NULL);
- memcpy(nData, (char *)event + event->data_offset, event->data_size);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
-
- jEvent = env->NewObject(gSoundModelEventClass, gSoundModelEventCstor,
- event->status, event->model, jData);
-
- env->DeleteLocalRef(jData);
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SOUNDMODEL, 0, 0, jEvent);
- env->DeleteLocalRef(jEvent);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onServiceStateChange(sound_trigger_service_state_t state)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE, state, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onServiceDied()
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
-
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SERVICE_DIED, 0, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-// ----------------------------------------------------------------------------
-
-static sp<SoundTrigger> getSoundTrigger(JNIEnv* env, jobject thiz)
-{
- Mutex::Autolock l(gLock);
- SoundTrigger* const st = (SoundTrigger*)env->GetLongField(thiz,
- gModuleFields.mNativeContext);
- return sp<SoundTrigger>(st);
-}
-
-static sp<SoundTrigger> setSoundTrigger(JNIEnv* env, jobject thiz, const sp<SoundTrigger>& module)
-{
- Mutex::Autolock l(gLock);
- sp<SoundTrigger> old = (SoundTrigger*)env->GetLongField(thiz,
- gModuleFields.mNativeContext);
- if (module.get()) {
- module->incStrong((void*)setSoundTrigger);
- }
- if (old != 0) {
- old->decStrong((void*)setSoundTrigger);
- }
- env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get());
- return old;
-}
-
-
-static jint
-android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
- jstring opPackageName, jobject jModules)
-{
- ALOGV("listModules");
-
- if (jModules == NULL) {
- ALOGE("listModules NULL AudioPatch ArrayList");
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- if (!env->IsInstanceOf(jModules, gArrayListClass)) {
- ALOGE("listModules not an arraylist");
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
-
- unsigned int numModules = 0;
- struct sound_trigger_module_descriptor *nModules = NULL;
-
- ScopedUtfChars opPackageNameStr(env, opPackageName);
- const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
- status_t status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
- if (status != NO_ERROR || numModules == 0) {
- return (jint)status;
- }
-
- nModules = (struct sound_trigger_module_descriptor *)
- calloc(numModules, sizeof(struct sound_trigger_module_descriptor));
-
- status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
- ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules);
-
- if (status != NO_ERROR) {
- numModules = 0;
- }
-
- for (size_t i = 0; i < numModules; i++) {
- char str[SOUND_TRIGGER_MAX_STRING_LEN];
-
- jstring implementor = env->NewStringUTF(nModules[i].properties.implementor);
- jstring description = env->NewStringUTF(nModules[i].properties.description);
- SoundTrigger::guidToString(&nModules[i].properties.uuid,
- str,
- SOUND_TRIGGER_MAX_STRING_LEN);
- jstring uuid = env->NewStringUTF(str);
-
- ALOGV("listModules module %zu id %d description %s maxSoundModels %d",
- i, nModules[i].handle, nModules[i].properties.description,
- nModules[i].properties.max_sound_models);
-
- jobject newModuleDesc = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor,
- nModules[i].handle,
- implementor, description, uuid,
- nModules[i].properties.version,
- nModules[i].properties.max_sound_models,
- nModules[i].properties.max_key_phrases,
- nModules[i].properties.max_users,
- nModules[i].properties.recognition_modes,
- nModules[i].properties.capture_transition,
- nModules[i].properties.max_buffer_ms,
- nModules[i].properties.concurrent_capture,
- nModules[i].properties.power_consumption_mw,
- nModules[i].properties.trigger_in_event);
-
- env->DeleteLocalRef(implementor);
- env->DeleteLocalRef(description);
- env->DeleteLocalRef(uuid);
- if (newModuleDesc == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
- env->CallBooleanMethod(jModules, gArrayListMethods.add, newModuleDesc);
- }
-
-exit:
- free(nModules);
- return (jint) status;
-}
-
-static void
-android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz,
- jstring opPackageName, jobject weak_this)
-{
- ALOGV("setup");
-
- ScopedUtfChars opPackageNameStr(env, opPackageName);
- const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
- sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this);
-
- sound_trigger_module_handle_t handle =
- (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId);
-
- sp<SoundTrigger> module = SoundTrigger::attach(opPackageNameString16, handle, callback);
- if (module == 0) {
- return;
- }
-
- setSoundTrigger(env, thiz, module);
-}
-
-static void
-android_hardware_SoundTrigger_detach(JNIEnv *env, jobject thiz)
-{
- ALOGV("detach");
- sp<SoundTrigger> module = setSoundTrigger(env, thiz, 0);
- ALOGV("detach module %p", module.get());
- if (module != 0) {
- ALOGV("detach module->detach()");
- module->detach();
- }
-}
-
-static void
-android_hardware_SoundTrigger_finalize(JNIEnv *env, jobject thiz)
-{
- ALOGV("finalize");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module != 0) {
- ALOGW("SoundTrigger finalized without being detached");
- }
- android_hardware_SoundTrigger_detach(env, thiz);
-}
-
-static jint
-android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
- jobject jSoundModel, jintArray jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- jbyte *nData = NULL;
- struct sound_trigger_sound_model *nSoundModel;
- jbyteArray jData;
- sp<MemoryDealer> memoryDealer;
- sp<IMemory> memory;
- size_t size;
- sound_model_handle_t handle = 0;
- jobject jUuid;
- jstring jUuidString;
- const char *nUuidString;
-
- ALOGV("loadSoundModel");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (jHandle == NULL) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- jsize jHandleLen = env->GetArrayLength(jHandle);
- if (jHandleLen == 0) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- jint *nHandle = env->GetIntArrayElements(jHandle, NULL);
- if (nHandle == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (!env->IsInstanceOf(jSoundModel, gSoundModelClass)) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
- size_t offset;
- sound_trigger_sound_model_type_t type;
- if (env->IsInstanceOf(jSoundModel, gKeyphraseSoundModelClass)) {
- offset = sizeof(struct sound_trigger_phrase_sound_model);
- type = SOUND_MODEL_TYPE_KEYPHRASE;
- } else if (env->IsInstanceOf(jSoundModel, gGenericSoundModelClass)) {
- offset = sizeof(struct sound_trigger_generic_sound_model);
- type = SOUND_MODEL_TYPE_GENERIC;
- } else {
- offset = sizeof(struct sound_trigger_sound_model);
- type = SOUND_MODEL_TYPE_UNKNOWN;
- }
-
- jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.uuid);
- jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
- nUuidString = env->GetStringUTFChars(jUuidString, NULL);
- sound_trigger_uuid_t nUuid;
- SoundTrigger::stringToGuid(nUuidString, &nUuid);
- env->ReleaseStringUTFChars(jUuidString, nUuidString);
- env->DeleteLocalRef(jUuidString);
-
- sound_trigger_uuid_t nVendorUuid;
- jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.vendorUuid);
- if (jUuid != NULL) {
- jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
- nUuidString = env->GetStringUTFChars(jUuidString, NULL);
- SoundTrigger::stringToGuid(nUuidString, &nVendorUuid);
- env->ReleaseStringUTFChars(jUuidString, nUuidString);
- env->DeleteLocalRef(jUuidString);
- } else {
- SoundTrigger::stringToGuid("00000000-0000-0000-0000-000000000000", &nVendorUuid);
- }
-
- jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data);
- if (jData == NULL) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
- size = env->GetArrayLength(jData);
-
- nData = env->GetByteArrayElements(jData, NULL);
- if (jData == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
-
- memoryDealer = new MemoryDealer(offset + size, "SoundTrigge-JNI::LoadModel");
- if (memoryDealer == 0) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
- memory = memoryDealer->allocate(offset + size);
- if (memory == 0 || memory->unsecurePointer() == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
-
- nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer();
-
- nSoundModel->type = type;
- nSoundModel->uuid = nUuid;
- nSoundModel->vendor_uuid = nVendorUuid;
- nSoundModel->data_size = size;
- nSoundModel->data_offset = offset;
- memcpy((char *)nSoundModel + offset, nData, size);
- if (type == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_sound_model *phraseModel =
- (struct sound_trigger_phrase_sound_model *)nSoundModel;
-
- jobjectArray jPhrases =
- (jobjectArray)env->GetObjectField(jSoundModel, gKeyphraseSoundModelFields.keyphrases);
- if (jPhrases == NULL) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
-
- size_t numPhrases = env->GetArrayLength(jPhrases);
- phraseModel->num_phrases = numPhrases;
- ALOGV("loadSoundModel numPhrases %zu", numPhrases);
- for (size_t i = 0; i < numPhrases; i++) {
- jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
- phraseModel->phrases[i].id =
- env->GetIntField(jPhrase,gKeyphraseFields.id);
- phraseModel->phrases[i].recognition_mode =
- env->GetIntField(jPhrase,gKeyphraseFields.recognitionModes);
-
- jintArray jUsers = (jintArray)env->GetObjectField(jPhrase, gKeyphraseFields.users);
- phraseModel->phrases[i].num_users = env->GetArrayLength(jUsers);
- jint *nUsers = env->GetIntArrayElements(jUsers, NULL);
- memcpy(phraseModel->phrases[i].users,
- nUsers,
- phraseModel->phrases[i].num_users * sizeof(int));
- env->ReleaseIntArrayElements(jUsers, nUsers, 0);
- env->DeleteLocalRef(jUsers);
-
- jstring jLocale = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.locale);
- const char *nLocale = env->GetStringUTFChars(jLocale, NULL);
- strncpy(phraseModel->phrases[i].locale,
- nLocale,
- SOUND_TRIGGER_MAX_LOCALE_LEN);
- jstring jText = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.text);
- const char *nText = env->GetStringUTFChars(jText, NULL);
- strncpy(phraseModel->phrases[i].text,
- nText,
- SOUND_TRIGGER_MAX_STRING_LEN);
-
- env->ReleaseStringUTFChars(jLocale, nLocale);
- env->DeleteLocalRef(jLocale);
- env->ReleaseStringUTFChars(jText, nText);
- env->DeleteLocalRef(jText);
- ALOGV("loadSoundModel phrases %zu text %s locale %s",
- i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale);
- env->DeleteLocalRef(jPhrase);
- }
- env->DeleteLocalRef(jPhrases);
- } else if (type == SOUND_MODEL_TYPE_GENERIC) {
- /* No initialization needed */
- }
- status = module->loadSoundModel(memory, &handle);
- ALOGV("loadSoundModel status %d handle %d", status, handle);
-
-exit:
- if (nHandle != NULL) {
- nHandle[0] = (jint)handle;
- env->ReleaseIntArrayElements(jHandle, nHandle, NULL);
- }
- if (nData != NULL) {
- env->ReleaseByteArrayElements(jData, nData, NULL);
- }
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_unloadSoundModel(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("unloadSoundModel");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->unloadSoundModel((sound_model_handle_t)jHandle);
-
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz,
- jint jHandle, jobject jConfig)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("startRecognition");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
-
- if (!env->IsInstanceOf(jConfig, gRecognitionConfigClass)) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
-
- jbyteArray jData = (jbyteArray)env->GetObjectField(jConfig, gRecognitionConfigFields.data);
- jsize dataSize = 0;
- jbyte *nData = NULL;
- if (jData != NULL) {
- dataSize = env->GetArrayLength(jData);
- if (dataSize == 0) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- nData = env->GetByteArrayElements(jData, NULL);
- if (nData == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- }
-
- size_t totalSize = sizeof(struct sound_trigger_recognition_config) + dataSize;
- sp<MemoryDealer> memoryDealer =
- new MemoryDealer(totalSize, "SoundTrigge-JNI::StartRecognition");
- if (memoryDealer == 0) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- sp<IMemory> memory = memoryDealer->allocate(totalSize);
- if (memory == 0 || memory->unsecurePointer() == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (dataSize != 0) {
- memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config),
- nData,
- dataSize);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
- env->DeleteLocalRef(jData);
- struct sound_trigger_recognition_config *config =
- (struct sound_trigger_recognition_config *)memory->unsecurePointer();
- config->data_size = dataSize;
- config->data_offset = sizeof(struct sound_trigger_recognition_config);
- config->capture_requested = env->GetBooleanField(jConfig,
- gRecognitionConfigFields.captureRequested);
-
- config->num_phrases = 0;
- jobjectArray jPhrases =
- (jobjectArray)env->GetObjectField(jConfig, gRecognitionConfigFields.keyphrases);
- if (jPhrases != NULL) {
- config->num_phrases = env->GetArrayLength(jPhrases);
- }
- ALOGV("startRecognition num phrases %d", config->num_phrases);
- for (size_t i = 0; i < config->num_phrases; i++) {
- jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
- config->phrases[i].id = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.id);
- config->phrases[i].recognition_modes = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.recognitionModes);
- config->phrases[i].confidence_level = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.coarseConfidenceLevel);
- config->phrases[i].num_levels = 0;
- jobjectArray jConfidenceLevels = (jobjectArray)env->GetObjectField(jPhrase,
- gKeyphraseRecognitionExtraFields.confidenceLevels);
- if (jConfidenceLevels != NULL) {
- config->phrases[i].num_levels = env->GetArrayLength(jConfidenceLevels);
- }
- ALOGV("startRecognition phrase %zu num_levels %d", i, config->phrases[i].num_levels);
- for (size_t j = 0; j < config->phrases[i].num_levels; j++) {
- jobject jConfidenceLevel = env->GetObjectArrayElement(jConfidenceLevels, j);
- config->phrases[i].levels[j].user_id = env->GetIntField(jConfidenceLevel,
- gConfidenceLevelFields.userId);
- config->phrases[i].levels[j].level = env->GetIntField(jConfidenceLevel,
- gConfidenceLevelFields.confidenceLevel);
- env->DeleteLocalRef(jConfidenceLevel);
- }
- ALOGV("startRecognition phrases %zu", i);
- env->DeleteLocalRef(jConfidenceLevels);
- env->DeleteLocalRef(jPhrase);
- }
- env->DeleteLocalRef(jPhrases);
-
- status = module->startRecognition(jHandle, memory);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_stopRecognition(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("stopRecognition");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->stopRecognition(jHandle);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_getModelState(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("getModelState");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->getModelState(jHandle);
- return status;
-}
-
-static const JNINativeMethod gMethods[] = {
- {"listModules",
- "(Ljava/lang/String;Ljava/util/ArrayList;)I",
- (void *)android_hardware_SoundTrigger_listModules},
-};
-
-
-static const JNINativeMethod gModuleMethods[] = {
- {"native_setup",
- "(Ljava/lang/String;Ljava/lang/Object;)V",
- (void *)android_hardware_SoundTrigger_setup},
- {"native_finalize",
- "()V",
- (void *)android_hardware_SoundTrigger_finalize},
- {"detach",
- "()V",
- (void *)android_hardware_SoundTrigger_detach},
- {"loadSoundModel",
- "(Landroid/hardware/soundtrigger/SoundTrigger$SoundModel;[I)I",
- (void *)android_hardware_SoundTrigger_loadSoundModel},
- {"unloadSoundModel",
- "(I)I",
- (void *)android_hardware_SoundTrigger_unloadSoundModel},
- {"startRecognition",
- "(ILandroid/hardware/soundtrigger/SoundTrigger$RecognitionConfig;)I",
- (void *)android_hardware_SoundTrigger_startRecognition},
- {"stopRecognition",
- "(I)I",
- (void *)android_hardware_SoundTrigger_stopRecognition},
- {"getModelState",
- "(I)I",
- (void *)android_hardware_SoundTrigger_getModelState},
-};
-
-int register_android_hardware_SoundTrigger(JNIEnv *env)
-{
- jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
- gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
- gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
-
- jclass uuidClass = FindClassOrDie(env, "java/util/UUID");
- gUUIDClass = MakeGlobalRefOrDie(env, uuidClass);
- gUUIDMethods.toString = GetMethodIDOrDie(env, uuidClass, "toString", "()Ljava/lang/String;");
-
- jclass lClass = FindClassOrDie(env, kSoundTriggerClassPathName);
- gSoundTriggerClass = MakeGlobalRefOrDie(env, lClass);
-
- jclass moduleClass = FindClassOrDie(env, kModuleClassPathName);
- gModuleClass = MakeGlobalRefOrDie(env, moduleClass);
- gPostEventFromNative = GetStaticMethodIDOrDie(env, moduleClass, "postEventFromNative",
- "(Ljava/lang/Object;IIILjava/lang/Object;)V");
- gModuleFields.mNativeContext = GetFieldIDOrDie(env, moduleClass, "mNativeContext", "J");
- gModuleFields.mId = GetFieldIDOrDie(env, moduleClass, "mId", "I");
-
- jclass modulePropertiesClass = FindClassOrDie(env, kModulePropertiesClassPathName);
- gModulePropertiesClass = MakeGlobalRefOrDie(env, modulePropertiesClass);
- gModulePropertiesCstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>",
- "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZIZ)V");
-
- jclass soundModelClass = FindClassOrDie(env, kSoundModelClassPathName);
- gSoundModelClass = MakeGlobalRefOrDie(env, soundModelClass);
- gSoundModelFields.uuid = GetFieldIDOrDie(env, soundModelClass, "uuid", "Ljava/util/UUID;");
- gSoundModelFields.vendorUuid = GetFieldIDOrDie(env, soundModelClass, "vendorUuid",
- "Ljava/util/UUID;");
- gSoundModelFields.data = GetFieldIDOrDie(env, soundModelClass, "data", "[B");
-
- jclass genericSoundModelClass = FindClassOrDie(env, kGenericSoundModelClassPathName);
- gGenericSoundModelClass = MakeGlobalRefOrDie(env, genericSoundModelClass);
-
- jclass keyphraseClass = FindClassOrDie(env, kKeyphraseClassPathName);
- gKeyphraseClass = MakeGlobalRefOrDie(env, keyphraseClass);
- gKeyphraseFields.id = GetFieldIDOrDie(env, keyphraseClass, "id", "I");
- gKeyphraseFields.recognitionModes = GetFieldIDOrDie(env, keyphraseClass, "recognitionModes",
- "I");
- gKeyphraseFields.locale = GetFieldIDOrDie(env, keyphraseClass, "locale", "Ljava/lang/String;");
- gKeyphraseFields.text = GetFieldIDOrDie(env, keyphraseClass, "text", "Ljava/lang/String;");
- gKeyphraseFields.users = GetFieldIDOrDie(env, keyphraseClass, "users", "[I");
-
- jclass keyphraseSoundModelClass = FindClassOrDie(env, kKeyphraseSoundModelClassPathName);
- gKeyphraseSoundModelClass = MakeGlobalRefOrDie(env, keyphraseSoundModelClass);
- gKeyphraseSoundModelFields.keyphrases = GetFieldIDOrDie(env, keyphraseSoundModelClass,
- "keyphrases",
- "[Landroid/hardware/soundtrigger/SoundTrigger$Keyphrase;");
-
- jclass recognitionEventClass = FindClassOrDie(env, kRecognitionEventClassPathName);
- gRecognitionEventClass = MakeGlobalRefOrDie(env, recognitionEventClass);
- gRecognitionEventCstor = GetMethodIDOrDie(env, recognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
- jclass keyphraseRecognitionEventClass = FindClassOrDie(env,
- kKeyphraseRecognitionEventClassPathName);
- gKeyphraseRecognitionEventClass = MakeGlobalRefOrDie(env, keyphraseRecognitionEventClass);
- gKeyphraseRecognitionEventCstor = GetMethodIDOrDie(env, keyphraseRecognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
-
- jclass genericRecognitionEventClass = FindClassOrDie(env,
- kGenericRecognitionEventClassPathName);
- gGenericRecognitionEventClass = MakeGlobalRefOrDie(env, genericRecognitionEventClass);
- gGenericRecognitionEventCstor = GetMethodIDOrDie(env, genericRecognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
- jclass keyRecognitionConfigClass = FindClassOrDie(env, kRecognitionConfigClassPathName);
- gRecognitionConfigClass = MakeGlobalRefOrDie(env, keyRecognitionConfigClass);
- gRecognitionConfigFields.captureRequested = GetFieldIDOrDie(env, keyRecognitionConfigClass,
- "captureRequested", "Z");
- gRecognitionConfigFields.keyphrases = GetFieldIDOrDie(env, keyRecognitionConfigClass,
- "keyphrases", "[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;");
- gRecognitionConfigFields.data = GetFieldIDOrDie(env, keyRecognitionConfigClass, "data", "[B");
-
- jclass keyphraseRecognitionExtraClass = FindClassOrDie(env,
- kKeyphraseRecognitionExtraClassPathName);
- gKeyphraseRecognitionExtraClass = MakeGlobalRefOrDie(env, keyphraseRecognitionExtraClass);
- gKeyphraseRecognitionExtraCstor = GetMethodIDOrDie(env, keyphraseRecognitionExtraClass,
- "<init>", "(III[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
- gKeyphraseRecognitionExtraFields.id = GetFieldIDOrDie(env, gKeyphraseRecognitionExtraClass,
- "id", "I");
- gKeyphraseRecognitionExtraFields.recognitionModes = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "recognitionModes", "I");
- gKeyphraseRecognitionExtraFields.coarseConfidenceLevel = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "coarseConfidenceLevel", "I");
- gKeyphraseRecognitionExtraFields.confidenceLevels = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "confidenceLevels",
- "[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;");
-
- jclass confidenceLevelClass = FindClassOrDie(env, kConfidenceLevelClassPathName);
- gConfidenceLevelClass = MakeGlobalRefOrDie(env, confidenceLevelClass);
- gConfidenceLevelCstor = GetMethodIDOrDie(env, confidenceLevelClass, "<init>", "(II)V");
- gConfidenceLevelFields.userId = GetFieldIDOrDie(env, confidenceLevelClass, "userId", "I");
- gConfidenceLevelFields.confidenceLevel = GetFieldIDOrDie(env, confidenceLevelClass,
- "confidenceLevel", "I");
-
- jclass audioFormatClass = FindClassOrDie(env, kAudioFormatClassPathName);
- gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
- gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(IIII)V");
-
- jclass soundModelEventClass = FindClassOrDie(env, kSoundModelEventClassPathName);
- gSoundModelEventClass = MakeGlobalRefOrDie(env, soundModelEventClass);
- gSoundModelEventCstor = GetMethodIDOrDie(env, soundModelEventClass, "<init>", "(II[B)V");
-
-
- RegisterMethodsOrDie(env, kSoundTriggerClassPathName, gMethods, NELEM(gMethods));
- return RegisterMethodsOrDie(env, kModuleClassPathName, gModuleMethods, NELEM(gModuleMethods));
-}
diff --git a/core/jni/android_media_AudioDeviceAddress.cpp b/core/jni/android_media_AudioDeviceAddress.cpp
new file mode 100644
index 0000000..5f39f7e
--- /dev/null
+++ b/core/jni/android_media_AudioDeviceAddress.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 "core_jni_helpers.h"
+#include "android_media_AudioDeviceAddress.h"
+#include "android_media_AudioErrors.h"
+
+#include <media/AudioDeviceTypeAddr.h>
+
+using namespace android;
+
+static jclass gAudioDeviceAddressClass;
+static jmethodID gAudioDeviceAddressCstor;
+
+namespace android {
+
+jint createAudioDeviceAddressFromNative(
+ JNIEnv *env, jobject *jAudioDeviceAddress,
+ const AudioDeviceTypeAddr *devTypeAddr) {
+ jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
+ jint jNativeType = (jint)devTypeAddr->mType;
+ ScopedLocalRef<jstring> jAddress(env, env->NewStringUTF(devTypeAddr->mAddress.data()));
+
+ *jAudioDeviceAddress = env->NewObject(gAudioDeviceAddressClass, gAudioDeviceAddressCstor,
+ jNativeType, jAddress.get());
+
+ return jStatus;
+}
+
+}
+
+int register_android_media_AudioDeviceAddress(JNIEnv *env)
+{
+ jclass audioDeviceTypeAddressClass = FindClassOrDie(env, "android/media/AudioDeviceAddress");
+ gAudioDeviceAddressClass = MakeGlobalRefOrDie(env, audioDeviceTypeAddressClass);
+ gAudioDeviceAddressCstor = GetMethodIDOrDie(env, audioDeviceTypeAddressClass, "<init>",
+ "(ILjava/lang/String;)V");
+
+ return 0;
+}
diff --git a/core/jni/android_media_AudioDeviceAddress.h b/core/jni/android_media_AudioDeviceAddress.h
new file mode 100644
index 0000000..c66b179
--- /dev/null
+++ b/core/jni/android_media_AudioDeviceAddress.h
@@ -0,0 +1,33 @@
+/*
+ * 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 ANDROID_MEDIA_AUDIODEVICEADDRESS_H
+#define ANDROID_MEDIA_AUDIODEVICEADDRESS_H
+
+#include <system/audio.h>
+#include <media/AudioDeviceTypeAddr.h>
+
+#include "jni.h"
+
+namespace android {
+
+// Create a Java AudioDeviceAddress instance from a C++ AudioDeviceTypeAddress
+
+extern jint createAudioDeviceAddressFromNative(JNIEnv *env, jobject *jAudioDeviceAddress,
+ const AudioDeviceTypeAddr *devTypeAddr);
+} // namespace android
+
+#endif
\ No newline at end of file
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 99b5f85..a3c455b 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -38,6 +38,7 @@
#define ENCODING_AC4 17
#define ENCODING_E_AC3_JOC 18
#define ENCODING_DOLBY_MAT 19
+#define ENCODING_OPUS 20
#define ENCODING_INVALID 0
#define ENCODING_DEFAULT 1
@@ -88,6 +89,8 @@
return AUDIO_FORMAT_DEFAULT;
case ENCODING_DOLBY_MAT:
return AUDIO_FORMAT_MAT;
+ case ENCODING_OPUS:
+ return AUDIO_FORMAT_OPUS;
default:
return AUDIO_FORMAT_INVALID;
}
@@ -142,6 +145,8 @@
case AUDIO_FORMAT_MAT_2_0:
case AUDIO_FORMAT_MAT_2_1:
return ENCODING_DOLBY_MAT;
+ case AUDIO_FORMAT_OPUS:
+ return ENCODING_OPUS;
case AUDIO_FORMAT_DEFAULT:
return ENCODING_DEFAULT;
default:
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 342aba0..6cbc587 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -763,7 +763,7 @@
}
// get what we have for the metrics from the record session
- MediaAnalyticsItem *item = NULL;
+ mediametrics::Item *item = NULL;
status_t err = lpRecord->getMetrics(item);
if (err != OK) {
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 01f9d0b0..79cf019 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -26,19 +26,19 @@
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
+#include "android_media_AudioAttributes.h"
+#include "android_media_AudioDeviceAddress.h"
+#include "android_media_AudioEffectDescriptor.h"
+#include "android_media_AudioErrors.h"
+#include "android_media_AudioFormat.h"
+#include "android_media_MicrophoneInfo.h"
#include <audiomanager/AudioManager.h>
-#include <media/AudioDeviceTypeAddr.h>
-#include <media/AudioSystem.h>
#include <media/AudioPolicy.h>
+#include <media/AudioSystem.h>
#include <media/MicrophoneInfo.h>
#include <nativehelper/ScopedLocalRef.h>
#include <system/audio.h>
#include <system/audio_policy.h>
-#include "android_media_AudioEffectDescriptor.h"
-#include "android_media_AudioFormat.h"
-#include "android_media_AudioErrors.h"
-#include "android_media_MicrophoneInfo.h"
-#include "android_media_AudioAttributes.h"
// ----------------------------------------------------------------------------
@@ -2254,9 +2254,9 @@
android_media_AudioSystem_setAudioHalPids(JNIEnv *env, jobject clazz, jintArray jPids)
{
if (jPids == NULL) {
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return (jint) AUDIO_JAVA_BAD_VALUE;
}
- pid_t *nPidsArray = (pid_t *)env->GetIntArrayElements(jPids, NULL);
+ pid_t *nPidsArray = (pid_t *) env->GetIntArrayElements(jPids, NULL);
std::vector<pid_t> nPids(nPidsArray, nPidsArray + env->GetArrayLength(jPids));
status_t status = AudioSystem::setAudioHalPids(nPids);
env->ReleaseIntArrayElements(jPids, nPidsArray, 0);
@@ -2270,6 +2270,48 @@
return AudioSystem::isCallScreenModeSupported();
}
+static jint
+android_media_AudioSystem_setPreferredDeviceForStrategy(JNIEnv *env, jobject thiz,
+ jint strategy, jint deviceType, jstring deviceAddress) {
+
+ const char *c_address = env->GetStringUTFChars(deviceAddress, NULL);
+ int status = check_AudioSystem_Command(
+ AudioSystem::setPreferredDeviceForStrategy((product_strategy_t) strategy,
+ AudioDeviceTypeAddr(deviceType, c_address)));
+ env->ReleaseStringUTFChars(deviceAddress, c_address);
+ return (jint) status;
+}
+
+static jint
+android_media_AudioSystem_removePreferredDeviceForStrategy(JNIEnv *env, jobject thiz, jint strategy)
+{
+ return (jint) check_AudioSystem_Command(
+ AudioSystem::removePreferredDeviceForStrategy((product_strategy_t) strategy));
+}
+
+static jint
+android_media_AudioSystem_getPreferredDeviceForStrategy(JNIEnv *env, jobject thiz,
+ jint strategy, jobjectArray jDeviceArray)
+{
+ if (jDeviceArray == nullptr || env->GetArrayLength(jDeviceArray) != 1) {
+ ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ AudioDeviceTypeAddr elDevice;
+ status_t status = check_AudioSystem_Command(
+ AudioSystem::getPreferredDeviceForStrategy((product_strategy_t) strategy, elDevice));
+ if (status != NO_ERROR) {
+ return (jint) status;
+ }
+ jobject jAudioDeviceAddress = NULL;
+ jint jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &elDevice);
+ if (jStatus == AUDIO_JAVA_SUCCESS) {
+ env->SetObjectArrayElement(jDeviceArray, 0, jAudioDeviceAddress);
+ }
+ return jStatus;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -2350,6 +2392,9 @@
{"setRttEnabled", "(Z)I", (void *)android_media_AudioSystem_setRttEnabled},
{"setAudioHalPids", "([I)I", (void *)android_media_AudioSystem_setAudioHalPids},
{"isCallScreeningModeSupported", "()Z", (void *)android_media_AudioSystem_isCallScreeningModeSupported},
+ {"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},
};
static const JNINativeMethod gEventHandlerMethods[] = {
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index c5049ec..c979133 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1032,7 +1032,7 @@
}
// get what we have for the metrics from the track
- MediaAnalyticsItem *item = NULL;
+ mediametrics::Item *item = NULL;
status_t err = lpTrack->getMetrics(item);
if (err != OK) {
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index d62d2d9..6e0d5d8 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -43,6 +43,7 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
#include "jni.h"
+#include <dmabufinfo/dmabufinfo.h>
#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
#include <memtrack/memtrack.h>
@@ -560,6 +561,7 @@
MEMINFO_VMALLOC_USED,
MEMINFO_PAGE_TABLES,
MEMINFO_KERNEL_STACK,
+ MEMINFO_KERNEL_RECLAIMABLE,
MEMINFO_COUNT
};
@@ -780,6 +782,59 @@
return zramFreeKb;
}
+static jlong android_os_Debug_getIonHeapsSizeKb(JNIEnv* env, jobject clazz) {
+ jlong heapsSizeKb = 0;
+ uint64_t size;
+
+ if (meminfo::ReadIonHeapsSizeKb(&size)) {
+ heapsSizeKb = size;
+ }
+
+ return heapsSizeKb;
+}
+
+static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) {
+ jlong poolsSizeKb = 0;
+ uint64_t size;
+
+ if (meminfo::ReadIonPoolsSizeKb(&size)) {
+ poolsSizeKb = size;
+ }
+
+ return poolsSizeKb;
+}
+
+static jlong android_os_Debug_getIonMappedSizeKb(JNIEnv* env, jobject clazz) {
+ jlong ionPss = 0;
+ std::vector<dmabufinfo::DmaBuffer> dmabufs;
+
+ std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir);
+ if (!dir) {
+ LOG(ERROR) << "Failed to open /proc directory";
+ return false;
+ }
+
+ struct dirent* dent;
+ while ((dent = readdir(dir.get()))) {
+ if (dent->d_type != DT_DIR) continue;
+
+ int pid = atoi(dent->d_name);
+ if (pid == 0) {
+ continue;
+ }
+
+ if (!AppendDmaBufInfo(pid, &dmabufs, false)) {
+ LOG(ERROR) << "Failed to read maps for pid " << pid;
+ }
+ }
+
+ for (const dmabufinfo::DmaBuffer& buf : dmabufs) {
+ ionPss += buf.size() / 1024;
+ }
+
+ return ionPss;
+}
+
/*
* JNI registration.
*/
@@ -823,6 +878,12 @@
(void*)android_os_Debug_getUnreachableMemory },
{ "getZramFreeKb", "()J",
(void*)android_os_Debug_getFreeZramKb },
+ { "getIonHeapsSizeKb", "()J",
+ (void*)android_os_Debug_getIonHeapsSizeKb },
+ { "getIonPoolsSizeKb", "()J",
+ (void*)android_os_Debug_getIonPoolsSizeKb },
+ { "getIonMappedSizeKb", "()J",
+ (void*)android_os_Debug_getIonMappedSizeKb },
};
int register_android_os_Debug(JNIEnv *env)
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index bd82bd9..0f7611a 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -50,10 +50,6 @@
callback(buffer.data());
}
-static jlong android_os_Trace_nativeGetEnabledTags(JNIEnv*, jclass) {
- return atrace_get_enabled_tags();
-}
-
static void android_os_Trace_nativeTraceCounter(JNIEnv* env, jclass,
jlong tag, jstring nameStr, jlong value) {
withString(env, nameStr, [tag, value](char* str) {
@@ -96,9 +92,6 @@
static const JNINativeMethod gTraceMethods[] = {
/* name, signature, funcPtr */
- { "nativeGetEnabledTags",
- "()J",
- (void*)android_os_Trace_nativeGetEnabledTags },
{ "nativeSetAppTracingAllowed",
"(Z)V",
(void*)android_os_Trace_nativeSetAppTracingAllowed },
@@ -123,6 +116,11 @@
{ "nativeAsyncTraceEnd",
"(JLjava/lang/String;I)V",
(void*)android_os_Trace_nativeAsyncTraceEnd },
+
+ // ----------- @CriticalNative ----------------
+ { "nativeGetEnabledTags",
+ "()J",
+ (void*)atrace_get_enabled_tags },
};
int register_android_os_Trace(JNIEnv* env) {
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index ee11b61..25ffbab 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -96,28 +96,9 @@
return toJavaStringArray(env, cStrings);
}
-static jint android_os_VintfObject_verify(JNIEnv* env, jclass, jobjectArray packageInfo) {
- std::vector<std::string> cPackageInfo;
- if (packageInfo) {
- size_t count = env->GetArrayLength(packageInfo);
- cPackageInfo.resize(count);
- for (size_t i = 0; i < count; ++i) {
- jstring element = (jstring)env->GetObjectArrayElement(packageInfo, i);
- const char *cString = env->GetStringUTFChars(element, NULL /* isCopy */);
- cPackageInfo[i] = cString;
- env->ReleaseStringUTFChars(element, cString);
- }
- }
- std::string error;
- int32_t status = VintfObject::CheckCompatibility(cPackageInfo, &error);
- if (status)
- LOG(WARNING) << "VintfObject.verify() returns " << status << ": " << error;
- return status;
-}
-
static jint android_os_VintfObject_verifyWithoutAvb(JNIEnv* env, jclass) {
std::string error;
- int32_t status = VintfObject::CheckCompatibility({}, &error,
+ int32_t status = VintfObject::GetInstance()->checkCompatibility(&error,
::android::vintf::CheckFlags::DISABLE_AVB_CHECK);
if (status)
LOG(WARNING) << "VintfObject.verifyWithoutAvb() returns " << status << ": " << error;
@@ -170,7 +151,6 @@
static const JNINativeMethod gVintfObjectMethods[] = {
{"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
- {"verify", "([Ljava/lang/String;)I", (void*)android_os_VintfObject_verify},
{"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
{"getHalNamesAndVersions", "()[Ljava/lang/String;", (void*)android_os_VintfObject_getHalNamesAndVersions},
{"getSepolicyVersion", "()Ljava/lang/String;", (void*)android_os_VintfObject_getSepolicyVersion},
diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp
new file mode 100644
index 0000000..698062a
--- /dev/null
+++ b/core/jni/android_os_incremental_IncrementalManager.cpp
@@ -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.
+ */
+
+#define LOG_TAG "incremental_manager-jni"
+
+#include "core_jni_helpers.h"
+#include "incfs_ndk.h"
+#include "jni.h"
+#include "nativehelper/JNIHelp.h"
+
+#include <iterator>
+#include <memory>
+
+namespace android {
+
+static jboolean nativeIsIncrementalPath(JNIEnv* env,
+ jobject clazz,
+ jstring javaPath) {
+ ScopedUtfChars path(env, javaPath);
+ return (jboolean)IncFs_IsIncFsPath(path.c_str());
+}
+
+static const JNINativeMethod method_table[] = {
+ {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z",
+ (void*)nativeIsIncrementalPath},
+};
+
+int register_android_os_incremental_IncrementalManager(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager",
+ method_table, std::size(method_table));
+}
+
+} // namespace android
diff --git a/core/jni/android_service_DataLoaderService.cpp b/core/jni/android_service_DataLoaderService.cpp
new file mode 100644
index 0000000..a62d127
--- /dev/null
+++ b/core/jni/android_service_DataLoaderService.cpp
@@ -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.
+ */
+
+#define LOG_TAG "dataloader-jni"
+
+#include "core_jni_helpers.h"
+#include "dataloader_ndk.h"
+
+namespace android {
+namespace {
+
+static jboolean nativeCreateDataLoader(JNIEnv* env,
+ jobject thiz,
+ jint storageId,
+ jobject control,
+ jobject params,
+ jobject callback) {
+ return DataLoaderService_OnCreate(env, thiz,
+ storageId, control, params, callback);
+}
+
+static jboolean nativeStartDataLoader(JNIEnv* env,
+ jobject thiz,
+ jint storageId) {
+ return DataLoaderService_OnStart(storageId);
+}
+
+static jboolean nativeStopDataLoader(JNIEnv* env,
+ jobject thiz,
+ jint storageId) {
+ return DataLoaderService_OnStop(storageId);
+}
+
+static jboolean nativeDestroyDataLoader(JNIEnv* env,
+ jobject thiz,
+ jint storageId) {
+ return DataLoaderService_OnDestroy(storageId);
+}
+
+
+static jboolean nativePrepareImage(JNIEnv* env, jobject thiz, jint storageId, jobject addedFiles, jobject removedFiles) {
+ return DataLoaderService_OnPrepareImage(storageId, addedFiles, removedFiles);
+}
+
+static void nativeWriteData(JNIEnv* env,
+ jobject clazz,
+ jlong self,
+ jstring name,
+ jlong offsetBytes,
+ jlong lengthBytes,
+ jobject incomingFd) {
+ auto connector = (DataLoaderFilesystemConnectorPtr)self;
+ return DataLoader_FilesystemConnector_writeData(connector, name, offsetBytes, lengthBytes, incomingFd);
+}
+
+static const JNINativeMethod dlc_method_table[] = {
+ {"nativeCreateDataLoader",
+ "(ILandroid/content/pm/FileSystemControlParcel;"
+ "Landroid/content/pm/DataLoaderParamsParcel;"
+ "Landroid/content/pm/IDataLoaderStatusListener;)Z",
+ (void*)nativeCreateDataLoader},
+ {"nativeStartDataLoader", "(I)Z", (void*)nativeStartDataLoader},
+ {"nativeStopDataLoader", "(I)Z", (void*)nativeStopDataLoader},
+ {"nativeDestroyDataLoader", "(I)Z", (void*)nativeDestroyDataLoader},
+ {"nativePrepareImage", "(ILjava/util/Collection;Ljava/util/Collection;)Z", (void*)nativePrepareImage},
+ {"nativeWriteData", "(JLjava/lang/String;JJLandroid/os/ParcelFileDescriptor;)V", (void*)nativeWriteData},
+};
+
+} // namespace
+
+int register_android_service_DataLoaderService(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "android/service/dataloader/DataLoaderService",
+ dlc_method_table, NELEM(dlc_method_table));
+}
+
+} // namespace android
diff --git a/core/jni/android_view_FrameMetricsObserver.cpp b/core/jni/android_view_FrameMetricsObserver.cpp
deleted file mode 100644
index febcb55..0000000
--- a/core/jni/android_view_FrameMetricsObserver.cpp
+++ /dev/null
@@ -1,149 +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.
- */
-
-#include "android_view_FrameMetricsObserver.h"
-
-namespace android {
-
-struct {
- jfieldID frameMetrics;
- jfieldID timingDataBuffer;
- jfieldID messageQueue;
- jmethodID callback;
-} gFrameMetricsObserverClassInfo;
-
-static JNIEnv* getenv(JavaVM* vm) {
- JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
- }
- return env;
-}
-
-static jlongArray get_metrics_buffer(JNIEnv* env, jobject observer) {
- jobject frameMetrics = env->GetObjectField(
- observer, gFrameMetricsObserverClassInfo.frameMetrics);
- LOG_ALWAYS_FATAL_IF(frameMetrics == nullptr, "unable to retrieve data sink object");
- jobject buffer = env->GetObjectField(
- frameMetrics, gFrameMetricsObserverClassInfo.timingDataBuffer);
- LOG_ALWAYS_FATAL_IF(buffer == nullptr, "unable to retrieve data sink buffer");
- return reinterpret_cast<jlongArray>(buffer);
-}
-
-class NotifyHandler : public MessageHandler {
-public:
- NotifyHandler(JavaVM* vm, FrameMetricsObserverProxy* observer) : mVm(vm), mObserver(observer) {}
-
- virtual void handleMessage(const Message& message);
-
-private:
- JavaVM* const mVm;
- FrameMetricsObserverProxy* const mObserver;
-};
-
-void NotifyHandler::handleMessage(const Message& message) {
- JNIEnv* env = getenv(mVm);
-
- jobject target = env->NewLocalRef(mObserver->getObserverReference());
-
- if (target != nullptr) {
- jlongArray javaBuffer = get_metrics_buffer(env, target);
- int dropCount = 0;
- while (mObserver->getNextBuffer(env, javaBuffer, &dropCount)) {
- env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback, dropCount);
- }
- env->DeleteLocalRef(target);
- }
-
- mObserver->decStrong(nullptr);
-}
-
-FrameMetricsObserverProxy::FrameMetricsObserverProxy(JavaVM *vm, jobject observer) : mVm(vm) {
- JNIEnv* env = getenv(mVm);
-
- mObserverWeak = env->NewWeakGlobalRef(observer);
- LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
- "unable to create frame stats observer reference");
-
- jlongArray buffer = get_metrics_buffer(env, observer);
- jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(buffer));
- LOG_ALWAYS_FATAL_IF(bufferSize != kBufferSize,
- "Mismatched Java/Native FrameMetrics data format.");
-
- jobject messageQueueLocal = env->GetObjectField(
- observer, gFrameMetricsObserverClassInfo.messageQueue);
- mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
- LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
-
- mMessageHandler = new NotifyHandler(mVm, this);
- LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
- "OOM: unable to allocate NotifyHandler");
-}
-
-FrameMetricsObserverProxy::~FrameMetricsObserverProxy() {
- JNIEnv* env = getenv(mVm);
- env->DeleteWeakGlobalRef(mObserverWeak);
-}
-
-bool FrameMetricsObserverProxy::getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount) {
- FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
-
- if (elem.hasData.load()) {
- env->SetLongArrayRegion(sink, 0, kBufferSize, elem.buffer);
- *dropCount = elem.dropCount;
- mNextInQueue = (mNextInQueue + 1) % kRingSize;
- elem.hasData = false;
- return true;
- }
-
- return false;
-}
-
-void FrameMetricsObserverProxy::notify(const int64_t* stats) {
- FrameMetricsNotification& elem = mRingBuffer[mNextFree];
-
- if (!elem.hasData.load()) {
- memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
-
- elem.dropCount = mDroppedReports;
- mDroppedReports = 0;
-
- incStrong(nullptr);
- mNextFree = (mNextFree + 1) % kRingSize;
- elem.hasData = true;
-
- mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
- } else {
- mDroppedReports++;
- }
-}
-
-int register_android_view_FrameMetricsObserver(JNIEnv* env) {
- jclass observerClass = FindClassOrDie(env, "android/view/FrameMetricsObserver");
- gFrameMetricsObserverClassInfo.frameMetrics = GetFieldIDOrDie(
- env, observerClass, "mFrameMetrics", "Landroid/view/FrameMetrics;");
- gFrameMetricsObserverClassInfo.messageQueue = GetFieldIDOrDie(
- env, observerClass, "mMessageQueue", "Landroid/os/MessageQueue;");
- gFrameMetricsObserverClassInfo.callback = GetMethodIDOrDie(
- env, observerClass, "notifyDataAvailable", "(I)V");
-
- jclass metricsClass = FindClassOrDie(env, "android/view/FrameMetrics");
- gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
- env, metricsClass, "mTimingData", "[J");
- return JNI_OK;
-}
-
-} // namespace android
\ No newline at end of file
diff --git a/core/jni/android_view_FrameMetricsObserver.h b/core/jni/android_view_FrameMetricsObserver.h
deleted file mode 100644
index 647f51c..0000000
--- a/core/jni/android_view_FrameMetricsObserver.h
+++ /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.
- */
-
-#include "jni.h"
-#include "core_jni_helpers.h"
-
-#include "android_os_MessageQueue.h"
-
-#include <FrameInfo.h>
-#include <FrameMetricsObserver.h>
-
-namespace android {
-
-/*
- * Implements JNI layer for hwui frame metrics reporting.
- */
-class FrameMetricsObserverProxy : public uirenderer::FrameMetricsObserver {
-public:
- FrameMetricsObserverProxy(JavaVM *vm, jobject observer);
-
- ~FrameMetricsObserverProxy();
-
- jweak getObserverReference() {
- return mObserverWeak;
- }
-
- bool getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount);
-
- virtual void notify(const int64_t* stats);
-
-private:
- static const int kBufferSize = static_cast<int>(uirenderer::FrameInfoIndex::NumIndexes);
- static constexpr int kRingSize = 3;
-
- class FrameMetricsNotification {
- public:
- FrameMetricsNotification() : hasData(false) {}
-
- std::atomic_bool hasData;
- int64_t buffer[kBufferSize];
- int dropCount = 0;
- };
-
- JavaVM* const mVm;
- jweak mObserverWeak;
-
- sp<MessageQueue> mMessageQueue;
- sp<MessageHandler> mMessageHandler;
- Message mMessage;
-
- int mNextFree = 0;
- int mNextInQueue = 0;
- FrameMetricsNotification mRingBuffer[kRingSize];
-
- int mDroppedReports = 0;
-};
-
-} // namespace android
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index c6e678ab..33da34d 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -141,7 +141,7 @@
static struct {
jclass clazz;
jmethodID ctor;
- jfieldID defaultModeId;
+ jfieldID defaultConfig;
jfieldID minRefreshRate;
jfieldID maxRefreshRate;
} gDesiredDisplayConfigSpecsClassInfo;
@@ -776,65 +776,40 @@
return configArray;
}
-static jboolean nativeSetAllowedDisplayConfigs(JNIEnv* env, jclass clazz,
- jobject tokenObj, jintArray configArray) {
- sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
- if (token == nullptr) return JNI_FALSE;
-
- std::vector<int32_t> allowedConfigs;
- jsize configArraySize = env->GetArrayLength(configArray);
- allowedConfigs.reserve(configArraySize);
-
- jint* configArrayElements = env->GetIntArrayElements(configArray, 0);
- for (int i = 0; i < configArraySize; i++) {
- allowedConfigs.push_back(configArrayElements[i]);
- }
- env->ReleaseIntArrayElements(configArray, configArrayElements, 0);
-
- size_t result = SurfaceComposerClient::setAllowedDisplayConfigs(token, allowedConfigs);
- return result == NO_ERROR ? JNI_TRUE : JNI_FALSE;
-}
-
-static jintArray nativeGetAllowedDisplayConfigs(JNIEnv* env, jclass clazz, jobject tokenObj) {
- sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
- if (token == nullptr) return JNI_FALSE;
-
- std::vector<int32_t> allowedConfigs;
- size_t result = SurfaceComposerClient::getAllowedDisplayConfigs(token, &allowedConfigs);
- if (result != NO_ERROR) {
- return nullptr;
- }
-
- jintArray allowedConfigsArray = env->NewIntArray(allowedConfigs.size());
- if (allowedConfigsArray == nullptr) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return nullptr;
- }
- jint* allowedConfigsArrayValues = env->GetIntArrayElements(allowedConfigsArray, 0);
- for (size_t i = 0; i < allowedConfigs.size(); i++) {
- allowedConfigsArrayValues[i] = static_cast<jint>(allowedConfigs[i]);
- }
- env->ReleaseIntArrayElements(allowedConfigsArray, allowedConfigsArrayValues, 0);
- return allowedConfigsArray;
-}
-
static jboolean nativeSetDesiredDisplayConfigSpecs(JNIEnv* env, jclass clazz, jobject tokenObj,
jobject desiredDisplayConfigSpecs) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
if (token == nullptr) return JNI_FALSE;
- jint defaultModeId = env->GetIntField(desiredDisplayConfigSpecs,
- gDesiredDisplayConfigSpecsClassInfo.defaultModeId);
+ jint defaultConfig = env->GetIntField(desiredDisplayConfigSpecs,
+ gDesiredDisplayConfigSpecsClassInfo.defaultConfig);
jfloat minRefreshRate = env->GetFloatField(desiredDisplayConfigSpecs,
gDesiredDisplayConfigSpecsClassInfo.minRefreshRate);
jfloat maxRefreshRate = env->GetFloatField(desiredDisplayConfigSpecs,
gDesiredDisplayConfigSpecsClassInfo.maxRefreshRate);
size_t result = SurfaceComposerClient::setDesiredDisplayConfigSpecs(
- token, defaultModeId, minRefreshRate, maxRefreshRate);
+ token, defaultConfig, minRefreshRate, maxRefreshRate);
return result == NO_ERROR ? JNI_TRUE : JNI_FALSE;
}
+static jobject nativeGetDesiredDisplayConfigSpecs(JNIEnv* env, jclass clazz, jobject tokenObj) {
+ sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
+ if (token == nullptr) return nullptr;
+
+ int32_t defaultConfig;
+ float minRefreshRate;
+ float maxRefreshRate;
+ if (SurfaceComposerClient::getDesiredDisplayConfigSpecs(token, &defaultConfig, &minRefreshRate,
+ &maxRefreshRate) != NO_ERROR) {
+ return nullptr;
+ }
+
+ return env->NewObject(gDesiredDisplayConfigSpecsClassInfo.clazz,
+ gDesiredDisplayConfigSpecsClassInfo.ctor, defaultConfig, minRefreshRate,
+ maxRefreshRate);
+}
+
static jint nativeGetActiveConfig(JNIEnv* env, jclass clazz, jobject tokenObj) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
if (token == NULL) return -1;
@@ -1387,13 +1362,12 @@
(void*)nativeGetActiveConfig },
{"nativeSetActiveConfig", "(Landroid/os/IBinder;I)Z",
(void*)nativeSetActiveConfig },
- {"nativeSetAllowedDisplayConfigs", "(Landroid/os/IBinder;[I)Z",
- (void*)nativeSetAllowedDisplayConfigs },
- {"nativeGetAllowedDisplayConfigs", "(Landroid/os/IBinder;)[I",
- (void*)nativeGetAllowedDisplayConfigs },
{"nativeSetDesiredDisplayConfigSpecs",
"(Landroid/os/IBinder;Landroid/view/SurfaceControl$DesiredDisplayConfigSpecs;)Z",
(void*)nativeSetDesiredDisplayConfigSpecs },
+ {"nativeGetDesiredDisplayConfigSpecs",
+ "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DesiredDisplayConfigSpecs;",
+ (void*)nativeGetDesiredDisplayConfigSpecs },
{"nativeGetDisplayColorModes", "(Landroid/os/IBinder;)[I",
(void*)nativeGetDisplayColorModes},
{"nativeGetDisplayNativePrimaries", "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DisplayPrimaries;",
@@ -1569,12 +1543,12 @@
MakeGlobalRefOrDie(env, desiredDisplayConfigSpecsClazz);
gDesiredDisplayConfigSpecsClassInfo.ctor =
GetMethodIDOrDie(env, gDesiredDisplayConfigSpecsClassInfo.clazz, "<init>", "(IFF)V");
- gDesiredDisplayConfigSpecsClassInfo.defaultModeId =
- GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "mDefaultModeId", "I");
+ gDesiredDisplayConfigSpecsClassInfo.defaultConfig =
+ GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "defaultConfig", "I");
gDesiredDisplayConfigSpecsClassInfo.minRefreshRate =
- GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "mMinRefreshRate", "F");
+ GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "minRefreshRate", "F");
gDesiredDisplayConfigSpecsClassInfo.maxRefreshRate =
- GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "mMaxRefreshRate", "F");
+ GetFieldIDOrDie(env, desiredDisplayConfigSpecsClazz, "maxRefreshRate", "F");
return err;
}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 170e467..69ca17c 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -30,7 +30,7 @@
#include <gui/BufferQueue.h>
#include <gui/Surface.h>
-#include "android_view_FrameMetricsObserver.h"
+#include "android_graphics_HardwareRendererObserver.h"
#include <private/EGL/cache.h>
@@ -580,28 +580,21 @@
}
// ----------------------------------------------------------------------------
-// FrameMetricsObserver
+// HardwareRendererObserver
// ----------------------------------------------------------------------------
-static jlong android_view_ThreadedRenderer_addFrameMetricsObserver(JNIEnv* env,
- jclass clazz, jlong proxyPtr, jobject fso) {
- JavaVM* vm = nullptr;
- if (env->GetJavaVM(&vm) != JNI_OK) {
- LOG_ALWAYS_FATAL("Unable to get Java VM");
- return 0;
- }
-
+static void android_view_ThreadedRenderer_addObserver(JNIEnv* env, jclass clazz,
+ jlong proxyPtr, jlong observerPtr) {
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
renderthread::RenderProxy* renderProxy =
reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
- FrameMetricsObserver* observer = new FrameMetricsObserverProxy(vm, fso);
renderProxy->addFrameMetricsObserver(observer);
- return reinterpret_cast<jlong>(observer);
}
-static void android_view_ThreadedRenderer_removeFrameMetricsObserver(JNIEnv* env, jclass clazz,
+static void android_view_ThreadedRenderer_removeObserver(JNIEnv* env, jclass clazz,
jlong proxyPtr, jlong observerPtr) {
- FrameMetricsObserver* observer = reinterpret_cast<FrameMetricsObserver*>(observerPtr);
+ HardwareRendererObserver* observer = reinterpret_cast<HardwareRendererObserver*>(observerPtr);
renderthread::RenderProxy* renderProxy =
reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
@@ -675,12 +668,8 @@
(void*)android_view_ThreadedRenderer_setFrameCallback},
{ "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
(void*)android_view_ThreadedRenderer_setFrameCompleteCallback },
- { "nAddFrameMetricsObserver",
- "(JLandroid/view/FrameMetricsObserver;)J",
- (void*)android_view_ThreadedRenderer_addFrameMetricsObserver },
- { "nRemoveFrameMetricsObserver",
- "(JJ)V",
- (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver },
+ { "nAddObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_addObserver },
+ { "nRemoveObserver", "(JJ)V", (void*)android_view_ThreadedRenderer_removeObserver },
{ "nCopySurfaceInto", "(Landroid/view/Surface;IIIIJ)I",
(void*)android_view_ThreadedRenderer_copySurfaceInto },
{ "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index f28c422..df5b02c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -31,6 +31,8 @@
// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
#include <sys/mount.h>
#include <linux/fs.h>
+#include <sys/types.h>
+#include <dirent.h>
#include <array>
#include <atomic>
@@ -40,6 +42,7 @@
#include <sstream>
#include <string>
#include <string_view>
+#include <unordered_set>
#include <android/fdsan.h>
#include <arpa/inet.h>
@@ -51,6 +54,7 @@
#include <mntent.h>
#include <paths.h>
#include <signal.h>
+#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/cdefs.h>
@@ -74,6 +78,7 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <bionic/malloc.h>
+#include <bionic/page.h>
#include <cutils/fs.h>
#include <cutils/multiuser.h>
#include <cutils/sockets.h>
@@ -158,6 +163,17 @@
*/
static int gUsapPoolEventFD = -1;
+static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751;
+
+/**
+ * Property to control if app data isolation is enabled.
+ */
+static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.zygote.app_data_isolation";
+
+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;
+
/**
* The maximum value that the gUSAPPoolSizeMax variable may take. This value
* is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT
@@ -662,7 +678,7 @@
return 0;
}
-static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
+static void PrepareDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
fail_fn_t fail_fn) {
if (fs_prepare_dir(dir.c_str(), mode, uid, gid) != 0) {
fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s: %s",
@@ -670,6 +686,16 @@
}
}
+static void PrepareDirIfNotPresent(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
+ fail_fn_t fail_fn) {
+ struct stat sb;
+ if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &sb)) != -1) {
+ // Directory exists already
+ return;
+ }
+ PrepareDir(dir, mode, uid, gid, fail_fn);
+}
+
static void BindMount(const std::string& source_dir, const std::string& target_dir,
fail_fn_t fail_fn) {
if (TEMP_FAILURE_RETRY(mount(source_dir.c_str(), target_dir.c_str(), nullptr,
@@ -679,6 +705,15 @@
}
}
+static void MountAppDataTmpFs(const std::string& target_dir,
+ fail_fn_t fail_fn) {
+ if (TEMP_FAILURE_RETRY(mount("tmpfs", target_dir.c_str(), "tmpfs",
+ MS_NOSUID | MS_NODEV | MS_NOEXEC, "uid=0,gid=0,mode=0751")) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount tmpfs to %s: %s",
+ target_dir.c_str(), strerror(errno)));
+ }
+}
+
// Create a private mount namespace and bind mount appropriate emulated
// storage for the given user.
static void MountEmulatedStorage(uid_t uid, jint mount_mode,
@@ -711,11 +746,19 @@
const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
bool isFuse = GetBoolProperty(kPropFuse, false);
- CreateDir(user_source, 0751, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDir(user_source, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
if (isFuse) {
- BindMount(mount_mode == MOUNT_EXTERNAL_PASS_THROUGH ? pass_through_source : user_source,
- "/storage", fail_fn);
+ 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
+ BindMount(pass_through_source, "/storage", fail_fn);
+ } else {
+ BindMount(user_source, "/storage", fail_fn);
+ }
} else {
const std::string& storage_source = ExternalStorageViews[mount_mode];
BindMount(storage_source, "/storage", fail_fn);
@@ -1007,6 +1050,231 @@
return pid;
}
+// Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it
+// from the actual app data directory in data mirror.
+static void createAndMountAppData(std::string_view package_name,
+ std::string_view mirror_pkg_dir_name, std::string_view mirror_data_path,
+ std::string_view actual_data_path, fail_fn_t fail_fn) {
+
+ char mirrorAppDataPath[PATH_MAX];
+ char actualAppDataPath[PATH_MAX];
+ snprintf(mirrorAppDataPath, PATH_MAX, "%s/%s", mirror_data_path.data(),
+ mirror_pkg_dir_name.data());
+ snprintf(actualAppDataPath, PATH_MAX, "%s/%s", actual_data_path.data(), package_name.data());
+
+ PrepareDir(actualAppDataPath, 0700, AID_ROOT, AID_ROOT, fail_fn);
+
+ // Bind mount from original app data directory in mirror.
+ BindMount(mirrorAppDataPath, actualAppDataPath, fail_fn);
+}
+
+// Get the directory name stored in /data/data. If device is unlocked it should be the same as
+// package name, otherwise it will be an encrypted name but with same inode number.
+static std::string getAppDataDirName(std::string_view parent_path, std::string_view package_name,
+ long long ce_data_inode, fail_fn_t fail_fn) {
+ // Check if directory exists
+ char tmpPath[PATH_MAX];
+ snprintf(tmpPath, PATH_MAX, "%s/%s", parent_path.data(), package_name.data());
+ struct stat s;
+ int err = stat(tmpPath, &s);
+ if (err == 0) {
+ // Directory exists, so return the directory name
+ return package_name.data();
+ } else {
+ if (errno != ENOENT) {
+ fail_fn(CREATE_ERROR("Unexpected error in getAppDataDirName: %s", strerror(errno)));
+ return nullptr;
+ }
+ // Directory doesn't exist, try to search the name from inode
+ DIR* dir = opendir(parent_path.data());
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (ent->d_ino == ce_data_inode) {
+ closedir(dir);
+ return ent->d_name;
+ }
+ }
+ closedir(dir);
+
+ // Fallback due to b/145989852, ce_data_inode stored in package manager may be corrupted
+ // if ino_t is 32 bits.
+ ino_t fixed_ce_data_inode = 0;
+ if ((ce_data_inode & UPPER_HALF_WORD_MASK) == UPPER_HALF_WORD_MASK) {
+ fixed_ce_data_inode = ce_data_inode & LOWER_HALF_WORD_MASK;
+ } else if ((ce_data_inode & LOWER_HALF_WORD_MASK) == LOWER_HALF_WORD_MASK) {
+ fixed_ce_data_inode = ((ce_data_inode >> 32) & LOWER_HALF_WORD_MASK);
+ }
+ if (fixed_ce_data_inode != 0) {
+ dir = opendir(parent_path.data());
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+ }
+ while ((ent = readdir(dir))) {
+ if (ent->d_ino == fixed_ce_data_inode) {
+ long long d_ino = ent->d_ino;
+ ALOGW("Fallback success inode %lld -> %lld", ce_data_inode, d_ino);
+ closedir(dir);
+ return ent->d_name;
+ }
+ }
+ closedir(dir);
+ }
+ // Fallback done
+
+ fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(),
+ ce_data_inode, parent_path.data()));
+ return nullptr;
+ }
+}
+
+// Isolate app's data directory, by mounting a tmpfs on CE DE storage,
+// and create and bind mount app data in related_packages.
+static void isolateAppDataPerPackage(int userId, std::string_view package_name,
+ std::string_view volume_uuid, long long ce_data_inode, std::string_view actualCePath,
+ std::string_view actualDePath, fail_fn_t fail_fn) {
+
+ char mirrorCePath[PATH_MAX];
+ char mirrorDePath[PATH_MAX];
+ char mirrorCeParent[PATH_MAX];
+ snprintf(mirrorCeParent, PATH_MAX, "/data_mirror/data_ce/%s", volume_uuid.data());
+ snprintf(mirrorCePath, PATH_MAX, "%s/%d", mirrorCeParent, userId);
+ snprintf(mirrorDePath, PATH_MAX, "/data_mirror/data_de/%s/%d", volume_uuid.data(), userId);
+
+ createAndMountAppData(package_name, package_name, mirrorDePath, actualDePath, fail_fn);
+
+ std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn);
+ createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
+}
+
+/**
+ * Make other apps data directory not visible in CE, DE storage.
+ *
+ * Apps without app data isolation can detect if another app is installed on system,
+ * by "touching" other apps data directory like /data/data/com.whatsapp, if it returns
+ * "Permission denied" it means apps installed, otherwise it returns "File not found".
+ * Traditional file permissions or SELinux can only block accessing those directories but
+ * can't fix fingerprinting like this.
+ * We fix it by "overlaying" data directory, and only relevant app data packages exists
+ * in data directories.
+ *
+ * Steps:
+ * 1). Collect a list of all related apps (apps with same uid and whitelisted apps) data info
+ * (package name, data stored volume uuid, and inode number of its CE data directory)
+ * 2). Mount tmpfs on /data/data, /data/user(_de) and /mnt/expand, so apps no longer
+ * able to access apps data directly.
+ * 3). For each related app, create its app data directory and bind mount the actual content
+ * from apps data mirror directory. This works on both CE and DE storage, as DE storage
+ * is always available even storage is FBE locked, while we use inode number to find
+ * the encrypted DE directory in mirror so we can still bind mount it successfully.
+ *
+ * Example:
+ * 0). Assuming com.android.foo CE data is stored in /data/data and no shared uid
+ * 1). Mount a tmpfs on /data/data, /data/user, /data/user_de, /mnt/expand
+ * List = ["com.android.foo", "null" (volume uuid "null"=default),
+ * 123456 (inode number)]
+ * 2). On DE storage, we create a directory /data/user_de/0/com.com.android.foo, and bind
+ * mount (in the app's mount namespace) it from /data_mirror/data_de/0/com.android.foo.
+ * 3). We do similar for CE storage. But in direct boot mode, as /data_mirror/data_ce/0/ is
+ * encrypted, we can't find a directory with name com.android.foo on it, so we will
+ * use the inode number to find the right directory instead, which that directory content will
+ * be decrypted after storage is decrypted.
+ *
+ */
+static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
+ uid_t uid, const char* process_name, jstring managed_nice_name,
+ fail_fn_t fail_fn) {
+
+ const userid_t userId = multiuser_get_user_id(uid);
+
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+
+ 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 tmpfs on all possible data directories, so app no longer see the original apps data.
+ char internalCePath[PATH_MAX];
+ char internalLegacyCePath[PATH_MAX];
+ char internalDePath[PATH_MAX];
+ char externalPrivateMountPath[PATH_MAX];
+
+ snprintf(internalCePath, PATH_MAX, "/data/user");
+ snprintf(internalLegacyCePath, PATH_MAX, "/data/data");
+ snprintf(internalDePath, PATH_MAX, "/data/user_de");
+ snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
+
+ MountAppDataTmpFs(internalLegacyCePath, fail_fn);
+ MountAppDataTmpFs(internalCePath, fail_fn);
+ MountAppDataTmpFs(internalDePath, fail_fn);
+ MountAppDataTmpFs(externalPrivateMountPath, 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();
+
+ jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1));
+ std::string volUuid = extract_fn(vol_str).value();
+
+ jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2));
+ std::string inode = extract_fn(inode_str).value();
+ std::string::size_type sz;
+ long long ceDataInode = std::stoll(inode, &sz);
+
+ std::string actualCePath, actualDePath;
+ if (volUuid.compare("null") != 0) {
+ // Volume that is stored in /mnt/expand
+ char volPath[PATH_MAX];
+ char volCePath[PATH_MAX];
+ char volDePath[PATH_MAX];
+ char volCeUserPath[PATH_MAX];
+ char volDeUserPath[PATH_MAX];
+
+ snprintf(volPath, PATH_MAX, "/mnt/expand/%s", volUuid.c_str());
+ snprintf(volCePath, PATH_MAX, "%s/user", volPath);
+ snprintf(volDePath, PATH_MAX, "%s/user_de", volPath);
+ snprintf(volCeUserPath, PATH_MAX, "%s/%d", volCePath, userId);
+ snprintf(volDeUserPath, PATH_MAX, "%s/%d", volDePath, userId);
+
+ PrepareDirIfNotPresent(volPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volCePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volDePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volCeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+ PrepareDirIfNotPresent(volDeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+
+ actualCePath = volCeUserPath;
+ actualDePath = volDeUserPath;
+ } else {
+ // Internal volume that stored in /data
+ char internalCeUserPath[PATH_MAX];
+ char internalDeUserPath[PATH_MAX];
+ snprintf(internalCeUserPath, PATH_MAX, "/data/user/%d", userId);
+ snprintf(internalDeUserPath, PATH_MAX, "/data/user_de/%d", userId);
+ // If it's user 0, create a symlink /data/user/0 -> /data/data,
+ // otherwise create /data/user/$USER
+ if (userId == 0) {
+ symlink(internalLegacyCePath, internalCeUserPath);
+ actualCePath = internalLegacyCePath;
+ } else {
+ PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION,
+ AID_ROOT, AID_ROOT, fail_fn);
+ actualCePath = internalCeUserPath;
+ }
+ PrepareDirIfNotPresent(internalDeUserPath, DEFAULT_DATA_DIR_PERMISSION,
+ AID_ROOT, AID_ROOT, fail_fn);
+ actualDePath = internalDeUserPath;
+ }
+ isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode,
+ actualCePath, actualDePath, fail_fn);
+ }
+}
+
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
@@ -1014,7 +1282,8 @@
jint mount_external, jstring managed_se_info,
jstring managed_nice_name, bool is_system_server,
bool is_child_zygote, jstring managed_instruction_set,
- jstring managed_app_data_dir, bool is_top_app) {
+ jstring managed_app_data_dir, bool is_top_app,
+ jobjectArray pkg_data_info_list) {
const char* process_name = is_system_server ? "system_server" : "zygote";
auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1050,6 +1319,16 @@
MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn);
+ // System services, isolated process, webview/app zygote, old target sdk app, should
+ // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
+ // 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);
+ }
+
// If this zygote isn't root, it won't be able to create a process group,
// since the directory is owned by root.
if (!is_system_server && getuid() == 0) {
@@ -1389,9 +1668,14 @@
void* data [[maybe_unused]]) {
// Search for any execute-only segments and mark them read+execute.
for (int i = 0; i < info->dlpi_phnum; i++) {
- if ((info->dlpi_phdr[i].p_type == PT_LOAD) && (info->dlpi_phdr[i].p_flags == PF_X)) {
- mprotect(reinterpret_cast<void*>(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr),
- info->dlpi_phdr[i].p_memsz, PROT_READ | PROT_EXEC);
+ const auto& phdr = info->dlpi_phdr[i];
+ if ((phdr.p_type == PT_LOAD) && (phdr.p_flags == PF_X)) {
+ auto addr = reinterpret_cast<void*>(info->dlpi_addr + PAGE_START(phdr.p_vaddr));
+ size_t len = PAGE_OFFSET(phdr.p_vaddr) + phdr.p_memsz;
+ if (mprotect(addr, len, PROT_READ | PROT_EXEC) == -1) {
+ ALOGE("mprotect(%p, %zu, PROT_READ | PROT_EXEC) failed: %m", addr, len);
+ return -1;
+ }
}
}
// Return non-zero to exit dl_iterate_phdr.
@@ -1411,7 +1695,8 @@
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
- jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
+ jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
+ jobjectArray pkg_data_info_list) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -1443,7 +1728,7 @@
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list);
}
return pid;
}
@@ -1467,10 +1752,13 @@
fds_to_ignore,
true);
if (pid == 0) {
+ // System server prcoess does not need data isolation so no need to
+ // know pkg_data_info_list.
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities,
MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
- false, nullptr, nullptr, /* is_top_app= */ false);
+ false, nullptr, nullptr, /* is_top_app= */ false,
+ /* pkg_data_info_list */ nullptr);
} else if (pid > 0) {
// The zygote process checks whether the child process has died or not.
ALOGI("System server process %d has been created", pid);
@@ -1593,14 +1881,15 @@
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
- jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
+ jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
+ jobjectArray pkg_data_info_list) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list);
}
/**
@@ -1761,7 +2050,7 @@
static const JNINativeMethod gMethods[] = {
{ "nativeForkAndSpecialize",
- "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I",
(void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
{ "nativeForkSystemServer", "(II[II[[IJJ)I",
(void *) com_android_internal_os_Zygote_nativeForkSystemServer },
@@ -1774,7 +2063,7 @@
{ "nativeForkUsap", "(II[IZ)I",
(void *) com_android_internal_os_Zygote_nativeForkUsap },
{ "nativeSpecializeAppProcess",
- "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V",
(void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess },
{ "nativeInitNativeState", "(Z)V",
(void *) com_android_internal_os_Zygote_nativeInitNativeState },
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index fd6984b..738965e 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -33,10 +33,17 @@
// 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.telephony/javalib/telephony-common.jar",
+ "/apex/com.android.telephony/javalib/ims-common.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",
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index ab97fdd..b2a19cf 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -251,6 +251,9 @@
// ACTION: Create a Settings shortcut item.
ACTION_SETTINGS_CREATE_SHORTCUT = 829;
+ // ACTION: A tile in Settings information architecture is clicked
+ ACTION_SETTINGS_TILE_CLICK = 830;
+
// ACTION: Settings advanced button is expanded
ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND = 834;
@@ -2467,4 +2470,51 @@
// CATEGORY: SETTINGS
// OS: R
ACCOUNT_WORK = 1807;
+
+ // OPEN: Settings > Developer Options > Bug report handler
+ // CATEGORY: SETTINGS
+ // OS: R
+ SETTINGS_BUGREPORT_HANDLER = 1808;
+
+ // Panel for adding Wi-Fi networks
+ // CATEGORY: SETTINGS
+ // OS: R
+ PANEL_ADD_WIFI_NETWORKS = 1809;
+
+ // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_TOGGLE_SCREEN_ACCESSIBILITY_BUTTON = 1810;
+
+ // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog in
+ // gesture mode
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_TOGGLE_SCREEN_GESTURE_NAVIGATION = 1811;
+
+ // OPEN: Settings > Accessibility > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT = 1812;
+
+ // OPEN: Settings > Accessibility > Magnification > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_MAGNIFICATION_EDIT_SHORTCUT = 1813;
+
+ // OPEN: Settings > Accessibility > Color correction > Edit shortcut dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_DALTONIZER_EDIT_SHORTCUT = 1814;
+
+ // OPEN: Settings > Accessibility > Magnification > Settings
+ // CATEGORY: SETTINGS
+ // OS: R
+ ACCESSIBILITY_MAGNIFICATION_SETTINGS = 1815;
+
+ // OPEN: Settings > Accessibility > Magnification > Settings > Magnification area dialog
+ // CATEGORY: SETTINGS
+ // OS: R
+ DIALOG_MAGNIFICATION_CAPABILITY = 1816;
+
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 31c19ca..a98d7db 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -566,6 +566,7 @@
optional LowPowerMode low_power_mode = 70;
optional SettingProto lte_service_forced = 71 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ repeated SettingProto max_error_bytes = 151;
optional SettingProto mdc_initial_max_retry = 72 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Mhl {
@@ -1058,5 +1059,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 151;
+ // Next tag = 152;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4ea574d..6a1ec6c 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -219,6 +219,12 @@
}
optional Gesture gesture = 74;
+ message GestureNavigation {
+ optional SettingProto back_gesture_inset_scale_left = 1 [(android.privacy).dest = DEST_AUTOMATIC];
+ optional SettingProto back_gesture_inset_scale_right = 2 [(android.privacy).dest = DEST_AUTOMATIC];
+ }
+ optional GestureNavigation gesture_navigation = 77;
+
optional SettingProto immersive_mode_confirmations = 24 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Incall {
@@ -316,6 +322,7 @@
optional SettingProto multi_press_timeout = 38 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto navigation_mode = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
message NfcPayment {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -564,5 +571,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 77;
+ // Next tag = 78;
}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 24456d8..0c74842 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -163,6 +163,7 @@
repeated IdentifierProto opening_apps = 17;
repeated IdentifierProto closing_apps = 18;
repeated IdentifierProto changing_apps = 19;
+ repeated WindowTokenProto overlay_windows = 20;
}
/* represents DisplayFrames */
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index f8c304c..ee5144c 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -152,4 +152,5 @@
CROSS_PROFILE_APPS_GET_TARGET_USER_PROFILES = 125;
CROSS_PROFILE_APPS_START_ACTIVITY_AS_USER = 126;
SET_AUTO_TIME = 127;
+ SET_AUTO_TIME_ZONE = 128;
}
diff --git a/core/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto
index f648912..5963f6a 100644
--- a/core/proto/android/stats/docsui/docsui_enums.proto
+++ b/core/proto/android/stats/docsui/docsui_enums.proto
@@ -56,6 +56,7 @@
ROOT_VIDEOS = 9;
ROOT_MTP = 10;
ROOT_THIRD_PARTY_APP = 11;
+ ROOT_DOCUMENTS = 12;
}
enum ContextScope {
diff --git a/core/proto/android/util/quotatracker.proto b/core/proto/android/util/quotatracker.proto
new file mode 100644
index 0000000..0dea853
--- /dev/null
+++ b/core/proto/android/util/quotatracker.proto
@@ -0,0 +1,219 @@
+/*
+ * 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";
+
+package android.util.quota;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+// A com.android.util.quota.QuotaTracker object.
+message QuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional bool is_enabled = 1;
+
+ // If quota is free for everything in the tracker.
+ optional bool is_global_quota_free = 2;
+
+ // Current elapsed realtime.
+ optional int64 elapsed_realtime = 3;
+
+ message AlarmListener {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Whether the listener is waiting for an alarm or not.
+ optional bool is_waiting = 1;
+ // The time at which the alarm should go off, in the elapsed realtime timebase. Only
+ // valid if is_waiting is true.
+ optional int64 trigger_time_elapsed = 2;
+ }
+
+ // Next tag: 4
+}
+
+// A com.android.util.quota.Category object.
+message CategoryProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Name of the category set by the system service.
+ optional string name = 1;
+}
+
+// A com.android.util.quota.Uptc object.
+message UptcProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // UserHandle value. Should be 0, 10, 11, 12, etc. where 0 is the owner.
+ optional int32 user_id = 1;
+ // Package name
+ optional string name = 2;
+ // Tag set by the system service to differentiate calls.
+ optional string tag = 3;
+}
+
+// A com.android.util.quota.CountQuotaTracker object.
+message CountQuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional QuotaTrackerProto base_quota_data = 1;
+
+ message CountLimit {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional CategoryProto category = 1;
+ optional int32 limit = 2;
+ optional int64 window_size_ms = 3;
+ }
+ repeated CountLimit count_limit = 2;
+
+ message Event {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time the event occurred, in the elapsed realtime timebase.
+ optional int64 timestamp_elapsed = 1;
+ }
+
+ message ExecutionStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time after which this record should be considered invalid (out of date), in the
+ // elapsed realtime timebase.
+ optional int64 expiration_time_elapsed = 1;
+
+ optional int64 window_size_ms = 2;
+ optional int32 count_limit = 3;
+
+ // The total number of events that occurred in the window.
+ optional int32 count_in_window = 4;
+
+ // The time after which the app will be under the bucket quota. This is only valid if
+ // count_in_window >= count_limit.
+ optional int64 in_quota_time_elapsed = 5;
+ }
+
+ message UptcStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+
+ // True if the UPTC has been given free quota.
+ optional bool is_quota_free = 2;
+
+ repeated Event events = 3;
+
+ repeated ExecutionStats execution_stats = 4;
+
+ optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 5;
+ }
+ repeated UptcStats uptc_stats = 3;
+
+ // Next tag: 4
+}
+
+// A com.android.util.quota.DurationQuotaTracker object.
+message DurationQuotaTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional QuotaTrackerProto base_quota_data = 1;
+
+ message DurationLimit {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional CategoryProto category = 1;
+ optional int64 limit_ms = 2;
+ optional int64 window_size_ms = 3;
+ }
+ repeated DurationLimit duration_limit = 2;
+
+ message ExecutionStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The time after which this record should be considered invalid (out of date), in the
+ // elapsed realtime timebase.
+ optional int64 expiration_time_elapsed = 1;
+
+ optional int32 window_size_ms = 2;
+ optional int64 duration_limit_ms = 3;
+
+ // The overall session duration in the window.
+ optional int64 session_duration_in_window_ms = 4;
+ // The number of individual long-running events in the window.
+ optional int32 event_count_in_window = 5;
+
+ // The time after which the app will be under the bucket quota. This is only valid if
+ // session_duration_in_window_ms >= duration_limit_ms.
+ optional int64 in_quota_time_elapsed = 6;
+ }
+
+ message Timer {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // True if the Timer is actively tracking long-running events.
+ optional bool is_active = 1;
+ // The time this timer last became active. Only valid if is_active is true.
+ optional int64 start_time_elapsed = 2;
+ // How many long-running events are currently running. Valid only if is_active is true.
+ optional int32 event_count = 3;
+ }
+
+ message TimingSession {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 start_time_elapsed = 1;
+ optional int64 end_time_elapsed = 2;
+ // How many events started during this session. This only count long-running events, not
+ // instantaneous events.
+ optional int32 event_count = 3;
+ }
+
+ message UptcStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+
+ // True if the UPTC has been given free quota.
+ optional bool is_quota_free = 2;
+
+ optional Timer timer = 3;
+
+ repeated TimingSession saved_sessions = 4;
+
+ repeated ExecutionStats execution_stats = 5;
+
+ optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 6;
+ }
+ repeated UptcStats uptc_stats = 3;
+
+ message ReachedQuotaAlarmListener {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 trigger_time_elapsed = 1;
+
+ message UptcTimes {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional UptcProto uptc = 1;
+ optional int64 out_of_quota_time_elapsed = 2;
+ }
+ repeated UptcTimes uptc_times = 2;
+ }
+ optional ReachedQuotaAlarmListener reached_quota_alarm_listener = 4;
+
+ // Next tag: 5
+}
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index 93a9fe2..272a245 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -56,13 +56,6 @@
optional uint32 input_feature_flags = 19;
optional int64 user_activity_timeout = 20;
- enum NeedsMenuState {
- NEEDS_MENU_UNSET = 0;
- NEEDS_MENU_SET_TRUE = 1;
- NEEDS_MENU_SET_FALSE = 2;
- }
- optional NeedsMenuState needs_menu_key = 22;
-
optional DisplayProto.ColorMode color_mode = 23;
optional uint32 flags = 24;
optional uint32 private_flags = 26;
@@ -70,4 +63,7 @@
optional uint32 subtree_system_ui_visibility_flags = 28;
optional uint32 appearance = 29;
optional uint32 behavior = 30;
+ optional uint32 fit_insets_types = 31;
+ optional uint32 fit_insets_sides = 32;
+ optional bool fit_ignore_visibility = 33;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f6e91ef..44a902c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -307,6 +307,7 @@
<protected-broadcast android:name="android.net.nsd.STATE_CHANGED" />
<protected-broadcast android:name="android.nfc.action.ADAPTER_STATE_CHANGED" />
+ <protected-broadcast android:name="android.nfc.action.PREFERRED_PAYMENT_CHANGED" />
<protected-broadcast android:name="android.nfc.action.TRANSACTION_DETECTED" />
<protected-broadcast android:name="com.android.nfc.action.LLCP_UP" />
<protected-broadcast android:name="com.android.nfc.action.LLCP_DOWN" />
@@ -635,10 +636,9 @@
<protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
- <!-- NETWORK_SET_TIME / NETWORK_SET_TIMEZONE moved from com.android.phone to system server.
- They should ultimately be removed. -->
- <protected-broadcast android:name="android.intent.action.NETWORK_SET_TIME" />
- <protected-broadcast android:name="android.intent.action.NETWORK_SET_TIMEZONE" />
+ <!-- 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
@@ -1630,6 +1630,14 @@
<permission android:name="android.permission.NETWORK_SETTINGS"
android:protectionLevel="signature|telephony" />
+ <!-- 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.
+ @hide
+ -->
+ <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"
+ android:protectionLevel="signature|companion" />
+
<!-- Allows SetupWizard to call methods in Networking services
<p>Not for use by any other third-party or privileged applications.
@SystemApi
@@ -1750,6 +1758,14 @@
<p>Protection level: normal
-->
<permission android:name="android.permission.NFC_TRANSACTION_EVENT"
+ android:protectionLevel="normal" />
+
+ <!-- Allows applications to receive NFC preferred payment service information.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.NFC_PREFERRED_PAYMENT_INFO"
+ android:description="@string/permdesc_preferredPaymentInfo"
+ android:label="@string/permlab_preferredPaymentInfo"
android:protectionLevel="normal" />
<!-- @deprecated This permission used to allow too broad access to sensitive methods and all its
@@ -1851,6 +1867,13 @@
android:description="@string/permdesc_vibrate"
android:protectionLevel="normal|instant" />
+ <!-- Allows access to the vibrator always-on settings.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.VIBRATE_ALWAYS_ON"
+ android:protectionLevel="signature" />
+
<!-- Allows using PowerManager WakeLocks to keep processor from sleeping or screen
from dimming.
<p>Protection level: normal
@@ -2202,6 +2225,17 @@
<permission android:name="android.permission.MANAGE_DOCUMENTS"
android:protectionLevel="signature|documenter" />
+ <!-- Allows an application to manage access to crates, usually as part
+ of a crates picker.
+ <p>This permission should <em>only</em> be requested by the platform
+ management app. This permission cannot be granted to
+ third-party apps.
+ @hide
+ @TestApi
+ -->
+ <permission android:name="android.permission.MANAGE_CRATES"
+ android:protectionLevel="signature" />
+
<!-- @hide Allows an application to cache content.
<p>Not for use by third-party applications.
-->
@@ -2927,6 +2961,14 @@
<permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
android:protectionLevel="signature" />
+ <!-- Allows SystemUI to request third party controls.
+ <p>Should only be requested by the System and required by
+ ControlsService declarations.
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_CONTROLS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to force a BACK operation on whatever is the
top activity.
<p>Not for use by third-party applications.
@@ -3265,13 +3307,6 @@
<permission android:name="android.permission.BIND_TV_INPUT"
android:protectionLevel="signature|privileged" />
- <!-- Must be required by an {@link android.service.sms.FinancialSmsService}
- to ensure that only the system can bind to it.
- @hide This is not a third-party API (intended for OEMs and system apps).
- -->
- <permission android:name="android.permission.BIND_FINANCIAL_SMS_SERVICE"
- android:protectionLevel="signature" />
-
<!-- @SystemApi
Must be required by a {@link com.android.media.tv.remoteprovider.TvRemoteProvider}
to ensure that only the system can bind to it.
@@ -4078,7 +4113,7 @@
<permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"
android:protectionLevel="signature|privileged" />
- <!-- Must be required by intent filter verifier receiver, to ensure that only the
+ <!-- Must be required by intent filter verifier rintent-filtereceiver, to ensure that only the
system can interact with it.
@hide
-->
@@ -4418,6 +4453,12 @@
<permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
android:protectionLevel="signature|privileged" />
+ <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on
+ implementations which do not support running both concurrently.
+ @hide -->
+ <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER"
+ android:protectionLevel="signature|privileged" />
+
<!-- Must be required by system/priv apps implementing sound trigger detection services
@hide
@SystemApi -->
@@ -4691,6 +4732,12 @@
<permission android:name="android.permission.PEEK_DROPBOX_DATA"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to access TV tuner HAL
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TV_TUNER"
+ android:protectionLevel="signature|privileged" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
@@ -5096,6 +5143,12 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
-</application>
+ <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader">
+ <intent-filter>
+ <action android:name="android.intent.action.LOAD_DATA" />
+ </intent-filter>
+ </service>
+
+ </application>
</manifest>
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index 6807f9a..0c45e45 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -29,7 +29,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
- android:elevation="1dp"
+ android:elevation="0dp"
android:background="@drawable/bottomsheet_background">
<ImageView
@@ -55,16 +55,16 @@
android:layout_centerHorizontal="true"/>
</RelativeLayout>
- <com.android.internal.widget.RecyclerView
+ <FrameLayout
+ android:id="@+id/content_preview_container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layoutManager="com.android.internal.widget.GridLayoutManager"
- android:id="@+id/resolver_list"
- android:clipToPadding="false"
- android:background="?attr/colorBackgroundFloating"
- android:scrollbars="none"
- android:elevation="1dp"
- android:nestedScrollingEnabled="true"/>
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+
+ <com.android.internal.widget.ViewPager
+ android:id="@+id/profile_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml
index 9c725b9..4d7846d 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -45,7 +45,8 @@
android:ellipsize="end"
android:fontFamily="@android:string/config_headlineFontFamily"
android:textColor="?android:attr/textColorPrimary"
- android:maxLines="2"/>
+ android:maxLines="2"
+ android:focusable="true"/>
<LinearLayout
android:id="@+id/copy_button"
diff --git a/core/res/res/layout/chooser_list_per_profile.xml b/core/res/res/layout/chooser_list_per_profile.xml
new file mode 100644
index 0000000..212813f
--- /dev/null
+++ b/core/res/res/layout/chooser_list_per_profile.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+
+<com.android.internal.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layoutManager="com.android.internal.widget.GridLayoutManager"
+ android:id="@+id/resolver_list"
+ android:clipToPadding="false"
+ android:background="?attr/colorBackgroundFloating"
+ android:scrollbars="none"
+ android:elevation="1dp"
+ android:nestedScrollingEnabled="true"/>
\ No newline at end of file
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 6e45e7a..c5d8912 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -63,32 +63,28 @@
</RelativeLayout>
<View
- android:layout_alwaysShow="true"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
- android:foreground="?attr/dividerVertical" />
- <ListView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/resolver_list"
- android:clipToPadding="false"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="@dimen/resolver_elevation"
- android:nestedScrollingEnabled="true"
- android:scrollbarStyle="outsideOverlay"
- android:scrollIndicators="top|bottom"
- android:divider="?attr/dividerVertical"
- android:footerDividersEnabled="false"
- android:headerDividersEnabled="false"
- android:dividerHeight="1dp" />
- <View
+ android:id="@+id/divider"
android:layout_alwaysShow="true"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorBackgroundFloating"
android:foreground="?attr/dividerVertical" />
+ <com.android.internal.app.WrapHeightViewPager
+ android:id="@+id/profile_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp"/>
+
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml
new file mode 100644
index 0000000..68b9917
--- /dev/null
+++ b/core/res/res/layout/resolver_list_per_profile.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+<ListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/resolver_list"
+ android:clipToPadding="false"
+ android:background="?attr/colorBackgroundFloating"
+ android:elevation="@dimen/resolver_elevation"
+ android:nestedScrollingEnabled="true"
+ android:scrollbarStyle="outsideOverlay"
+ android:scrollIndicators="top|bottom"
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
\ No newline at end of file
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index dbba0b7..5b3d929 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -144,25 +144,21 @@
</LinearLayout>
<View
+ android:id="@+id/divider"
android:layout_alwaysShow="true"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorBackgroundFloating"
android:foreground="?attr/dividerVertical" />
- <ListView
+
+ <com.android.internal.app.WrapHeightViewPager
+ android:id="@+id/profile_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:id="@+id/resolver_list"
- android:clipToPadding="false"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="@dimen/resolver_elevation"
- android:nestedScrollingEnabled="true"
- android:scrollbarStyle="outsideOverlay"
- android:scrollIndicators="top|bottom"
+ android:dividerHeight="1dp"
android:divider="?attr/dividerVertical"
android:footerDividersEnabled="false"
- android:headerDividersEnabled="false"
- android:dividerHeight="1dp" />
+ android:headerDividersEnabled="false"/>
<View
android:layout_alwaysShow="true"
android:layout_width="match_parent"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 4acdeeb..c9c0615 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Die werkprofiel se administrasieprogram ontbreek of is korrup. Gevolglik is jou werkprofiel en verwante data uitgevee. Kontak jou administrateur vir bystand."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Jou werkprofiel is nie meer op hierdie toestel beskikbaar nie"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Te veel wagwoordpogings"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrateur het toestel vir persoonlike gebruik afgestaan"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Toestel word bestuur"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Jou organisasie bestuur hierdie toestel en kan netwerkverkeer monitor. Tik vir besonderhede."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Jou toestel sal uitgevee word"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Laat die program toe om vaste uitsendings wat agterbly nadat die uitsending eindig, te stuur. Oormatige gebruik kan jou Android TV-toestel stadig of onstabiel maak omdat dit veroorsaak dat jou toestel te veel geheue gebruik."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Laat die program toe om taai uitsendings te stuur, wat agterbly nadat die uitsending klaar is. Oormatige gebruik kan die foon stadig of onstabiel maak deurdat dit te veel geheue gebruik."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lees jou kontakte"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Laat die program toe om inligting oor jou kontakte wat op jou tablet gestoor is, te lees, insluitend die gereeldheid van oproepe wat jy gemaak het, e-posse wat jy gestuur het, of ander maniere waarop jy met spesifieke individue gekommunikeer het. Hierdie toestemming laat programme toe om jou kontakdata te stoor, en kwaadwillige programme kan moontlik kontakdata sonder jou kennis deel."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Laat die program toe om data te lees oor jou kontakte wat op jou Android TV-toestel geberg is, insluitend hoe gereeld jy spesifieke individue gebel, ge-e-pos of op ander maniere met hulle gekommunikeer het. Hierdie toestemming laat programme toe om jou kontakdata te stoor, én kwaadwillige programme kan kontakdata deel sonder dat jy dit weet."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Laat die program toe om inligting oor jou kontakte wat op jou foon gestoor is, te lees, insluitend die gereeldheid van oproepe wat jy gemaak het, e-posse wat jy gestuur het, of ander maniere waarop jy met spesifieke individue gekommunikeer het. Hierdie toestemming laat programme toe om jou kontakdata te stoor, en kwaadwillige programme kan moontlik kontakdata sonder jou kennis deel."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Laat die program toe om data te lees oor jou kontakte wat op jou tablet geberg is. Programme sal ook toegang hê tot die rekeninge op jou tablet wat kontakte geskep het. Dit kan rekeninge insluit wat geskep is deur programme wat jy geïnstalleer het. Hierdie toestemming laat programme toe om jou kontakdata te stoor, en kwaadwillige programme kan kontakdata deel sonder dat jy dit weet."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Laat die program toe om data te lees oor jou kontakte wat op jou Android TV-toestel geberg is. Programme sal ook toegang hê tot die rekeninge op jou Android TV-toestel wat kontakte geskep het. Dit kan rekeninge insluit wat geskep is deur programme wat jy geïnstalleer het. Hierdie toestemming laat programme toe om jou kontakdata te stoor, en kwaadwillige programme kan kontakdata deel sonder dat jy dit weet."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Laat die program toe om data te lees oor jou kontakte wat op jou foon geberg is. Programme sal ook toegang hê tot die rekeninge op jou foon wat kontakte geskep het. Dit kan rekeninge insluit wat geskep is deur programme wat jy geïnstalleer het. Hierdie toestemming laat programme toe om jou kontakdata te stoor, en kwaadwillige programme kan kontakdata deel sonder dat jy dit weet."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"verander jou kontakte"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Laat die program toe om data oor jou kontakte wat op jou tablet gestoor is te verander, insluitend die gereeldheid van oproepe wat jy gemaak het, e-posse wat jy gestuur het, of ander maniere waarop jy met spesifieke individue gekommunikeer het. Hierdie toestemming laat programme toe om kontakdata uit te vee."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Laat die program toe om die data oor jou kontakte wat op jou Android TV-toestel gestoor is, te verander – dit sluit in hoe gereeld jy spesifieke kontakte gebel, ge-e-pos of op ander maniere met hulle gekommunikeer het. Hierdie toestemming laat programme toe om kontakdata uit te vee."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Laat die program toe om data oor jou kontakte wat op jou foon gestoor is te verander, insluitend die gereeldheid waarop jy oproepe gemaak het, gee-pos het, of op ander maniere met spesifieke kontakte gekommunikeer het. Hierdie toestemming laat programme toe om kontakdata te skrap."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Laat die program toe om die data te wysig oor jou kontakte wat op jou tablet geberg is. Hierdie toestemming laat programme toe om kontakdata uit te vee."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Laat die program toe om die data te wysig oor jou kontakte wat op jou Android TV-toestel geberg is. Hierdie toestemming laat programme toe om kontakdata uit te vee."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Laat die program toe om die data te wysig oor jou kontakte wat op jou foon geberg is. Hierdie toestemming laat programme toe om kontakdata uit te vee."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lees oproeprekord"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Hierdie program kan jou oproepgeskiedenis lees."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"skryf oproeprekord"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"Kry toegang tot ekstra liggingverskaffer-bevele"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Gee die program toegang tot ekstra liggingverskaffer-bevele. Dit kan die program dalk toelaat om in te meng met die werking van die GPS of ander liggingbronne."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"kry net op die voorgrond toegang tot presiese ligging"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Hierdie program kan jou presiese ligging kry net wanneer dit op die voorgrond is. Hierdie liggingdienste moet aangeskakel wees en op jou foon beskikbaar wees sodat die program hulle kan gebruik. Dit kan veroorsaak dat meer batterykrag gebruik word."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"kry benaderde ligging (netwerkgegrond) net op die voorgrond"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Hierdie program kan jou ligging kry op grond van netwerkhulpbronne soos selfoontorings en Wi-Fi-netwerke, maar net wanneer die program op die voorgrond is. Die program kan hierdie liggingdienste net gebruik as hulle aangeskakel is en op jou tablet beskikbaar is."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Hierdie program kan jou ligging kry op grond van netwerkhulpbronne soos selfoontorings en Wi-Fi-netwerke, maar net wanneer die program op die voorgrond is. Die program kan hierdie liggingdienste net gebruik as hulle aangeskakel is en op jou Android TV-toestel beskikbaar is."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Hierdie program kan jou ligging kry op grond van netwerkhulpbronne soos selfoontorings en Wi-Fi-netwerke, maar net wanneer die program op die voorgrond is. Die program kan hierdie liggingdienste net gebruik as hulle aangeskakel is en op jou foon beskikbaar is."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Hierdie program kan jou presiese ligging kry net wanneer dit op die voorgrond is. Liggingdienste moet aangeskakel wees en op jou toestel beskikbaar wees sodat die program hulle kan gebruik. Dit kan veroorsaak dat meer batterykrag gebruik word."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"kry benaderde ligging net op die voorgrond"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Hierdie program kan jou benaderde ligging net kry wanneer dit op die voorgrond is. Liggingdienste moet aangeskakel wees en op jou toestel beskikbaar wees sodat die program hulle kan gebruik."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"kry ligging op die agtergrond"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"As dit bo en behalwe toegang tot die benaderde of presiese ligging verleen word, kan die program die ligging kry terwyl dit op die agtergrond werk."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Hierdie program het toegang tot ligging terwyl dit op die agtergrond werk, asook voorgrondtoegang tot ligging."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"verander jou klankinstellings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Laat die program toe om globale klankinstellings soos volume en watter luidspreker vir uitvoer gebruik word, te verander."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"neem klank op"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Laat die program toe om die opstelling van Bluetooth op die tablet te sien, en om verbindings met saamgebinde toestelle te maak en te aanvaar."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Laat die program toe om die opstelling van Bluetooth op jou Android TV-toestel te bekyk, en om verbindings met saamgebinde toestelle te maak en te aanvaar."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Laat die program toe om die opstelling van die Bluetooth op die foon te sien, en om verbindings met saamgebinde toestelle te maak en te aanvaar."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"beheer kortveldkommunikasie"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Laat die program toe om met kortveldkommunikasie- (NFC) merkers, kaarte en lesers te kommunikeer."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktiveer jou skermslot"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Gekoppel aan <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tik om lêers te bekyk"</string>
<string name="pin_target" msgid="8036028973110156895">"Speld vas"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Ontspeld"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Programinligting"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Begin tans demonstrasie …"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Dateer hierdie items op in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> en <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Stoor"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nee, dankie"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Nie nou nie"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nooit"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Dateer op"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Gaan voort"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"wagwoord"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Wissel verdeelde skerm"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Sluitskerm"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Skermkiekie"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g>-program in opspringervenster."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> se onderskrifbalk."</string>
</resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index c6e55c3..972737d 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"የሥራ መገለጫ አስተዳዳሪ መተግበሪያው ወይም ይጎድላል ወይም ተበላሽቷል። በዚህ ምክንያት የሥራ መገለጫዎ እና ተዛማጅ ውሂብ ተሰርዘዋል። እርዳታን ለማግኘት አስተዳዳሪዎን ያነጋግሩ።"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"የሥራ መገለጫዎ ከዚህ በኋላ በዚህ መሣሪያ ላይ አይገኝም"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"በጣም ብዙ የይለፍ ቃል ሙከራዎች"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"አስተዳዳሪ መሣሪያዎን ለግል ጥቅም ትተውታል"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"መሣሪያው የሚተዳደር ነው"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"የእርስዎ ድርጅት ይህን መሣሪያ ያስተዳድራል፣ እና የአውታረ መረብ ትራፊክን ሊከታተል ይችላል። ዝርዝሮችን ለማግኘት መታ ያድርጉ።"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"የእርስዎ መሣሪያ ይደመሰሳል"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"መተግበሪያው ስርጭቱ ከተጠናቀቀ በኋላ የሚቀሩ አጣባቂ ስርጭቶችን እንዲልክ ያስችለዋል። ከልክ በላይ መጠቀም የእርስዎን Android TV መሣሪያ ብዙ ማህደረ ትውስታን እንዲጠቀም በማድረግ ቀርፋፋ ወይም ያልተረጋጋ ሊያደርገው ይችላል።"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"መተግበሪያው ስርጭቱ ከተጠናቀቀ በኋላ የሚቀሩ አጣባቂ ስርጭቶችን እንዲልክ ይፈቅድለታል። ከልክ በላይ መጠቀም ስልኩ ብዙ ማህደረ ትውስታን እንዲጠቀም በማድረግ ቀርፋፋ ወይም ያልተረጋጋ ሊያደርገው ይችላል።"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"እውቂያዎችዎን ያንብቡ"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"መተግበሪያው በጡባዊ ተኮህ ስለተከማቹ ዕውቂያዎች ያሉትን ውሂቦች በሙሉ፤ ጥሪ ያደረግክበትን፣ ኢሜይል የላክበትን ወይም ከተወሰኑ ግለሰቦች ጋር በሌላ መንገድ የተገናኘህበትን ድግምግሞሽ ጨምሮ፣ እንዲያነብ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብህን እንዲያስቀምጡ የሚፈቅድላቸው ሲሆን ተንኮል አዘል መተግበሪያዎች የእውቂያህን ውሂብ ሳታውቀው ሊያጋሩት ይችላሉ።"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"መተግበሪያው ከተወሰኑ ግለሰቦች ጋር የተደዋወሉበት፣ ኢሜይል የተላላኩበት ወይም የተገናኙበት ተደጋጋሚነትም ጨምሮ በእርስዎ Android TV ላይ ስለተከማቹ እውቂያዎች እንዲያነብ ያስችለዋል። ይህ ፍቃድ መተግበሪያዎች የእውቂያ ውሂብዎን እንዲያስቀምጥ ያስችላቸዋል፣ እና ተንኮል-አዘል መተግበሪያዎች የእውቂያ ውሂብ ያለእውቀትዎ ሊያጋሩ ይችላሉ።"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"መተግበሪያው በስልክዎ ስለተከማቹ ዕውቂያዎች ያሉትን ውሂቦች በሙሉ፤ ጥሪ ያደረጉበትን፣ ኢሜይል የላኩበትን ወይም ከተወሰኑ ግለሰቦች ጋር በሌላ መንገድ የተገናኙበትን ድግምግሞሽ ጨምሮ፣ እንዲያነብ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብዎን እንዲያስቀምጡ የሚፈቅድላቸው ሲሆን ተንኮል አዘል መተግበሪያዎች የእውቂያዎን ውሂብ ሳያውቁት ሊያጋሩት ይችላሉ።"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"መተግበሪያው በእርስዎ ጡባዊ ላይ ስለተከማቹ ዕውቂያዎችዎ ያለ ውሂብ እንዲያነብብ ያስችለዋል። መተግበሪያዎች እንዲሁም በእርስዎ ጡባዊ ላይ እውቂያዎችን የፈጠሩ የመለያዎች መዳረሻ ይኖራቸዋል። ይህ እርስዎ በጫኗቸው መተግበሪያዎች የተፈጠሩ መለያዎችን ሊያካትት ይችላል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብዎን እንዲያስቀምጡ የሚፈቅድላቸው ሲሆን ተንኮል-አዘል መተግበሪያዎች የእውቂያዎን ውሂብ ሳያውቁት ሊያጋሩት ይችላሉ።"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"መተግበሪያው በእርስዎ የAndroid TV መሣሪያ ላይ ስለተከማቹ እውቂያዎችዎ ያለ ውሂብን እንዲቀይር ያስችለዋል። መተግበሪያዎች እንዲሁም በእርስዎ የAndroid TV መሣሪያ ላይ እውቂያዎችን የፈጠሩ የመለያዎች መዳረሻ ይኖራቸዋል። ይህ እርስዎ በጫኗቸው መተግበሪያዎች የተፈጠሩ መለያዎችን ሊያካትት ይችላል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብዎን እንዲያስቀምጡ የሚፈቅድላቸው ሲሆን ተንኮል-አዘል መተግበሪያዎች የእውቂያዎን ውሂብ ሳያውቁት ሊያጋሩት ይችላሉ።"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"መተግበሪያው በእርስዎ ስልክ ላይ ስለተከማቹ ዕውቂያዎችዎ ያለ ውሂብ እንዲያነብብ ያስችለዋል። መተግበሪያዎች እንዲሁም በእርስዎ ስልክ ላይ እውቂያዎችን የፈጠሩ የመለያዎች መዳረሻ ይኖራቸዋል። ይህ እርስዎ በጫኗቸው መተግበሪያዎች የተፈጠሩ መለያዎችን ሊያካትት ይችላል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብዎን እንዲያስቀምጡ የሚፈቅድላቸው ሲሆን ተንኮል-አዘል መተግበሪያዎች የእውቂያዎን ውሂብ ሳያውቁት ሊያጋሩት ይችላሉ።"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ዕውቂያዎችዎን ያስተካክሉ"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"መተግበሪያው በጡባዊ ቱኮህ ስለተከማቹ የዕውቂያዎችህ ውሂብ በሙሉ፤ ጥሪ ያደረግክበትን፣ ኢሜይል የላክበትን ወይም ከተወሰኑ እውቂያዎች ጋር በሌላ መንገድ የተገናኘህበትን ድግምግሞሽ ጨምሮ፣ እንዲያስተካክል ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብ እንዲሰርዙ ይፈቅድላቸዋል።"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"መተግበሪያው ከተወሰኑ እውቂያዎች ጋር የሚደዋወሉበት፣ ኢሜይል የሚላላኩበት ወይም የሚገናኙበት ተደጋጋሚነትም ጨምሮ በእርስዎ Android TV ላይ ስለተከማቹ ዕውቂያዎችዎ ያለ ውሂብ እንዲቀይር ያስችለዋል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብ እንዲሰርዙ ያስችላቸዋል።"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"መተግበሪያው በስልክዎ ስለተከማቹ የዕውቂያዎችዎ ውሂብ በሙሉ፤ ጥሪ ያደረጉበትን፣ ኢሜይል የላኩበትን ወይም ከተወሰኑ እውቂያዎች ጋር በሌላ መንገድ የተገናኙበትን ድግምግሞሽ ጨምሮ፣ እንዲያስተካክል ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብ እንዲሰርዙ ይፈቅድላቸዋል።"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"መተግበሪያው በእርስዎ ጡባዊ ላይ ስለተከማቹ እውቂያዎችዎ ያለ ውሂብን እንዲቀይር ያስችለዋል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብን እንዲሰርዙ ያስችላቸዋል።"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"መተግበሪያው በእርስዎ የAndroid TV መሣሪያ ላይ ስለተከማቹ የዕውቂያዎች እንዲቀይር ይፈቅድለታል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብን እንዲሰርዙ ያስችላቸዋል።"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"መተግበሪያው በእርስዎ ስልክ ላይ ስለተከማቹ እውቂያዎችዎ ያለ ውሂብን እንዲቀይር ያስችለዋል። ይህ ፈቃድ መተግበሪያዎች የእውቂያ ውሂብን እንዲሰርዙ ያስችላቸዋል።"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"የጥሪ ምዝግብ ማስታወሻን ያንብቡ"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ይህ መተግበሪያ የእርስዎን የጥሪ ታሪክ ማንበብ ይችላል።"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"የጥሪ ምዝግብ ማስታወሻን ፃፍ"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ተጨማሪ ሥፍራ አቅራቢ ትዕዛዞችን ድረስ።"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"መተግበሪያው ተጨማሪ የአካባቢ አቅራቢ ትእዛዞችን እንዲደርስ ይፈቅድለታል። ይሄ መተግበሪያው በጂፒኤስ ወይም ሌላ የአካባቢ ምንጮች ስራ ላይ ጣልቃ እንዲገባ ሊፈቅድለት ይችላል።"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"መዳረሻ ከፊት ለፊት ብቻ ትክክለኛ ነው"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"ይህ መተግበሪያ ከፊት ላይ ሆኖ ሲበራ ብቻ ትክክለኛውን መገኛ አካባቢ ማግኘት ይችላል። እነዚህ የመገኛ አካባቢ አገልግሎቶች መተግበሪያው መጠቀም እንዲችል ሊበሩ እና በእርስዎ ስልክ ላይ ሊገኙ የሚችሉ መሆን አለባቸው።"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"ግምታዊ አካባቢ (በአውታረ መረብ ላይ የተመሠረተ) ከፊት ላይ ሲሆን ብቻ መድረስ"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ይህ መተግበሪያ እንደ የሕዋስ ማማዎች እና የWi-Fi አውታረ መረቦች ባሉ የአውታረ መረብ ምንጮች ላይ በመመስረት የእርስዎን አካባቢ ማግኘት ይችላል፣ ነገር ግን መተግበሪያው ከፊት ሲሆን ብቻ። እነዚህ የአካባቢ አገልግሎቶች መተግበሪያው መጠቀም እንዲችል ሊበሩ እና በእርስዎ ጡባዊ ላይ ሊገኙ የሚችሉ መሆን አለባቸው።"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"ይህ መተግበሪያ እንደ የሕዋስ ማማዎች እና የWi-Fi አውታረ መረቦች ባሉ የአውታረ መረብ ምንጮች ላይ በመመስረት የእርስዎን አካባቢ ማግኘት ይችላል፣ ነገር ግን መተግበሪያው ከፊት ሲሆን ብቻ። እነዚህ የአካባቢ አገልግሎቶች መተግበሪያው መጠቀም እንዲችል ሊበሩ እና በእርስዎ Android TV መሣሪያ ላይ ሊገኙ የሚችሉ መሆን አለባቸው።"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ይህ መተግበሪያ እንደ የሕዋስ ማማዎች እና የWi-Fi አውታረ መረቦች ባሉ የአውታረ መረብ ምንጮች ላይ በመመስረት የእርስዎን አካባቢ ማግኘት ይችላል፣ ነገር ግን መተግበሪያው ከፊት ሲሆን ብቻ። እነዚህ የአካባቢ አገልግሎቶች መተግበሪያው መጠቀም እንዲችል ሊበሩ እና በእርስዎ ስልክ ላይ ሊገኙ የሚችሉ መሆን አለባቸው።"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"ይህ መተግበሪያ ከፊት ላይ ሆኖ ሲበራ ብቻ ትክክለኛውን አካባቢዎ ማግኘት ይችላል። መተግበሪያው የአካባቢ አገልግሎቶች መጠቀም እንዲችል ሊበሩ እና በእርስዎ መሣሪያ ላይ ሊገኙ የሚችሉ መሆን አለባቸው።"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"ከፊት ለፊት ብቻ ግምታዊ አካባቢን ድረስ"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"ይህ መተግበሪያ ግምታዊ አካባቢዎን ማግኘት የሚችለው ከፊት ሲሆን ብቻ ነው። መተግበሪያው የአካባቢ አገልግሎቶች መጠቀም እንዲችል ሊበሩ እና በእርስዎ መሣሪያ ላይ ሊገኙ የሚችሉ መሆን አለባቸው።"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"አካባቢን በበስተጀርባ ድረስ"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ይህ ከግምታዊ ወይም ትክክለኛ አካባቢ በተጨማሪ ከተሰጠ መተግበሪያው በበስተጀርባ እያሄደ ሳለ አካባቢውን መድረስ ይችላል።"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"ይህ መተግበሪያ ከፊት የአካባቢ መዳረሻ በተጨማሪም ከበስተጀርባ እያሄደ ሳለ አካባቢን መድረስ ይችላል።"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"የድምፅ ቅንብሮችን ለውጥ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"መተግበሪያው አንደ የድምጽ መጠን እና ለውጽአት የትኛውን የድምጽ ማጉያ ጥቅም ላይ እንደዋለ የመሳሰሉ ሁለንተናዊ የድምጽ ቅንብሮችን እንዲያስተካክል ይፈቅድለታል።"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ኦዲዮ ይቅዱ"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"መተግበሪያው በጡባዊ ተኮው ላይ ያለውን የብሉቱዝ ውቅር እንዲያይ እና ከተጣመሩ መሳሪያዎች ጋር ግንኙነቶችን እንዲያደርግና እንዲቀበል ይፈቅድለታል።"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"በእርስዎ የ Android TV መሣሪያ የብሉቱዝ ውቅረት ለማየት፣ እና ከተጣመረው መሣሪያ ጋር ግንኙነት ለመቀበል እንዲችል ለመተግበሪያው ይፈቅዳል።"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"መተግበሪያው በስልኩ ላይ ያለውን የብሉቱዝ ውቅር እንዲያይ እና ከተጣመሩ መሳሪያዎች ጋር ግንኙነቶችን እንዲያደርግና እንዲቀበል ይፈቅድለታል።"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ቅርብ የግኑኙነትመስክ (NFC) ተቆጣጠር"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ከቅርብ ግኑኙነት መስክ (NFC) መለያዎች፣ ካርዶች እና አንባቢ ጋር ለማገናኘት ለመተግበሪያው ይፈቅዳሉ።"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"የማያ ገጽዎን መቆለፊያ ያሰናክሉ"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"ከ<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> ጋር ተገናኝቷል"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ፋይሎችን ለመመልከት መታ ያድርጉ"</string>
<string name="pin_target" msgid="8036028973110156895">"ፒን"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"ንቀል"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"የመተግበሪያ መረጃ"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ማሳያን በማስጀመር ላይ…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"እነዚህ ንጥሎች በ"<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ውስጥ ይዘመኑ፦ <xliff:g id="TYPE_0">%1$s</xliff:g>፣ <xliff:g id="TYPE_1">%2$s</xliff:g> እና <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"አስቀምጥ"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"አይ፣ አመሰግናለሁ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"አሁን አይደለም"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"በጭራሽ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"አዘምን"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ቀጥል"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"የይለፍ ቃል"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"የተከፈለ ማያን ቀያይር"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"የማያ ገጽ ቁልፍ"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ቅጽበታዊ ገጽ እይታ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"የ<xliff:g id="APP_NAME">%1$s</xliff:g> መተግበሪያ በብቅ-ባይ መስኮት ውስጥ።"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"የ<xliff:g id="APP_NAME">%1$s</xliff:g> የሥዕል ገላጭ ጽሑፍ አሞሌ።"</string>
</resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 609b77e..839a336 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -196,8 +196,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"تطبيق المشرف للملف الشخصي للعمل مفقود أو تالف لذا تم حذف الملف الشخصي للعمل والبيانات ذات الصلة. اتصل بالمشرف للحصول على المساعدة."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"لم يعد ملفك الشخصي للعمل متاحًا على هذا الجهاز"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"تم إجراء محاولات كثيرة جدًا لإدخال كلمة المرور"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"تنازل المشرف عن الجهاز للاستخدام الشخصي"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"تتم إدارة الجهاز"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"تدير مؤسستك هذا الجهاز ويمكنها مراقبة حركة بيانات الشبكة. يمكنك النقر للحصول على تفاصيل."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"سيتم محو بيانات جهازك."</string>
@@ -393,13 +392,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"للسماح للتطبيق بإرسال مواد بث ثابتة، والتي تظل متوفرة بعد انتهاء البث. وقد يؤدي الاستخدام المفرط لهذا التطبيق إلى حدوث بطء أو عدم استقرار في جهاز Android TV من خلال جعل الجهاز يستخدم قدرًا كبيرًا جدًا من الذاكرة."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"للسماح للتطبيق بإرسال مجموعات بث مستمرة، والتي تظل بعد انتهاء البث. قد يؤدي الاستخدام بكثرة إلى حدوث بطء أو عدم استقرار في الهاتف من خلال التسبب في استخدام الهاتف لمساحة كبيرة للغاية من الذاكرة."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"قراءة جهات الاتصال"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"للسماح للتطبيق بقراءة بيانات حول جهات الاتصال المخزنة على الجهاز اللوحي، بما في ذلك مدى تكرار اتصالك بأفراد بعينهم أو مراسلتهم عبر البريد الإلكتروني أو التواصل معهم بطرق أخرى خلافًا لذلك. ويتيح هذا الإذن للتطبيقات حفظ بيانات جهات الاتصال، وقد تشارك التطبيقات الضارة بيانات جهات الاتصال بدون معرفتك."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"للسماح للتطبيق بقراءة البيانات حول جهات الاتصال المخزّنة على جهاز Android TV، بما في ذلك مدى تكرار اتصالك بأفراد بعينهم أو مراسلتهم عبر البريد الإلكتروني أو التواصل معهم بطرق أخرى. ويتيح هذا الإذن للتطبيقات إمكانية حفظ بيانات جهة الاتصال، وقد تشارك التطبيقات الضارة بيانات جهة الاتصال بدون علمك."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"للسماح للتطبيق بقراءة بيانات حول جهات الاتصال المخزنة على الهاتف، بما في ذلك مدى تكرار اتصالك بأفراد بعينهم أو مراسلتهم عبر البريد الإلكتروني أو التواصل معهم بطرق أخرى خلافًا لذلك. ويتيح هذا الإذن للتطبيقات حفظ بيانات جهات الاتصال، وقد تشارك التطبيقات الضارة بيانات جهات الاتصال بدون معرفتك."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"يسمح هذا الإذن للتطبيق بقراءة بيانات حول جهات الاتصال المخزّنة على جهازك اللوحي. ويمكن للتطبيقات أيضًا الوصول إلى الحسابات التي تم إنشاء جهات الاتصال من خلالها على جهازك اللوحي. ويمكن أن يشمل ذلك الحسابات التي أنشأتها التطبيقات التي ثبتّها. ويتيح هذا الإذن للتطبيقات حفظ بيانات جهة الاتصال، وقد تشارك التطبيقات الضارة بيانات جهة الاتصال بدون علمك."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"يسمح هذا الإذن للتطبيق بقراءة بيانات حول جهات الاتصال المخزّنة على جهاز Android TV. ويمكن للتطبيقات أيضًا الوصول إلى الحسابات التي تم إنشاء جهات الاتصال من خلالها على جهاز Android TV. ويمكن أن يشمل ذلك الحسابات التي أنشأتها التطبيقات التي ثبتّها. ويتيح هذا الإذن للتطبيقات حفظ بيانات جهة الاتصال، وقد تشارك التطبيقات الضارة بيانات جهة الاتصال بدون علمك."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"يسمح هذا الإذن للتطبيق بقراءة بيانات حول جهات الاتصال المخزّنة على هاتفك. ويمكن للتطبيقات أيضًا الوصول إلى الحسابات التي تم إنشاء جهات الاتصال من خلالها على هاتفك. ويمكن أن يشمل ذلك الحسابات التي أنشأتها التطبيقات التي ثبتّها. ويتيح هذا الإذن للتطبيقات حفظ بيانات جهة الاتصال، وقد تشارك التطبيقات الضارة بيانات جهة الاتصال بدون علمك."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"تعديل جهات الاتصال"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"للسماح للتطبيق بتعديل البيانات حول جهات الاتصال المخزنة على جهازك اللوحي، بما في ذلك مدى تكرار اتصالك بجهات اتصال بعينها أو مراسلتها عبر البريد الإلكتروني أو التواصل معها بأية طريقة أخرى خلافًا لذلك. وقد يتيح هذا الإذن للتطبيقات حذف بيانات جهات الاتصال."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"للسماح للتطبيق بتعديل البيانات حول جهات الاتصال المخزّنة على جهاز Android TV، بما في ذلك مدى تكرار اتصالك بجهات اتصال بعينها أو مراسلتها عبر البريد الإلكتروني أو التواصل معها بطرق أخرى. ويتيح هذا الإذن للتطبيقات إمكانية حذف بيانات جهات الاتصال."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"للسماح للتطبيق بتعديل البيانات حول جهات الاتصال المخزنة على هاتفك، بما في ذلك مدى تكرار اتصالك بجهات اتصال بعينها أو مراسلتها عبر البريد الإلكتروني أو التواصل معها بأية طريقة أخرى خلافًا لذلك. وقد يتيح هذا الإذن للتطبيقات حذف بيانات جهات الاتصال."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"يسمح هذا الإذن للتطبيق بتعديل البيانات حول جهات الاتصال المخزّنة على جهازك اللوحي. ويسمح هذا الإذن للتطبيقات بحذف بيانات جهات الاتصال."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"يسمح هذا الإذن للتطبيق بتعديل البيانات حول جهات الاتصال المخزّنة على جهاز Android TV. ويسمح هذا الإذن للتطبيقات بحذف بيانات جهات الاتصال."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"يسمح هذا الإذن للتطبيق بتعديل البيانات حول جهات الاتصال المخزّنة على هاتفك. ويسمح هذا الإذن للتطبيقات بحذف بيانات جهات الاتصال."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"قراءة سجل المكالمات"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"يمكن لهذا التطبيق قراءة سجل المكالمات."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"كتابة سجل المكالمات"</string>
@@ -419,13 +418,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"الدخول إلى المزيد من أوامر موفر الموقع"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"للسماح للتطبيق بالدخول إلى أوامر إضافية لموفر الموقع. قد يتيح هذا للتطبيق التداخل مع تشغيل تقنية نظام تحديد المواقع العالمي (GPS) أو مصادر الموقع الأخرى."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"الوصول إلى الموقع الجغرافي الدقيق في الواجهة الأمامية فقط"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"لا يمكن لهذا التطبيق معرفة موقعك الجغرافي بالضبط إلا عندما يعمل في الخلفية. ويجب تفعيل خدمات الموقع الجغرافي هذه وأن تكون متاحة على الهاتف حتى يتمكن التطبيق من استخدامها. وقد يؤدي هذا إلى زيادة استهلاك طاقة البطارية."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"الوصول إلى الموقع الجغرافي التقريبي (بالاعتماد على الشبكة) في الخلفية فقط"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"يمكن لهذا التطبيق معرفة موقعك مستعينًا بمصادر الشبكات مثل الأبراج الخلوية وشبكات Wi-Fi ولكن يجب أن يعمل في الخلفية. ويجب تفعيل خدمات المواقع هذه وتوفّرها على جهازك اللوحي كي يتمكن التطبيق من استخدامها."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"يمكن لهذا التطبيق معرفة موقعك الجغرافي مستعينًا بمصادر الشبكات مثل الأبراج الخلوية وشبكات Wi-Fi، ولكن بشرط أن يعمل التطبيق في الواجهة الأمامية. ويجب تفعيل \"خدمات الموقع الجغرافي\" هذه وتوفّرها على جهاز Android TV لكي يتمكّن التطبيق من استخدامها."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"يمكن لهذا التطبيق معرفة موقعك مستعينًا بمصادر الشبكات مثل الأبراج الخلوية وشبكات Wi-Fi ولكن يجب أن يعمل في الخلفية. ويجب تفعيل خدمات المواقع هذه وتوفّرها على هاتفك كي يتمكن التطبيق من استخدامها."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"لا يمكن لهذا التطبيق معرفة موقعك الجغرافي بالضبط عندما يعمل في الخلفية. ويجب تفعيل خدمات الموقع الجغرافي وأن تكون متاحة على جهازك حتى يتمكن التطبيق من استخدامها. وقد يؤدي هذا إلى زيادة استهلاك طاقة البطارية."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"الوصول إلى الموقع الجغرافي التقريبي في الواجهة الأمامية فقط"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"لا يمكن لهذا التطبيق معرفة موقعك الجغرافي التقريبي إذا كان يعمل في الخلفية. ويجب تفعيل خدمات الموقع الجغرافي وأن تكون متاحة على جهازك حتى يتمكن التطبيق من استخدامها."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"الوصول إلى الموقع الجغرافي في الخلفية"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"إذا تمّ منح إذن التطبيق هذا بالإضافة إلى الموقع الجغرافي التقريبي أو الدقيق، يمكن للتطبيق الوصول إلى الموقع الجغرافي أثناء تشغيله في الخلفية."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"يمكن لهذا التطبيق الوصول إلى الموقع الجغرافي أثناء عمله في الخلفية، بالإضافة إلى إمكانية وصوله للموقع الجغرافي أثناء عمله في الواجهة الأمامية."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"تغيير إعداداتك الصوتية"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"للسماح للتطبيق بتعديل إعدادات الصوت العامة مثل مستوى الصوت وأي السماعات يتم استخدامها للاستماع."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"تسجيل الصوت"</string>
@@ -506,6 +503,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"للسماح للتطبيق بعرض تهيئة البلوتوث على الجهاز اللوحي وإجراء اتصالات وقبولها مع الأجهزة المقترنة."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"للسماح للتطبيق بعرض بيانات ضبط البلوتوث على جهاز Android TV وإجراء اتصالات مع الأجهزة المقترنة وقبولها."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"للسماح للتطبيق بعرض تهيئة البلوتوث على الهاتف وإجراء اتصالات وقبولها مع الأجهزة المقترنة."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"التحكم في اتصال الحقل القريب"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"للسماح للتطبيق بالاتصال بعلامات الاتصال قريب المدى (NFC)، والبطاقات وبرامج القراءة."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"إيقاف قفل الشاشة"</string>
@@ -1990,7 +1991,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"تم الاتصال بـ <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"انقر لعرض الملفات"</string>
<string name="pin_target" msgid="8036028973110156895">"تثبيت"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"إزالة تثبيت"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"معلومات عن التطبيق"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"جارٍ بدء العرض التوضيحي…"</string>
@@ -2037,6 +2042,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"هل تريد تحديث هذه العناصر في "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g> و<xliff:g id="TYPE_1">%2$s</xliff:g> و<xliff:g id="TYPE_2">%3$s</xliff:g>؟"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"حفظ"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"لا، شكرًا"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ليس الآن"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"أبدًا"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"تعديل"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"متابعة"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"كلمة مرور"</string>
@@ -2136,5 +2143,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"تبديل \"تقسيم الشاشة\""</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"شاشة القفل"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"لقطة شاشة"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> في نافذة منبثقة"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"شريط الشرح لتطبيق <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 091b563f..a9529db 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"কৰ্মস্থানৰ প্ৰ\'ফাইলৰ প্ৰশাসক এপ্ নাই বা ব্যৱহাৰযোগ্য হৈ থকা নাই। যাৰ ফলত আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইল আৰু ইয়াৰ লগত জড়িত অন্য ডেটাসমূহ মচা হৈছে। সহায়ৰ বাবে আপোনাৰ প্ৰশাসকৰ সৈতে সম্পর্ক কৰক।"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"আপোনাৰ কৰ্মস্থানৰ প্ৰ\'ফাইল এই ডিভাইচটোত আৰু উপলব্ধ নহয়"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"বহুতবাৰ ভুলকৈ পাছৱৰ্ড দিয়া হৈছে"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"প্ৰশাসকে ডিভাইচটো ব্যক্তিগত ব্যৱহাৰৰ বাবে বাজেয়প্ত কৰিছে"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"পৰিচালিত ডিভাইচ"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"আপোনাৰ প্ৰতিষ্ঠানটোৱে এই ডিভাইচটো পৰিচালনা কৰে আৰু ই নেটৱৰ্কৰ ট্ৰেফিক পৰ্যবেক্ষণ কৰিব পাৰে। সবিশেষ জানিবলৈ টিপক।"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"আপোনাৰ ডিভাইচৰ ডেটা মচা হ\'ব"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"এপ্টোক ব্ৰ’ডকাষ্ট শেষ হোৱাৰ পাছত বাকী থকা ষ্টিকী ব্ৰ’ডকাষ্টবোৰ পঠিয়াবলৈ অনুমতি দিয়ে। ইয়াক অত্যধিক ব্যৱহাৰ কৰিলে আপোনাৰ Android TV ডিভাইচটোক অতি বেছি পৰিমাণৰ মেম’ৰী খৰচ কৰাই লেহেমীয়া অথবা অস্থিৰ কৰিব পাৰে।"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"সম্প্ৰচাৰৰ শেষত বাকী ৰোৱা ষ্টিকী ব্ৰ\'ডকাষ্টবোৰ প্ৰেৰণ কৰিবলৈ এপক অনুমতি দিয়ে। ইয়াক অত্য়ধিক ব্যৱহাৰ কৰাৰ ফলত মেম\'ৰি অধিক খৰচ হোৱাৰ বাবে ফ\'নটো লেহেমীয়া বা অস্থিৰ হৈ পৰে।"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"নিজৰ সম্পর্ক সূচী পঢ়ক"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"আপুনি কোনো ব্যক্তি বিশেষৰ সৈতে টেবলেট, ইমেইল বা অন্য মাধ্যমেৰে কিমান সঘনাই যোগাযোগ কৰিছে সেই তথ্য়সহ ফ\'নৰ সম্পর্কসূচীত সঞ্চয় কৰা ডেটা পঢ়িবলৈ এপক অনুমতি দিয়ে৷ এই কার্যই এপক আপোনাৰ সম্পর্কৰ ডেটা ছেভ কৰিবলৈ অনুমতি দিয়ে আৰু ক্ষতিকাৰক এপবোৰে সম্পর্কসূচীৰ ডেটা আপোনাৰ অজ্ঞাতেই শ্বেয়াৰ কৰিব পাৰে৷"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"এপ্টোক আপোনাৰ Android TV ডিভাইচটোত ষ্ট’ৰ কৰি ৰখা সম্পৰ্কবোৰৰ ডেটা পঢ়িবলৈ দিয়াৰ লগতে আপুনি কোনো বিশেষ ব্যক্তিৰ সৈতে কিমান সঘনাই কল, ইমেইল বা অন্য মাধ্যমেৰে যোগাযোগ কৰিছে তাকো জানিবলৈ অনুমতি দিয়ে। এই অনুমতিটোৱে এপ্টোক আপোনাৰ সম্পর্ক ডেটা ছেভ কৰিবলৈ দিয়ে আৰু ক্ষতিকাৰক এপ্সমূহে আপুনি নজনাকৈ আপোনাৰ সম্পর্কসূচীৰ ডেটা শ্বেয়াৰ কৰিব পাৰে।"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"আপুনি কোনো বিশেষ ব্যক্তিৰ সৈতে কিমান সঘনাই ফ\'ন, ইমেইল বা অন্য মাধ্যমেৰে যোগাযোগ কৰে সেই সম্পর্কে ফ\'নৰ সম্পর্ক সূচীত সঞ্চয় কৰা ডেটা পঢ়িবলৈ এপক অনুমতি দিয়ে। এই অনুমতিএ এপক আপোনাৰ সম্পর্কৰ ডেটা ছেভ কৰিবলৈ দিয়ে আৰু ক্ষতিকাৰক এপবোৰে সম্পর্কৰ ডেটা আপোনাৰ অজ্ঞাতেই শ্বেয়াৰ কৰিব পাৰে।"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"এপ্টোক আপোনাৰ টেবলেটত ষ্ট’ৰ কৰি ৰখা সম্পৰ্কবোৰৰ ডেটা পঢ়িবলৈ দিয়ে। এপ্সমূহৰ আপোনাৰ টেবলেটত থকা একাউণ্টসমূহলৈও এক্সেছ থাকিব যি সম্পৰ্কসমূহ সৃষ্টি কৰিছে। ইয়াত আপুনি ইনষ্টল কৰা এপ্সমূহে সৃষ্টি কৰা একাউণ্টসমূহ অন্তৰ্ভুক্ত হ’ব পাৰে। এই অনুমতিটোৱে এপ্সমূহক আপোনাৰ সম্পর্ক ডেটা ছেভ কৰিবলৈ দিয়ে আৰু ক্ষতিকাৰক এপ্সমূহে আপুনি নজনাকৈ সম্পর্ক ডেটা শ্বেয়াৰ কৰিব পাৰে।"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"এপ্টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা সম্পৰ্কবোৰৰ ডেটা পঢ়িবলৈ দিয়ে। এপ্সমূহৰ আপোনাৰ Android TV ডিভাইচটোত থকা একাউণ্টসমূহলৈও এক্সেছ থাকিব যিবোৰে সম্পৰ্কসমূহ সৃষ্টি কৰিছে। ইয়াত আপুনি ইনষ্টল কৰা এপ্সমূহে সৃষ্টি কৰা একাউণ্টসমূহ অন্তৰ্ভুক্ত হ’ব পাৰে। এই অনুমতিটোৱে এপ্সমূহক আপোনাৰ সম্পর্ক ডেটা ছেভ কৰিবলৈ দিয়ে আৰু ক্ষতিকাৰক এপ্সমূহে আপুনি নজনাকৈ সম্পর্ক ডেটা শ্বেয়াৰ কৰিব পাৰে।"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"এপ্টোক আপোনাৰ ফ’নত ষ্ট’ৰ কৰি ৰখা সম্পৰ্কবোৰৰ ডেটা পঢ়িবলৈ দিয়ে। এপ্সমূহৰ আপোনাৰ ফ’নত থকা একাউণ্টসমূহলৈও এক্সেছ থাকিব যিবোৰে সম্পৰ্কসমূহ সৃষ্টি কৰিছে। ইয়াত আপুনি ইনষ্টল কৰা এপ্সমূহে সৃষ্টি কৰা একাউণ্টসমূহ অন্তৰ্ভুক্ত হ’ব পাৰে। এই অনুমতিটোৱে এপ্সমূহক আপোনাৰ সম্পর্ক ডেটা ছেভ কৰিবলৈ দিয়ে আৰু ক্ষতিকাৰক এপ্সমূহে আপুনি নজনাকৈ সম্পর্ক ডেটা শ্বেয়াৰ কৰিব পাৰে।"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"আপোনাৰ সম্পর্ক সূচী সংশোধন কৰক"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"আপুনি ব্য়ক্তি বিশেষক কিমান সঘনাই কল কৰিছে, ইমেইল কৰিছে বা অন্য উপায়েৰে যোগাযোগ কৰিছে তাক অন্তৰ্ভুক্ত কৰি এপটোক আপোনাৰ টেবলেটত সঞ্চয় কৰি ৰখা সম্পৰ্ক সূচীৰ ডেটা সংশোধন কৰিবলৈ অনুমতি দিয়ে৷ এই অনুমতি দিলে এপসমূহে সম্পৰ্কসূচীৰ ডেটা মচিব পাৰে।"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"এপ্টোক আপোনাৰ Android TV ডিভাইচটোত ষ্ট’ৰ কৰি ৰখা সম্পৰ্কবোৰৰ ডেটা সংশোধন কৰিবলৈ দিয়াৰ লগতে আপুনি কোনো বিশেষ ব্যক্তিৰ সৈতে কিমান সঘনাই কল, ইমেইল বা অন্য মাধ্যমেৰে যোগাযোগ কৰিছে তাকো জানিবলৈ অনুমতি দিয়ে। এই অনুমতিটোৱে এপ্সমূহক সম্পৰ্কসূচীৰ ডেটা মচিবলৈ অনুমতি দিয়ে।"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"আপুনি কোনো বিশেষ ব্যক্তিৰ সৈতে কিমান সঘনাই ফ\'ন, ইমেইল বা অন্য মাধ্যমেৰে যোগাযোগ কৰে সেই সম্পর্কে ফ\'নৰ সম্পর্ক সূচীত সঞ্চয় কৰা ডেটা পঢ়িবলৈ এপক অনুমতি দিয়ে। এই কার্যই এপক সম্পর্কৰ ডেটা মচিবলৈ অনুমতি দিয়ে।"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"এপ্টোক আপোনাৰ টেবলেটত ষ্ট’ৰ কৰি ৰখা সম্পৰ্কবোৰৰ ডেটা সংশোধন কৰিবলৈ দিয়ে। এই অনুমতিয়ে এপ্সমূহক সম্পর্ক ডেটা মচিবলৈ দিয়ে।"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"এপ্টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা সম্পৰ্কবোৰৰ ডেটা সংশোধন কৰিবলৈ দিয়ে। এই অনুমতিয়ে এপ্সমূহক সম্পর্ক ডেটা মচিবলৈ দিয়ে।"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"এপ্টোক আপোনাৰ ফ\'নত ষ্ট’ৰ কৰি ৰখা সম্পৰ্কবোৰৰ ডেটা সংশোধন কৰিবলৈ দিয়ে। এই অনুমতিয়ে এপ্সমূহক সম্পর্ক ডেটা মচিবলৈ দিয়ে।"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"কল লগ পঢ়ক"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"এই এপে আপোনাৰ কলৰ ইতিহাস পঢ়িব পাৰে।"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"কল লগ লিখক"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"অতিৰিক্ত অৱস্থান দেখুওৱা নির্দেশত প্ৰৱেশ কৰক"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"অৱস্থানৰ অতিৰিক্ত নির্দেশনাসমূহত প্ৰৱেশ কৰিবলৈ এপক অনুমতি দিয়ে। ইয়ে এপটোক জিপিএছ বা অন্য অৱস্থান উৎসসমূহৰ কাৰ্যকলাপত হস্তক্ষেপ কৰাৰ সুযোগ দিব পাৰে।"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"কেৱল অগ্ৰভূমিত অৱস্থানৰ সঠিক তথ্য় পাওক"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"এই এপটোৱে যেতিয়া ই নেপথ্যত চলি থাকে তেতিয়া আপোনাৰ সঠিক অৱস্থান নিৰ্ণয় কৰিব পাৰে। এপটোৱে ব্যৱহাৰ কৰিব পৰাকৈ এই অৱস্থান সেৱাসমূহ অন হৈ থাকিবই লাগিব আৰু আপোনাৰ ফ\'নত উপলব্ধ হ\'ব লাগিব। ইয়াৰ ফলত বেটাৰিৰ খৰচ বাঢ়িব পাৰে।"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"কেৱল অগ্ৰভূমিত থকা অৱস্থাতহে আনুমানিক অৱস্থানৰ (নেটৱৰ্কৰ ওপৰত ভিত্তি কৰি) এক্সেছ"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"এই এপটোৱে অগ্ৰভূমিত থকা অৱস্থাতহে চেল টাৱাৰ আৰু ৱাই-ফাই নেটৱৰ্ক আদিৰ দৰে নেটৱৰ্ক উৎসৰ ওপৰত ভিত্তি কৰি আপোনাৰ অৱস্থান জানিব পাৰে। এপটোৱে এই অৱস্থান সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ হ\'লে সেইবোৰ অন হৈ থকাৰ লগতে আপোনাৰ টেবলেটত থাকিবই লাগিব।"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"এই এপ্টোৱে অগ্ৰভূমিত থকা অৱস্থাতহে চেল টাৱাৰবোৰ আৰু ৱাই-ফাই নেটৱৰ্কবোৰৰ দৰে নেটৱৰ্ক উৎসবোৰৰ ওপৰত ভিত্তি কৰি আপোনাৰ অৱস্থান পাব পাৰে, কিন্তু কেৱল তেতিয়াহে যেতিয়া এপ্টো অগ্ৰভূমিত থাকে। এপ্টোৱে এই অৱস্থান সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ সেইবোৰ আপোনাৰ Android TV ডিভাইচটোত অন কৰি ৰখাৰ লগতে উপলব্ধ হ’ব লাগিব।"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"এই এপটোৱে অগ্ৰভূমিত থকা অৱস্থাতহে চেল টাৱাৰ আৰু ৱাই-ফাই নেটৱৰ্ক আদিৰ দৰে নেটৱৰ্ক উৎসৰ ওপৰত ভিত্তি কৰি আপোনাৰ অৱস্থান জানিব পাৰে। এপটোৱে এই অৱস্থান সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ হ\'লে সেইবোৰ অন হৈ থকাৰ লগতে আপোনাৰ ফ\'নত থাকিবই লাগিব।"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"এই এপ্টো কেৱল অগ্ৰভূমিত থাকিলেহে এইটোৱে আপোনাৰ সঠিক অৱস্থান লাভ কৰিব পাৰে। এপ্টোৱে অৱস্থান সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ হ’লে সেইবোৰ অন হৈ থকাৰ লগতে সেয়া আপোনাৰ ডিভাইচত উপলব্ধ থাকিবই লাগিব। ইয়াৰ ফলত বেটাৰিৰ খৰচ বাঢ়িব পাৰে।"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"কেৱল অগ্ৰভূমিত আনুমানিক অৱস্থান এক্সেছ কৰক"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"এই এপ্টো অগ্ৰভূমিত থাকিলেহে এইটোৱে আপোনাৰ আনুমানিক অৱস্থান লাভ কৰিব পাৰে। এপ্টোৱে অৱস্থান সেৱাসমূহ ব্যৱহাৰ কৰিবলৈ হ’লে সেইবোৰ অন হৈ থকাৰ লগতে সেয়া আপোনাৰ ডিভাইচত উপলব্ধ থাকিবই লাগিব।"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"নেপথ্যত চলি থকা সময়ত অৱস্থানৰ এক্সেছ"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ইয়াৰ উপৰিও যদি ইয়াক আনুমানিক বা সঠিক অৱস্থানৰ এক্সেছ দিয়া হয়, তেন্তে উক্ত এপে নেপথ্যত চলি থকাৰ সময়ত অৱস্থানৰ এক্সেছ লাভ কৰিব পাৰে।"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"এই এপ্টোৱে অগ্ৰভূমিত অৱস্থান এক্সেছ কৰাৰ ওপৰিও নেপথ্যত চলি থাকিলেও অৱস্থান এক্সেছ কৰিব পাৰে।"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"আপোনাৰ অডিঅ\' ছেটিংসমূহ সলনি কৰক"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"এপটোক ভলিউমৰ দৰে গ্ল\'বেল অডিঅ\' ছেটিংসমূহ যাৰ স্পীকাৰক আউটপুটৰ বাবে ব্যৱহাৰ হয় তাক সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"অডিঅ\' ৰেকর্ড কৰক"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"টেবলেটত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"এপ্টোক আপোনাৰ Android TV ডিভাইচটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু পেয়াৰ কৰি থোৱা ডিভাইচসমূহৰ সৈতে সংযোগ কৰিবলৈ আৰু গ্ৰহণ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ফ\'নটোত ব্লুটুথৰ কনফিগাৰেশ্বন চাবলৈ আৰু যোৰা লগোৱা ডিভাইচসমূহৰ জৰিয়তে সংযোগ কৰিবলৈ আৰু সংযোগৰ অনুৰোধ স্বীকাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"নিয়েৰ ফিল্ড কমিউনিকেশ্বন নিয়ন্ত্ৰণ কৰক"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"এপটোক নিয়েৰ ফিল্ড কমিউনিকেশ্বন (NFC) টেগ, কাৰ্ড আৰু ৰিডাৰসমূহৰ সৈতে যোগাযোগ কৰিবলৈ অনুমতি দিয়ে।"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"আপোনাৰ স্ক্ৰীণ ল\'ক অক্ষম কৰক"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g>ৰ সৈতে সংযুক্ত হৈ আছে"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ফাইলসমূহ চাবৰ বাবে টিপক"</string>
<string name="pin_target" msgid="8036028973110156895">"পিন"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"আনপিন"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"এপ্ সম্পৰ্কীয় তথ্য"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ডেম\' আৰম্ভ কৰি থকা হৈছে…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"এই তথ্যবোৰ "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> আৰু <xliff:g id="TYPE_2">%3$s</xliff:g>ত আপডে’ট কৰিবনে ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"ছেভ কৰক"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"নালাগে, ধন্যবাদ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"এতিয়া নহয়"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"কেতিয়াও নহয়"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"আপডে’ট কৰক"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"অব্যাহত ৰাখক"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"পাছৱৰ্ড"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"বিভাজিত স্ক্ৰীন ট’গল কৰক"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"লক স্ক্ৰীন"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"স্ক্ৰীণশ্বট"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"পপ আপ ৱিণ্ড’ত <xliff:g id="APP_NAME">%1$s</xliff:g> এপ্।"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ কেপশ্বন বাৰ।"</string>
</resources>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 359dfb7..a34da13 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"İş profili admin tətbiqi ya yoxdur, ya da korlanıb. Nəticədə iş profili və onunla bağlı data silinib. Kömək üçün admin ilə əlaqə saxlayın."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"İş profili artıq bu cihazda əlçatan deyil"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Həddindən çox parol cəhdi"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin şəxsi istifadə üçün cihazdan imtina etdi"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Cihaz idarə olunur"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Təşkilat bu cihazı idarə edir və şəbəkənin ötürülməsinə nəzarət edə bilər. Detallar üçün klikləyin."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Cihazınız təmizlənəcəkdir"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Tətbiqə yayım bitdikdən sonra qalan əlaqələndirici yayımları göndərmək icazəsi verir. Həddindən çox istifadə Android TV cihazının daha çox yaddaşdan istifadə etməsinə səbəb olaraq onun surətini zəiflədə və stabilliyini poza bilər."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Tətbiqə yayım bitdikdən sonra da qalan çətin yayımlar göndərməyə imkan verir. Hədsiz istifadə çox yaddaş istifadəsinə səbəb olmaqla telefonu yavaş və qeyri-stabil edə bilər."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"kontakrlatınızı oxumaq"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Tətbiqə planşetinizdə yerləşən kontaktları oxumaq icazəsi verir, tez-tez zəng elədiyiniz, emailləşdiyiniz və ya əlaqə saxladığınız xüsusi individuallar daxil olmaqla. Bu icazə tətbiqlərə kontakt məlumatlarınızı saxlamağa və zərərli tətbiqlərə kontakt məlumatlarını sizin bilginiz olmada paylaşma imkanı yaradır."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Tətbiqə xüsusi şəxslərə hansı müddətdən bir zəng etmək, e-poçt göndərmək və ya onlarla başqa şəkildə əlaqə saxlamaq daxil olmaqla, Android TV cihazında saxlanan kontaktlar haqqında datanı oxumaq icazəsi verir. Bu icazə proqramlara əlaqə məlumatlarınızı saxlamaq imkanı verir və zərərli proqramlar sizin xəbəriniz olmadan əlaqə məlumatlarınızı paylaşa bilər."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Tətbiqə tez-tez zəng elədiyiniz, e-məktub göndərdiyiniz və ya əlaqə saxladığınız xüsusi individuallar daxil olmaqla telefonunuzda yerləşən kontaktları oxumaq icazəsi verir. Bu icazə tətbiqlərə kontakt məlumatlarınızı saxlamağa və zərərli tətbiqlərə kontakt məlumatlarını sizin xəbəriniz olmada paylaşma imkanı yaradır."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tətbiqə planşetinizdə saxlanılan kontaktlar haqqında datanı oxumaq icazəsi verir. Tətbiqlərin, həmçinin planşetinizdə kontaktlar yaradan hesablara da giriş imkanı olacaq. Buraya quraşdırdığınız tətbiqlər tərəfindən yaradılan hesablar daxil ola bilər. Bu icazə tətbiqlərə kontakt məlumatlarınızı yadda saxlamaq imkanı verir və zərərli tətbiqlər kontakt məlumatlarını xəbəriniz olmadan paylaşa bilər."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Tətbiqə Android TV cihazınızda saxlanan kontaktlar haqqında datanı oxumaq icazəsi verir. Tətbiqlərin, həmçinin Android TV cihazınızda kontaktlar yaradan hesablara da giriş imkanı olacaq. Buraya quraşdırdığınız tətbiqlər tərəfindən yaradılan hesablar daxil ola bilər. Bu icazə tətbiqlərə kontakt məlumatlarınızı yadda saxlamaq imkanı verir və zərərli tətbiqlər kontakt məlumatlarını xəbəriniz olmadan paylaşa bilər."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Tətbiqə telefonunuzda saxlanılan kontaktlar haqqında datanı oxumaq icazəsi verir. Tətbiqlərin, həmçinin telefonunuzda kontaktlar yaradan hesablara da giriş imkanı olacaq. Buraya quraşdırdığınız tətbiqlər tərəfindən yaradılan hesablar daxil ola bilər. Bu icazə tətbiqlərə kontakt məlumatlarınızı yadda saxlamaq imkanı verir və zərərli tətbiqlər kontakt məlumatlarını xəbəriniz olmadan paylaşa bilər."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"kontaktlarınızı dəyişdirir"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Tətbiqə planşetinizdəki zəng etmək tezliyiniz, elektron poçtlarınız, ünsiyyətləriniz haqqında məlumatları dəyişməyə imkan verir. Bu icazə kontakt məlumatlarının silinməsinə də imkan verir."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Tətbiqə xüsusi şəxslərə hansı müddətdən bir zəng etmək, e-poçt göndərmək və ya onlarla başqa şəkildə əlaqə saxlamaq daxil olmaqla, Android TV cihazında saxlanan kontaktlar haqqında datanı dəyişdirmək icazəsi verir. Bu icazə tətbiqlərə kontakt datasını silmək imkanı verir."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Tətbiqə Sizin zəng etmək tezliyiniz, elektron poçtlarınız, ünsiyyətləriniz haqqında məlumatları dəyişməyə imkan verir. Buna kontaktların silinməsi imkanı də daxildir."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tətbiqə planşetinizdə saxlanan kontaktlar haqqında datanı dəyişdirmək icazəsi verir. Bu icazə tətbiqlərə kontakt datasını silmək imkanı verir."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Tətbiqə Android TV cihazında saxlanan kontaktlar haqqında datanı dəyişdirmək icazəsi verir. Bu icazə tətbiqlərə kontakt datasını silmək imkanı verir."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Tətbiqə telefonunuzda saxlanan kontaktlar haqqında datanı dəyişdirmək icazəsi verir. Bu icazə tətbiqlərə kontakt datasını silmək imkanı verir."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"zəng qeydiyyatını oxu"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Bu tətbiq zəng tarixçənizi oxuya bilər."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"zəng loqu yazır"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"əlavə məkan provayderi əmrlərinə çıxış"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Tətbiqə ekstra məkan provayder əmrlərinə girişə imkan verir. Bu, tətbiqə GPS və ya digər lokal mənbələrlə əməliyyata müdaxiləyə imkan verə bilər."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"yalnız ön planda dəqiq məkana daxil olun"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Bu tətbiq yalnız ön fonda olduqda dəqiq məkanınızı əldə edə bilər. Tətbiqin bunlardan istifadə etməsi üçün bu məkan xidmətləri aktiv edilməlidir və telefonda əlçatan olmalıdır. Bu, batareya sərfiyyatını artıra bilər."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"yalnız ön planda təxmini məkana (şəbəkəyə əsaslanan) giriş"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Bu tətbiq baza stansiyaları və Wi-Fi şəbəkələri kimi şəbəkə mənbələrinə əsaslanaraq məkanınızı əldə edə bilər. Bu, yalnız tətbiq ön planda olduqda mümkündür. Tətbiqin istifadə edə bilməsi üçün bu məkan xidmətləri aktiv edilməli və planşetdə əlçatan olmalıdır."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Bu tətbiq baza stansiyaları və Wi-Fi kimi şəbəkə mənbələrinə əsaslanaraq məkanınızı əldə edə bilər. Bu, yalnız tətbiq ön planda olduqda mümkündür. Tətbiqin istifadə etməsi üçün bu məkan xidmətləri aktiv edilməli və Android TV cihazında əlçatan olmalıdır."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Bu tətbiq baza stansiyaları və Wi-Fi kimi şəbəkə mənbələrinə əsaslanaraq məkanınızı əldə edə bilər. Bu, yalnız tətbiq ön planda olduqda mümkündür. Tətbiqin istifadə edə bilməsi üçün bu məkan xidmətləri aktiv edilməli və telefonda əlçatan olmalıdır."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Bu tətbiq yalnız ön fonda olduqda dəqiq məkanınızı əldə edə bilər. Tətbiqin bunlardan istifadə etməsi üçün məkan xidmətləri aktiv edilməli və cihazda əlçatan olmalıdır. Bu, batareya sərfiyyatını artıra bilər."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"yalnız ön planda təqribi məkana giriş"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Bu tətbiq yalnız ön fonda olduqda təqribi məkanınızı əldə edə bilər. Tətbiqin istifadə edə bilməsi üçün məkan xidmətləri cihazda aktiv edilməli və əlçatan olmalıdır."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"arxa fonda məkan girişi"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Bu, təqribi və ya dəqiq məkan girişinə əlavə olaraq verilərsə, tətbiq arxa fonda işləyərkən məkana daxil ola bilər."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Bu tətbiq ön planda məkana girişlə yanaşı, arxa fonda işləyərkən məkana giriş edə bilər."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"audio ayarlarınızı dəyişir"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tətbiqə səs və hansı spikerin çıxış üçün istifadə olunduğu kimi qlobal səs ayarlarını dəyişdirməyə imkan verir."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"səs yaz"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Tətbiqə yerli Bluetooth planşetinin konfiqurasiyasını görməyə və cütlənmiş cihazlarla bağlantılar etməyə və qəbul etməyə imkan verir."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Tətbiqə Android TV cihazında Bluetooth konfiqurasiyasına baxmaq, həmçinin qoşulmuş cihazlar ilə bağlantılar yaratmaq və qəbul etmək icazəsi verir."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Tətbiqə Bluetooth və ya telefon konfiqurasiyalarını görməyə və qoşulmuş cihazlarla əlaqə qurmağa və qəbul etməyə icazə verir."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communication\'ı kontrol et"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Tətbiqə Yaxın Məsafə Kommunikasiyası (NFC) teqləri, kartları və oxuyucuları ilə əlaqə qurmağa icazə verir."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"Ekran kilidini deaktiv edir"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> məhsuluna bağlandı"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Faylları görmək üçün basın"</string>
<string name="pin_target" msgid="8036028973110156895">"Pin kod"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Çıxarın"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Tətbiq məlumatı"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Demo başlayır…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Bu elementlər "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ünvanında yenilənsin: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> və <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Yadda saxlayın"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Xeyr, çox sağ olun"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"İndi yox"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Heç vaxt"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Yeniləyin"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Davam edin"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"parol"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Bölünmüş Ekrana keçid"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Kilid Ekranı"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Ekran şəkli"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Açilən pəncərədə <xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqi."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> başlıq paneli."</string>
</resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index a27d359..96a12c7 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -190,8 +190,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplikacija za administratore na profilu za Work nedostaje ili je oštećena. Zbog toga su profil za Work i povezani podaci izbrisani. Obratite se administratoru za pomoć."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profil za Work više nije dostupan na ovom uređaju"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Previše pokušaja unosa lozinke"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator je ustupio uređaj za ličnu upotrebu"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Uređajem se upravlja"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Organizacija upravlja ovim uređajem i može da nadgleda mrežni saobraćaj. Dodirnite za detalje."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Uređaj će biti obrisan"</string>
@@ -384,13 +383,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Dozvoljava aplikaciji da šalje lepljiva emitovanja koja ostaju po završetku emitovanja. Prekomerna upotreba može da uspori ili destabilizuje Android TV uređaj tako što će ga primorati da troši previše memorije."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Dozvoljava aplikaciji da šalje prijemčiva emitovanja, koja ostaju po završetku emitovanja. Prekomerna upotreba može da uspori ili destabilizuje telefon tako što će ga primorati da troši previše memorije."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"čitanje kontakata"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Dozvoljava aplikaciji da čita podatke o kontaktima uskladištene na tabletu, uključujući podatke o tome koliko često zovete određene osobe, šaljete im poruke e-pošte ili na drugi način komunicirate sa njima. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima, a zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Dozvoljava aplikaciji da čita podatke o kontaktima koje čuvate na Android TV uređaju, uključujući učestalost poziva, slanja imejlova ili drugih načina komunikacije sa određenim pojedincima. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima i zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Dozvoljava aplikaciji da čita podatke o kontaktima uskladištene na telefonu, uključujući podatke o tome koliko često zovete određene osobe, šaljete im poruke e-pošte ili na drugi način komunicirate sa njima. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima, a zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Dozvoljava aplikaciji da čita podatke o kontaktima koje čuvate na tabletu. Aplikacije će imati pristup i nalozima na vašem tabletu na kojima su napravljeni kontakti. Tu mogu da spadaju nalozi koje su otvorile aplikacije koje ste instalirali. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima i zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Dozvoljava aplikaciji da čita podatke o kontaktima koje čuvate na Android TV uređaju. Aplikacije će imati pristup i nalozima na vašem Android TV uređaju na kojima su napravljeni kontakti. Tu mogu da spadaju nalozi koje su otvorile aplikacije koje ste instalirali. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima i zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Dozvoljava aplikaciji da čita podatke o kontaktima koje čuvate na telefonu. Aplikacije će imati pristup i nalozima na vašem telefonu na kojima su napravljeni kontakti. Tu mogu da spadaju nalozi koje su otvorile aplikacije koje ste instalirali. Ova dozvola omogućava aplikacijama da čuvaju podatke o kontaktima i zlonamerne aplikacije mogu da dele podatke o kontaktima bez vašeg znanja."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"izmena kontakata"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Dozvoljava aplikaciji da menja podatke o kontaktima uskladištene na tabletu, uključujući podatke o tome koliko često zovete određene kontakte, šaljete im poruke e-pošte ili na drugi način komunicirate sa njima. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Dozvoljava aplikaciji da menja podatke o kontaktima koje čuvate na Android TV uređaju, uključujući učestalost poziva, slanja imejlova ili drugih načina komunikacije sa određenim kontaktima. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Dozvoljava aplikaciji da menja podatke o kontaktima uskladištene na telefonu, uključujući podatke o tome koliko često zovete određene kontakte, šaljete im poruke e-pošte ili na drugi način komunicirate sa njima. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Dozvoljava aplikaciji da menja podatke o kontaktima koje čuvate na tabletu. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Dozvoljava aplikaciji da menja podatke o kontaktima koje čuvate na Android TV uređaju. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Dozvoljava aplikaciji da menja podatke o kontaktima koje čuvate na telefonu. Ova dozvola omogućava aplikacijama da brišu podatke o kontaktima."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"čitanje evidencije poziva"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Ova aplikacija može da čita istoriju poziva."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"pisanje evidencije poziva"</string>
@@ -410,13 +409,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"pristup dodatnim komandama dobavljača lokacije"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Omogućava aplikaciji da pristupa dodatnim komandama davaoca usluga lokacije. To može da omogući aplikaciji da utiče na rad GPS-a ili drugih izvora lokacije."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"pristup preciznoj lokaciji samo u prvom planu"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Ova aplikacija može da odredi vašu tačnu lokaciju samo kada radi u prvom planu. Ove usluge lokacije moraju da budu uključene i dostupne na telefonu da bi aplikacija mogla da ih koristi. To može da poveća potrošnju baterije."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"pristup približnoj lokaciji (utvrđenoj preko mreže) samo u prvom planu"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ova aplikacija može da pristupi vašoj lokaciji pomoću izvora mreže, kao što su mobilni predajnici i Wi-Fi mreže, ali samo kada aplikacija radi u prvom planu. Ove usluge lokacije moraju da budu uključene i dostupne na tabletu da bi aplikacija mogla da ih koristi"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ova aplikacija može da pristupi vašoj lokaciji pomoću izvora mreže, kao što su mobilni predajnici i Wi-Fi mreže, ali samo kada aplikacija radi u prvom planu. Ove usluge lokacije moraju da budu uključene i dostupne na Android TV uređaju da bi aplikacija mogla da ih koristi."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ova aplikacija može da pristupi vašoj lokaciji pomoću izvora mreže, kao što su mobilni predajnici i Wi-Fi mreže, ali samo kada aplikacija radi u prvom planu. Ove usluge lokacije moraju da budu uključene i dostupne na telefonu da bi aplikacija mogla da ih koristi."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ova aplikacija može da odredi vašu tačnu lokaciju samo kada radi u prvom planu. Usluge lokacije moraju da budu uključene i dostupne na uređaju da bi aplikacija mogla da ih koristi. To može da poveća potrošnju baterije."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"pristup približnoj lokaciji samo u prvom planu"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ova aplikacija može da odredi vašu približnu lokaciju samo kada radi u prvom planu. Usluge lokacije moraju da budu uključene i dostupne na uređaju da bi aplikacija mogla da ih koristi."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"pristup lokaciji u pozadini"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ako se pored približnog ili preciznog pristupa lokacija odobri i ovaj, aplikacija može da pristupa lokaciji dok je pokrenuta u pozadini."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ova aplikacija može da pristupa lokaciji dok radi u pozadini, kao i kada radi u prvom planu."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"promena audio podešavanja"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Dozvoljava aplikaciji da menja globalna audio podešavanja kao što su jačina zvuka i izbor zvučnika koji se koristi kao izlaz."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje audio zapisa"</string>
@@ -497,6 +494,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Dozvoljava aplikaciji da pregleda konfiguraciju Bluetooth-a na tabletu, kao i da uspostavlja i prihvata veze sa uparenim uređajima."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Dozvoljava aplikaciji da pregleda konfiguraciju Bluetooth-a na Android TV uređaju i da uspostavlja i prihvata veze sa uparenim uređajima."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Dozvoljava aplikaciji da pregleda konfiguraciju Bluetooth-a na telefonu, kao i da uspostavlja i prihvata veze sa uparenim uređajima."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontrola komunikacije u užem polju (Near Field Communication)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Dozvoljava aplikaciji da komunicira sa oznakama, karticama i čitačima komunikacije kratkog dometa (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"onemogućavanje zaključavanja ekrana"</string>
@@ -1894,7 +1895,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Povezano je sa proizvodom <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Dodirnite za pregled datoteka"</string>
<string name="pin_target" msgid="8036028973110156895">"Zakači"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Otkači"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informacije o aplikaciji"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Pokrećemo demonstraciju..."</string>
@@ -1938,6 +1943,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Želite li da ažurirate ove stavke u usluzi "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> i <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Sačuvaj"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ne, hvala"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ne sada"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nikada"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Ažuriraj"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Nastavi"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"lozinka"</string>
@@ -2034,5 +2041,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Uključite/isključite podeljeni ekran"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Zaključani ekran"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Snimak ekrana"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u iskačućem prozoru."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Traka sa naslovima aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index fb0fa33..c7503d0 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Праграма адміністратара для працоўнага профілю адсутнічае або пашкоджана. У выніку гэтага ваш працоўны профіль і звязаныя з ім даныя былі выдалены. Звярніцеся па дапамогу да адміністратара."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Ваш працоўны профіль больш не даступны на гэтай прыладзе"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Занадта шмат спроб уводу пароля"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Адміністратар пераналадзіў прыладу для асабістага выкарыстання"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Прылада знаходзіцца пад кіраваннем"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Ваша арганізацыя кіруе гэтай прыладай і можа сачыць за сеткавым трафікам. Дакраніцеся для атрымання дадатковай інфармацыі."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Даныя вашай прылады будуць сцерты"</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Дазваляе праграме адпраўляць замацаваныя трансляцыі, якія застаюцца пасля заканчэння асноўнай трансляцыі. Злоўжыванне гэтым можа зрабіць працу прылады Android TV павольнай або няўстойлівай, прымушаючы яе выкарыстоўваць занадта шмат памяці."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Дазваляе прыкладанню адпраўляць далейшыя звязаныя перадачы, якія застаюцца пасля заканчэння асноўнай перадачы. Злоўжыванне можа зрабіць працу тэлефона павольнай або няўстойлівай, прымушаючы яго выкарыстоўваць занадта шмат памяці."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"чытанне кантактаў"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Дазваляе прыкладанням счытваць дадзеныя аб кантактах, якія захоўваюцца на планшэце, у тым ліку звесткi пра тое, як часта вы выклiкалi канкрэтных абанентаў, пiсалi iм па электроннай пошце або кантактавалi іншымi спосабамі. Дзякуючы гэтаму дазволу прыкладаннi могуць захоўваць дадзеныя гiсторыi выклiкаў, а шкоднасныя прыкладаннi могуць адпраўляць кантактныя дадзеныя без вашага ведама."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Дазваляе праграме счытваць даныя пра кантакты, якія захоўваюцца на прыладзе Android TV, у тым ліку звесткі пра тое, як часта вы выклікалі пэўных абанентаў, пісалі ім электронныя лісты ці звязваліся з імі іншымі спосабамі. Гэты дазвол дае праграмам магчымасць захоўваць даныя пра кантакты, а шкодныя праграмы могуць абагульваць гэтыя даныя без вашага ведама."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Дазваляе прыкладанням счытваць дадзеныя аб кантактах, якія захоўваюцца на тэлефоне, у тым ліку звесткi пра частату, з якой вы выклiкалi iх, пiсалi iм па электроннай пошце ці кантактавалi іншым спосабам. Дзякуючы гэтаму дазволу прыкладаннi могуць захоўваць дадзеныя гiсторыi выклiкаў, а шкоднасныя прыкладаннi могуць адпраўляць кантактныя дадзеныя без вашага ведама."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Дазваляе праграме счытваць даныя пра кантакты, якія захоўваюцца на вашым планшэце. Праграмы таксама атрымаюць доступ на планшэце да ўліковых запісаў, у якіх створаны кантакты. Сярод іх могуць быць уліковыя запісы, створаныя праграмамі, якія вы ўсталявалі. Гэты дазвол дае праграмам магчымасць захоўваць даныя пра кантакты, а шкодныя праграмы могуць абагульваць гэтыя даныя без вашага ведама."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Дазваляе праграме счытваць даныя пра кантакты, якія захоўваюцца на вашай прыладзе Android TV. Праграмы таксама атрымаюць доступ да ўліковых запісаў на прыладзе Android TV, у якіх створаны кантакты. Сярод іх могуць быць уліковыя запісы, створаныя праграмамі, якія вы ўсталявалі. Гэты дазвол дае праграмам магчымасць захоўваць даныя пра кантакты, а шкодныя праграмы могуць абагульваць гэтыя даныя без вашага ведама."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Дазваляе праграме счытваць даныя пра кантакты, якія захоўваюцца на вашым тэлефоне. Праграмы таксама атрымаюць доступ на тэлефоне да ўліковых запісаў, у якіх створаны кантакты. Сярод іх могуць быць уліковыя запісы, створаныя праграмамі, якія вы ўсталявалі. Гэты дазвол дае праграмам магчымасць захоўваць даныя пра кантакты, а шкодныя праграмы могуць абагульваць гэтыя даныя без вашага ведама."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"змена кантактаў"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Дазваляе прыкладанням змяняць дадзеныя аб кантактах, якія захоўваюцца на планшэце, у тым ліку частата, з якой вы выклiкалi асоб, пiсалi па электроннай пошце ці іншым спосабам кантактавалi з iмі. З гэтым дазволам прыкладаннi змогуць выдаляць кантактныя дадзеныя."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Дазваляе праграме змяняць даныя пра кантакты, якія захоўваюцца на прыладзе Android TV, у тым ліку звесткі пра тое, як часта вы выклікалі пэўных абанентаў, пісалі ім электронныя лісты ці звязваліся з імі іншымi спосабамі. Гэты дазвол дае праграмам магчымасць выдаляць даныя пра кантакты."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Дазваляе прыкладанням змяняць дадзеныя аб кантактах, якія захоўваюцца на тэлефоне, у тым ліку частата, з якой вы выклiкалi асоб, пiсалi па электроннай пошце ці іншым спосабам кантактавалi з iмі. З гэтым дазволам прыкладаннi змогуць выдаляць кантактныя дадзеныя."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Дазваляе праграме змяняць даныя пра кантакты, якія захоўваюцца на вашым планшэце. Гэты дазвол дае праграмам магчымасць выдаляць даныя пра кантакты."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Дазваляе праграме змяняць даныя пра кантакты, якія захоўваюцца на вашай прыладзе Android TV. Гэты дазвол дае праграмам магчымасць выдаляць даныя пра кантакты."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Дазваляе праграме змяняць даныя пра кантакты, якія захоўваюцца на вашым тэлефоне. Гэты дазвол дае праграмам магчымасць выдаляць даныя пра кантакты."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"чытанне гiсторыi выклікаў"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Гэта праграма можа чытаць вашу гісторыю выклікаў."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"запіс гiсторыi выклікаў"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"доступ да дадатковых камандаў пастаўшчыка месцазнаходжання"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Дазваляе праграме атрымліваць доступ да дадатковых каманд службаў вызначэння месцазнаходжання. Гэта можа дазволіць праграме ўмешвацца ў функцыянаванне GPS або іншых крыніц даных аб месцазнаходжаннi."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"доступ да дакладнага месцазнаходжання толькі ў асноўным рэжыме"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Гэта праграма можа атрымліваць звесткі пра ваша дакладнае месцазнаходжанне толькі ў асноўным рэжыме. Службы геалакацыі павінны быць уключаны і даступныя на вашым тэлефоне, каб праграма магла імі карыстацца. Гэта можа павялічыць спажыванне зараду акумулятара."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"доступ да прыблізнага месцазнаходжання (на падставе сеткі) толькі ў актыўным рэжыме"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Гэта праграма можа атрымліваць звесткі пра ваша месцазнаходжанне толькі ў актыўным рэжыме на падставе даных сеткавых крыніц, такіх як вышкі сотавай сувязі і сеткі Wi-Fi. Каб праграма магла карыстацца гэтымі службамі геалакацыі, неабходна ўключыць і зрабіць іх даступнымі на планшэце."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Гэта праграма можа атрымліваць звесткі пра ваша месцазнаходжанне толькі ў актыўным рэжыме на падставе даных сеткавых крыніц, такіх як вышкі сотавай сувязі і сеткі Wi-Fi. Уключыце гэтыя службы геалакацыі і зрабіце іх даступнымі на прыладзе Android TV, каб праграма магла выкарыстоўваць іх даныя."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Гэта праграма можа атрымліваць звесткі пра ваша месцазнаходжанне толькі ў актыўным рэжыме на падставе даных сеткавых крыніц, такіх як вышкі сотавай сувязі і сеткі Wi-Fi. Каб праграма магла карыстацца гэтымі службамі геалакацыі, неабходна ўключыць і зрабіць іх даступнымі на тэлефоне."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Гэта праграма можа атрымліваць звесткі пра ваша дакладнае месцазнаходжанне толькі ў актыўным рэжыме. Службы геалакацыі павінны быць уключаны і даступныя на вашай прыладзе, каб праграма магла імі карыстацца. Гэта можа павялічыць спажыванне зараду акумулятара."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"доступ да прыблізнага месцазнаходжання толькі ў актыўным рэжыме"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Гэта праграма можа атрымліваць звесткі пра ваша прыблізнае месцазнаходжанне толькі ў актыўным рэжыме. Службы геалакацыі павінны быць уключаны і даступныя на вашай прыладзе, каб праграма магла імі карыстацца."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"доступ да вызначэння месцазнаходжання ў фонавым рэжыме"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Акрамя доступу да прыкладнага ці дакладнага месцазнаходжання праграма можа мець доступ да вызначэння геалакацыі ў фонавым рэжыме працы. На гэта патрабуецца дазвол."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Гэта праграма можа мець доступ да даных пра месцазнаходжанне не толькі ў актыўным, але і ў фонавым рэжыме."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"змяняць налады аудыё"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дазваляе прыкладанням змяняць глабальныя налады гуку, такія як моц і тое, што дынамік выкарыстоўваецца для выхаду."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"запіс аўдыя"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Дазваляе прыкладанню праглядаць канфігурацыю Bluetooth на планшэце , а таксама здзяйсняць і прымаць злучэнні са спалучанымі прыладамі."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Дазваляе праграме праглядаць канфігурацыю Bluetooth на прыладзе Android TV, а таксама выконваць і дазваляць злучэнні са спалучанымі прыладамі."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Дазваляе прыкладанню праглядаць канфігурацыю Bluetooth на тэлефоне , а таксама здзяйсняць і прымаць злучэнні са спалучанымі прыладамі."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"кантроль Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Дазваляе прыкладаннzv спалучацца з тэгамі, картамі і счытваючымі прыладамі Near Field Communication (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"адключэнне блакiроўкi экрана"</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Падлучана да <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Краніце для прагляду файлаў"</string>
<string name="pin_target" msgid="8036028973110156895">"Замацаваць"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Адмацаваць"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Інфармацыя пра праграму"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Ідзе запуск дэманстрацыі…"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Абнавіць наступныя элементы ў сэрвісе "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> і <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Захаваць"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Не, дзякуй"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Не зараз"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Ніколі"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Абнавіць"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Працягнуць"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"пароль"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Пераключальнік падзеленага экрана"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Экран блакіроўкі"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Здымак экрана"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ва ўсплывальным акне."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Панэль субцітраў праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"."</string>
</resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 69c998d..882d8fd 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Приложението за администриране на служебния потребителски профил липсва или е повредено. В резултат на това той и свързаните с него данни са изтрити. За съдействие се свържете с администратора си."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Служебният ви потребителски профил вече не е налице на това устройство"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Опитите за паролата са твърде много"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Администраторът предостави устройствотото за лична употреба"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Устройството се управлява"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Организацията ви управлява това устройство и може да наблюдава мрежовия трафик. Докоснете за подробности."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Данните на устройството ви ще бъдат изтрити"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Дава възможност на приложението да изпраща оставащи излъчвания, които се запазват след края на излъчването. Прекалената употреба може да причини бавна или нестабилна работа на устройството ви с Android TV, като го накара да използва твърде много памет."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Разрешава на приложението да изпраща оставащи излъчвания, които се запазват след края на излъчването. Прекалената употреба може да причини бавна или нестабилна работа на телефона, като го накара да използва твърде много памет."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"четене на контактите ви"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Разрешава на приложението да чете данни за съхранените на таблета ви контакти, включително честотата на обаждане, изпращане на имейли или общуване по друг начин с конкретни лица. Това разрешение позволява на приложенията да запазват информацията за контактите ви, а злонамерените могат да я споделят без ваше знание."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Дава възможност на приложението да чете данните за съхраняваните на устройството ви с Android TV контакти, включително колко често сте се обаждали на конкретни хора, изпращали сте им имейли или сте общували с тях по други начини. Това разрешение позволява на приложенията да запазват информация за контактите ви, а злонамерените приложения може да я споделят без знанието ви."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Разрешава на приложението да чете данни за съхранените на телефона ви контакти, включително честотата на обаждане, изпращане на имейли или общуване по друг начин с конкретни лица. Това разрешение позволява на приложенията да запазват информацията за контактите ви, а злонамерените могат да я споделят без ваше знание."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Разрешава на приложението да чете данните за съхраняваните в таблета ви контакти. Приложенията ще имат достъп и до профилите на таблета ви, в които са създадени контакти. Това може да включва и профилите, създадени от инсталирани от вас приложения. Така приложенията могат да запазват данните за контактите ви, а злонамерените приложения – да ги споделят без ваше знание."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Разрешава на приложението да чете данните за съхраняваните в устройството ви с Android TV контакти. Приложенията ще имат достъп и до профилите на устройството ви с Android TV, в които са създадени контакти. Това може да включва и профилите, създадени от инсталирани от вас приложения. Така приложенията могат да запазват данните за контактите ви, а злонамерените приложения – да ги споделят без ваше знание."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Разрешава на приложението да чете данните за съхраняваните в телефона ви контакти. Приложенията ще имат достъп и до профилите на телефона ви, в които са създадени контакти. Това може да включва и профилите, създадени от инсталирани от вас приложения. Така приложенията могат да запазват данните за контактите ви, а злонамерените приложения – да ги споделят без ваше знание."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"промяна на контактите ви"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Разрешава на приложението да променя данните за съхранените на таблета ви контакти, включително честотата на обаждане, изпращане на имейли или общуване по друг начин с конкретни контакти. Това разрешение му позволява да изтрива информацията за тях."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Дава възможност на приложението да променя данните за съхраняваните на устройството ви с Android TV контакти, включително колко често сте се обаждали на конкретни хора, изпращали сте им имейли или сте общували с тях по други начини. Това разрешение позволява на приложенията да изтриват данните за контактите."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Разрешава на приложението да променя данните за съхранените на телефона ви контакти, включително честотата на обаждане, изпращане на имейли или общуване по друг начин с конкретни контакти. Това разрешение му позволява да изтрива информацията за тях."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Разрешава на приложението да променя данните за съхраняваните на таблета ви контакти. Това разрешение позволява на приложенията да изтриват данните за контактите."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Разрешава на приложението да променя данните за съхраняваните на устройството ви с Android TV контакти. Това разрешение позволява на приложенията да изтриват данните за контактите."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Разрешава на приложението да променя данните за съхраняваните на телефона ви контакти. Това разрешение позволява на приложенията да изтриват данните за контактите."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"четене на списъка с обаждания"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Това приложение може да чете историята на обажданията ви."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"запис на списъка с обаждания"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"достъп до допълнителни команди на доставчика на местоположение"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Разрешава на приложението достъп до допълнителни команди на доставчика на местоположение. Това може да позволи на приложението да смущава работата на GPS или на другите източници на местоположение."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"достъп до точното местоположение само на преден план"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Приложението може да получава данни за точното ви местоположение само когато работи на преден план. Тези услуги за местоположение трябва да са включени и налице на телефона ви, за да могат да се използват от приложението. Това може да увеличи потреблението на батерията."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"достъп до приблизителното местоположение (основано на мрежи) само на преден план"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Приложението може да получава данни за местоположението ви въз основа на мрежови източници, като клетъчни базови станции и Wi-Fi мрежи, само когато работи на преден план. Тези услуги за местоположение трябва да са включени и налице на таблета ви, за да могат да се използват от приложението."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Това приложение може да получава данни за местоположението ви въз основа на мрежови източници, като клетъчни базови станции и Wi-Fi мрежи, само когато работи на преден план. Тези услуги за местоположение трябва да са включени и налични на устройството ви с Android TV, за да могат да се използват от приложението."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Приложението може да получава данни за местоположението ви въз основа на мрежови източници, като клетъчни базови станции и Wi-Fi мрежи, само когато работи на преден план. Тези услуги за местоположение трябва да са включени и налице на телефона ви, за да могат да се използват от приложението."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Приложението може да получава данни за точното ви местоположение само когато работи на преден план. Услугите за местоположение трябва да са включени и налице на устройството ви, за да могат да се използват от приложението. Това може да увеличи потреблението на батерията."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"достъп до приблизителното местоположение само на преден план"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Приложението може да получава данни за приблизителното ви местоположение само когато работи на преден план. Услугите за местоположение трябва да са включени и налице на устройството ви, за да могат да се използват от приложението."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"достъп до местоположението на заден план"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ако разрешението бъде предоставено в допълнение към достъпа до приблизителното или точното местоположение, приложението може да осъществява достъп до местоположението, докато се изпълнява на заден план."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Това приложение освен на преден план може да осъществява достъп до местоположението, докато работи на заден план."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"промяна на настройките ви за звука"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Разрешава на приложението да променя глобалните настройки за звука, като например силата и това, кой високоговорител се използва за изход."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"записва звук"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Разрешава на приложението да вижда конфигурацията на Bluetooth на таблета и да изгражда и приема връзки със сдвоени устройства."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Дава възможност на приложението да преглежда конфигурацията на Bluetooth на устройството ви с Android TV и да създава и приема връзки със сдвоени устройства."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Разрешава на приложението да вижда конфигурацията на Bluetooth на телефона и да изгражда и приема връзки със сдвоени устройства."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"контролиране на комуникацията в близкото поле"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Разрешава на приложението да комуникира с маркери, карти и четци, ползващи комуникация в близкото поле (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"деактивиране на заключването на екрана ви"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Установена е връзка с <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Докоснете, за да прегледате файловете"</string>
<string name="pin_target" msgid="8036028973110156895">"Фиксиране"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Освобождаване"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Информация за приложението"</string>
<string name="negative_duration" msgid="1938335096972945232">"-<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Демонстрацията се стартира…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Искате ли да актуализирате тези елементи в(ъв) "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> и <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Запазване"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Не, благодаря"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Не сега"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Никога"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Актуализиране"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Напред"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"Паролата"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Превключване на разделения екран"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Заключен екран"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Екранна снимка"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Приложението <xliff:g id="APP_NAME">%1$s</xliff:g> в изскачащ прозорец."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Лента за надписи на <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index e335396..98d5e5d 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"কর্মস্থলের প্রোফাইলের প্রশাসক অ্যাপটি হয় নেই, অথবা সেটি ক্ষতিগ্রস্ত হয়েছে৷ এর ফলে আপনার কর্মস্থলের প্রোফাইল এবং সম্পর্কিত ডেটা মুছে ফেলা হয়েছে৷ সহায়তার জন্য আপনার প্রশাসকের সাথে যোগাযোগ করুন৷"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"আপনার কর্মস্থলের প্রোফাইলটি আর এই ডিভাইসে নেই"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"বহুবার ভুল পাসওয়ার্ড দিয়েছেন"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"ব্যক্তিগত কাজের জন্য অ্যাডমিন এই ডিভাইস ব্যবহার করার অনুমতি দেয়নি"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"ডিভাইসটি পরিচালনা করা হচ্ছে"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"আপনার প্রতিষ্ঠান এই ডিভাইসটি পরিচালনা করে এবং এটির নেটওয়ার্ক ট্রাফিকের উপরে নজর রাখতে পারে। বিশদ বিবরণের জন্য ট্যাপ করুন।,"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"আপনার ডিভাইসটি মুছে ফেলা হবে"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"অ্যাপটিকে স্টিকি সম্প্রচার পাঠানোর অনুমতি দেয়। সম্প্রচার শেষ হয়ে যাওয়ার পরেও সেটি সরে যায় না। এটির অতিরিক্ত ব্যবহার করা হলে, অত্যধিক মেমরি ব্যবহার হওয়ার ফলে আপনার Android TV ডিভাইস স্লো হয়ে যেতে পারে অথবা স্থিতিশীলতা হারাতে পারে।"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"স্টিকি সম্প্রচারগুলি পাঠাতে অ্যাপ্লিকেশানটিকে মঞ্জুর করে, যা সম্প্রচার শেষ হয়ে যাওয়ার পরও উপলব্ধ থাকে৷ খুব বেশি পরিমাণে ব্যবহার করার ফলে ফোনটিকে ধীরগতির করে দিতে পারে অথবা খুব বেশি পরিমাণ মেমরি ব্যবহারের ফলে এটি যথাযথভাবে কাজ নাও করতে পারে৷"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"আপনার পরিচিতিগুলি পড়ুন"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"অ্যাপ্লিকেশনটিকে আপনি নির্দিষ্ট একজন স্বতন্ত্র ব্যক্তির সঙ্গে ফ্রিকোয়েন্সি দিয়ে কল, ইমেল বা যোগাযোগ করেছেন তা সহ আপনার ট্যাবলেটে সঞ্চিত পরিচিতিগুলি সম্পর্কে ডেটা পড়তে অনুমতি দেয়৷ এই অনুমতি অ্যাপ্লিকেশনগুলিকে আপনার পরিচিতি ডেটা সংরক্ষণ করতে দেয় এবং ক্ষতিকারক অ্যাপ্লিকেশনগুলি আপনাকে না জানিয়ে পরিচিতি ডেটা ভাগ করতে পারে৷"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"এই অ্যাপকে Android TV ডিভাইসে পরিচিতি সম্পর্কে সেভ করা ডেটা পড়ার অনুমতি দেয়, এক্ষেত্রে যেকোনও নির্দিষ্ট ব্যক্তিকে আপনি কত ঘন ঘন কল, ইমেল বা তার সাথে অন্য কোনও মাধ্যমে যোগাযোগ করেছেন তাও অন্তর্ভুক্ত। এই অনুমতির পেলে কোনও অ্যাপ আপনার পরিচিতির ডেটা সেভ করতে পারে এবং ক্ষতিকারক অ্যাপ আপনার অজান্তে পরিচিতির ডেটা শেয়ার করতে পারে।"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"অ্যাপ্লিকেশনটিকে আপনি নির্দিষ্ট একজন স্বতন্ত্র ব্যক্তির সঙ্গে ফ্রিকোয়েন্সি দিয়ে কল, ইমেল বা যোগাযোগ করেছেন তা সহ আপনার ফোনে সঞ্চিত পরিচিতিগুলি সম্পর্কে ডেটা পড়তে অনুমতি দেয়৷ এই অনুমতি অ্যাপ্লিকেশনগুলিকে আপনার পরিচিতি ডেটা সংরক্ষণ করতে দেয় এবং ক্ষতিকারক অ্যাপ্লিকেশনগুলি আপনাকে না জানিয়ে পরিচিতি ডেটা ভাগ করতে পারে৷"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"এই অ্যাপকে আপনার ট্যাবলেটে পরিচিতি সম্পর্কে সেভ করা ডেটা পড়ার অনুমতি দেয়। আপনার ট্যাবলেটের যেসব অ্যাকাউন্ট পরিচিতি তৈরি করা হয়েছে, এই অ্যাপ সেই অ্যাকাউন্টও অ্যাক্সেস করতে পারবে। আপনার ইনস্টল করা কোনও অ্যাপ অ্যাকাউন্ট তৈরি করলে এটির মধ্যে সেটিও থাকতে পারে। এই অনুমতি অ্যাপকে আপনার পরিচিতি সংক্রান্ত ডেটা সেভ করতে দেয় এবং ক্ষতিকারক অ্যাপ আপনাকে না জানিয়ে পরিচিতি সংক্রান্ত ডেটা শেয়ার করতে পারে।"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"এই অ্যাপকে Android TV ডিভাইসে পরিচিতি সম্পর্কে সেভ করা ডেটা পড়ার অনুমতি দেয়। আপনার Android TV ডিভাইসে যেসব অ্যাকাউন্টে পরিচিতি তৈরি করা হয়েছে, এই অ্যাপ সেই অ্যাকাউন্টও অ্যাক্সেস করতে পারবে। আপনার ইনস্টল করা কোনও অ্যাপ অ্যাকাউন্ট তৈরি করলে এটির মধ্যে সেটিও থাকতে পারে। এই অনুমতি অ্যাপকে আপনার পরিচিতি সংক্রান্ত ডেটা সেভ করতে দেয় এবং ক্ষতিকারক অ্যাপ আপনাকে না জানিয়ে পরিচিতি সংক্রান্ত ডেটা শেয়ার করতে পারে।"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"এই অ্যাপকে আপনার ফোনে পরিচিতি সম্পর্কে সেভ করা ডেটা পড়ার অনুমতি দেয়। আপনার ফোনের যেসব অ্যাকাউন্ট পরিচিতি তৈরি করা হয়েছে, এই অ্যাপ সেই অ্যাকাউন্টও অ্যাক্সেস করতে পারবে। আপনার ইনস্টল করা কোনও অ্যাপ অ্যাকাউন্ট তৈরি করলে এটির মধ্যে সেটিও থাকতে পারে। এই অনুমতি অ্যাপকে আপনার পরিচিতি সংক্রান্ত ডেটা সেভ করতে দেয় এবং ক্ষতিকারক অ্যাপ আপনাকে না জানিয়ে পরিচিতি সংক্রান্ত ডেটা শেয়ার করতে পারে।"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"আপনার পরিচিতিগুলি সংশোধন করুন"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"অ্যাপ্লিকেশনটিকে আপনি নির্দিষ্ট একজন পরিচিতির সঙ্গে যে ফ্রিকোয়েন্সিতে কল, ইমেল বা যোগাযোগ করেছেন তা সহ আপনার ট্যাবলেটে সঞ্চিত পরিচিতিগুলি সম্পর্কে ডেটা পরিবর্তন করতে অনুমতি দেয়৷ এই অনুমতি অ্যাপ্লিকেশনগুলিকে আপনার পরিচিতি ডেটা মুছতে দেয়৷"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"এই অ্যাপকে Android TV ডিভাইসে পরিচিতি সম্পর্কে সেভ করা ডেটা পরিবর্তন করার অনুমতি দেয়, এক্ষেত্রে নির্দিষ্ট পরিচিতিকে আপনি কত ঘন ঘন কল, ইমেল বা তার সাথে অন্য কোনও মাধ্যমে যোগাযোগ করেছেন তাও অন্তর্ভুক্ত। এই অনুমতি পেলে কোনও অ্যাপ আপনার পরিচিতির ডেটা মুছে ফেলতে পারে।"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"অ্যাপ্লিকেশনটিকে আপনি নির্দিষ্ট একজন পরিচিতির সঙ্গে যে ফ্রিকোয়েন্সিতে কল, ইমেল বা যোগাযোগ করেছেন তা সহ আপনার ফোনে সঞ্চিত পরিচিতিগুলি সম্পর্কে ডেটা পরিবর্তন করতে অনুমতি দেয়৷ এই অনুমতি অ্যাপ্লিকেশনগুলিকে আপনার পরিচিতি ডেটা মুছতে দেয়৷"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"এই অ্যাপকে আপনার ট্যাবলেটে পরিচিতি সম্পর্কে সেভ করা ডেটা পরিবর্তন করার অনুমতি দেয়। এই অনুমতি অ্যাপকে যোগাযোগ সংক্রান্ত ডেটা মুছতে দেয়।"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"এই অ্যাপকে Android TV ডিভাইসে পরিচিতি সম্পর্কে সেভ করা ডেটা পরিবর্তন করার অনুমতি দেয়। এই অনুমতি অ্যাপকে যোগাযোগ সংক্রান্ত ডেটা মুছতে দেয়।"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"এই অ্যাপকে আপনার ফোনে পরিচিতি সম্পর্কে সেভ করা ডেটা পরিবর্তন করার অনুমতি দেয়। এই অনুমতি অ্যাপকে যোগাযোগ সংক্রান্ত ডেটা মুছতে দেয়।"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"কল লগ পড়ুন"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"এই অ্যাপটি আপনার কলের ইতিহাস পড়তে পারে৷"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"কল লগ লিখুন"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"অতিরিক্ত লোকেশন প্রদানকারী কমান্ডগুলি অ্যাক্সেস করে"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"লোকেশনের সাথে সম্পর্কিত তথ্য প্রদানকারীর অতিরিক্ত কম্যান্ডগুলিকে অ্যাপ্লিকেশানটিকে মঞ্জুর করে৷ এটি অ্যাপ্লিকেশানটিকে GPS অথবা অন্যান্য লোকেশন নির্ণয়ের সাথে সম্পর্কিত উৎসগুলির ক্রিয়াপ্রণালীর নিয়ন্ত্রণকে মঞ্জুর করতে পারে৷"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"শুধুমাত্র অ্যাপটি খোলা থাকলে আপনার যথাযথ লোকেশন অ্যাক্সেস করা"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"এই অ্যাপটি ফোরগ্রাউন্ডে চলতে থাকলে যেকোনও সময়ে আপনার যথাযথ লোকেশন জানতে পারবে। এই লোকেশন পরিষেবাগুলি অবশ্যই চালু রাখতে হবে এবং আপনার ফোনে সেগুলি উপলভ্য থাকতে হবে যাতে অ্যাপটি সেগুলি ব্যবহার করতে পারে। এর জন্য অতিরিক্ত ব্যাটারি খরচ হতে পারে।"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"শুধুমাত্র খোলা অবস্থায় আনুমানিক লোকেশন (নেটওয়ার্ক ভিত্তিক) অ্যাক্সেস করবে"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"অ্যাপটি শুধুমাত্র খোলা অবস্থায় সেল টাওয়ার এবং ওয়াই-ফাইয়ের মতো নেটওয়ার্ক সোর্স ব্যবহার করে আপনার লোকেশন জানতে পারবে। লোকেশন সংক্রান্ত এই পরিষেবাগুলি অবশ্যই চালু থাকতে হবে এবং আপনার ট্যাবলেটে সেগুলি উপলভ্য থাকতে হবে যাতে অ্যাপটি সেগুলি ব্যবহার করতে পারে।"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"এই অ্যাপ ফোরগ্রাউন্ডে থাকার সময় সেল টাওয়ার ও ওয়াই-ফাই নেটওয়ার্কের মতো নেটওয়ার্ক সোর্স ব্যবহার করে আপনার লোকেশন জানতে পারে। অ্যাপটিকে সেটি ব্যবহার করতে হলে Android TV ডিভাইসে এই লোকেশন পরিষেবা চালু ও উপলভ্য থাকতে হবে।"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"অ্যাপটি শুধুমাত্র খোলা অবস্থায় সেল টাওয়ার এবং ওয়াই-ফাইয়ের মতো নেটওয়ার্ক সোর্স ব্যবহার করে আপনার লোকেশন জানতে পারবে। লোকেশন সংক্রান্ত এই পরিষেবাগুলি অবশ্যই চালু থাকতে হবে এবং আপনার ফোনে সেগুলি উপলভ্য থাকতে হবে যাতে অ্যাপটি সেগুলি ব্যবহার করতে পারে।"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"এই অ্যাপটি ফোরগ্রাউন্ডে চলতে থাকলে যেকোনও সময়ে আপনার যথাযথ লোকেশন জানতে পারবে। তাছাড়াও লোকেশন পরিষেবাগুলি অবশ্যই চালু রাখতে হবে এবং আপনার ডিভাইসে সেগুলি উপলভ্য থাকতে হবে যাতে অ্যাপটি সেগুলি ব্যবহার করতে পারে। এর জন্য অতিরিক্ত ব্যাটারি খরচ হতে পারে।"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"শুধুমাত্র অ্যাপটি খোলা থাকলে আপনার আনুমানিক লোকেশন অ্যাক্সেস করা"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"এই অ্যাপটি যদি ফোরগ্রাউন্ডে চলতে থাকে তবেই শুধুমাত্র আপনার আনুমানিক লোকেশন জানতে পারবে। আপনার ডিভাইসে লোকেশন পরিষেবা চালু ও উপলভ্য থাকতে হবে, তবেই অ্যাপটি সেগুলি ব্যবহার করতে পারবে।"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"ব্যাকগ্রাউন্ডে লোকেশন অ্যাক্সেস করা"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"আনুমানিক বা একদম যথাযথ লোকেশন অ্যাক্সেস করার অনুমতি দেওয়া হলে এই অ্যাপটি ব্যাকগ্রাউন্ডে চালু থাকাকালীন আপনার লোকেশন অ্যাক্সেস করতে পারবে।"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"ফোরগ্রাউন্ড লোকেশন অ্যাক্সেস করা ছাড়াও, ব্যাকগ্রাউন্ডে চলাকালীন এই অ্যাপটি লোকেশন অ্যাক্সেস করতে পারে।"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"আপনার অডিও সেটিংস পরিবর্তন করে"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ভলিউম এবং যেখানে স্পিকার আউটপুট হিসাবে ব্যবহৃত হয় সেই সব ক্ষেত্রে গ্লোবাল অডিও সেটিংসের সংশোধন করতে অ্যাপ্লিকেশনটিকে মঞ্জুর করে৷"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"অডিও রেকর্ড"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ট্যাবলেটের ব্লুটুথ কনফিগারেশন দেখতে, এবং যুক্ত ডিভাইসগুলির সাথে সংযোগ স্থাপন এবং সংযোগের অনুরোধ স্বীকার করতে অ্যাপ্লিকেশানটিকে মঞ্জুর করে৷"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"আপনার Android TV ডিভাইসের ব্লুটুথের কনফিগারেশন দেখার এবং পেয়ার করা ডিভাইসের সাথে কানেক্ট করার বা কানেকশন গ্রহণ করার অনুমতি দেয়।"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ফোনের ব্লুটুথ কনফিগারেশন দেখতে, এবং যুক্ত ডিভাইসগুলির সাথে সংযোগ স্থাপন এবং সংযোগের অনুরোধ স্বীকার করতে অ্যাপ্লিকেশানটিকে মঞ্জুর করে৷"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"নিয়ার ফিল্ড কমিউনিকেশন নিয়ন্ত্রণ করে"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"অ্যাপ্লিকেশানকে নিয়ার ফিল্ড কমিউনিকেশন (NFC) ট্যাগ, কার্ড এবং রিডারগুলির সাথে যোগাযোগ করতে দেয়৷"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"আপনার স্ক্রিন লক অক্ষম করুন"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> এর সাথে সংযুক্ত হয়েছে"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ফাইলগুলি দেখতে আলতো চাপ দিন"</string>
<string name="pin_target" msgid="8036028973110156895">"পিন করুন"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"আনপিন করুন"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"অ্যাপের তথ্য"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ডেমো শুরু করা হচ্ছে…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"-এ এই আইটেমগুলি আপডেট করতে চান: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> এবং <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"সেভ করুন"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"না থাক"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"এখনই নয়"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"কখনই নয়"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"আপডেট করুন"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"চালিয়ে যান"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"পাসওয়ার্ড"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"স্প্লিট স্ক্রিন টগল করুন"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"লক স্ক্রিন"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"স্ক্রিনশট"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"পপ-আপ উইন্ডোতে <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপ।"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>-এর ক্যাপশন বার।"</string>
</resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index d7bd7a1..58601b5 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -190,8 +190,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Nedostaje aplikacija administratora za radni profil ili je neispravna. Zbog toga su vaš radni profil i povezani podaci izbrisani. Obratite administratoru za pomoć."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Radni profil više nije dostupan na ovom uređaju"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Previše puta ste pokušali otključati uređaj"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator je ustupio uređaj za ličnu upotrebu"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Uređajem se upravlja."</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Vaša organizacija upravlja ovim uređajem i može pratiti mrežni saobraćaj. Dodirnite za detalje."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Uređaj će biti izbrisan"</string>
@@ -384,13 +383,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Omogućava aplikaciji slanje ljepljivih informacija koje ostaju nakon prestanka emitiranja. Pretjeranom upotrebom može se usporiti ili destabilizirati rad Android TV uređaja zbog korištenja previše memorije."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Omogućava aplikaciji slanje ljepljivih informacija koje ostaju nakon prestanka emitiranja. Njihova pretjerana upotreba može usporiti ili destabilizirati rad telefona jer troši previše memorije."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"čitanje vaših kontakata"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Omogućava aplikaciji čitanje podataka o kontaktima koji su pohranjeni na vašem tabletu, uključujući učestalost vaših poziva, slanja e-pošte ili nekog drugog vida komunikacije sa određenim pojedincima. Ovo odobrenje omogućava aplikacijama da pohrane podatke o vašim kontaktima tako da ih zlonamjerne aplikacije mogu podijeliti bez vašeg znanja."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Omogućava aplikaciji da čita podatke o vašim kontaktima pohranjenim na Android TV uređaju, uključujući učestalost poziva, slanja e-poruka ili komuniciranja na bilo koji način s određenim osobama. Ovo odobrenje omogućava aplikacijama da pohrane podatke o vašim kontaktima, a zlonamjerne aplikacije mogu bez vašeg znanja podijeliti ove podatke."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Omogućava aplikaciji čitanje podataka o kontaktima koji su pohranjeni na vašem telefonu, uključujući učestalost vaših poziva, slanja e-pošte ili nekog drugog vida komunikacije sa određenim pojedincima. Ovo odobrenje omogućava aplikacijama da pohrane podatke o vašim kontaktima tako da ih zlonamjerne aplikacije mogu podijeliti bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Dozvoljava aplikaciji da čita podatke o kontaktima pohranjenim na tabletu. Aplikacije će također imati pristup računima na tabletu koji su kreirali kontakte. Ovo može uključivati račune koje su kreirale aplikacije koje ste instalirali. Ovo odobrenje dozvoljava aplikacijama da sačuvaju podatke o kontaktima, a zlonamjerne aplikacije mogu dijeliti te podatke bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Dozvoljava aplikaciji da čita podatke o kontaktima pohranjenim na Android TV uređaju. Aplikacije će također imati pristup računima na Android TV uređaju koji su kreirali kontakte. Ovo može uključivati račune koje su kreirale aplikacije koje ste instalirali. Ovo odobrenje dozvoljava aplikacijama da sačuvaju podatke o kontaktima, a zlonamjerne aplikacije mogu dijeliti te podatke bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Dozvoljava aplikaciji da čita podatke o kontaktima pohranjenim na telefonu. Aplikacije će također imati pristup računima na telefonu koji su kreirali kontakte. Ovo može uključivati račune koje su kreirale aplikacije koje ste instalirali. Ovo odobrenje dozvoljava aplikacijama da sačuvaju podatke o kontaktima, a zlonamjerne aplikacije mogu dijeliti te podatke bez vašeg znanja."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"izmjena podataka o kontaktima"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Omogućava aplikaciji da izmijeni podatke o kontaktima koji su pohranjeni na vašem tabletu, uključujući učestalost vaših poziva, slanje e-pošte, ili neki drugi vid komunikacije sa određenim kontaktima. Ovo odobrenje omogućava aplikaciji da obriše podatke o kontaktima."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Omogućava aplikaciji izmjenu podataka o vašim kontaktima pohranjenim na Android TV uređaju, uključujući učestalost poziva, slanja e-poruka ili komuniciranja na bilo koji način s određenim kontaktima. Ovom dozvolom se aplikacijama omogućava brisanje podataka o kontaktima."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Omogućava aplikaciji da izmijeni podatke o kontaktima koji su pohranjeni na vašem telefonu, uključujući učestalost vaših poziva, slanje e-pošte, ili neki drugi vid komunikacije sa određenim kontaktima. Ovo odobrenje omogućava aplikaciji da izbriše podatke o kontaktima."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Dozvoljava aplikaciji da vrši izmjene podataka o kontaktima pohranjenim na tabletu. Ovo odobrenje dozvoljava aplikacijama da izbrišu podatke o kontaktima."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Dozvoljava aplikaciji da vrši izmjene podataka o kontaktima pohranjenim na Android TV uređaju. Ovo odobrenje dozvoljava aplikacijama da izbrišu podatke o kontaktima."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Dozvoljava aplikaciji da vrši izmjene podataka o kontaktima pohranjenim na telefonu. Ovo odobrenje dozvoljava aplikacijama da izbrišu podatke o kontaktima."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"čitanje zapisnika poziva"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Ova aplikacija može čitati historiju vaših poziva."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"pisanje zapisnika poziva"</string>
@@ -410,13 +409,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"pristup dodatnim informacijama o lokaciji"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Dozvoljava aplikaciji pristup dodatnim naredbama pružatelja lokacija. Ovim se aplikaciji može dozvoliti da ometa rad GPS-a ili drugih izvora lokacija."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"pristup tačnoj lokaciji samo u prvom planu"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Ova aplikacija može odrediti vašu tačnu lokaciju samo kada je u prvom planu. Ove usluge lokacije moraju biti uključene i dostupne na vašem telefonu da ih aplikacija može koristiti. To može dovesti do povećane potrošnje baterije."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"pristup približnoj lokaciji (utvrđena preko mreže) samo u prvom planu"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ova aplikacija može odrediti vašu lokaciju na osnovu izvora mreže kao što su predajnici za mobilnu mrežu i WiFi mreže ali samo kada je aplikacija u prvom planu. Ove usluge za određivanje lokacije moraju biti uključene i omogućene na vašem tabletu kako bi ih aplikacija mogla koristiti."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ova aplikacija može odrediti vašu lokaciju na osnovu izvora mreže kao što su predajnici za mobilnu mrežu i WiFi mreže, ali samo kada je aplikacija u prvom planu. Te usluge za određivanje lokacije moraju biti uključene i omogućene na vašem Android TV uređaju kako bi ih aplikacija mogla koristiti."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ova aplikacija može odrediti vašu lokaciju na osnovu izvora mreže kao što su predajnici za mobilnu mrežu i WiFi mreže ali samo kada je aplikacija u prvom planu. Ove usluge za određivanje lokacije moraju biti uključene i omogućene na vašem telefonu kako bi ih aplikacija mogla koristiti."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ova aplikacija može odrediti vašu tačnu lokaciju samo kada je u prvom planu. Usluge lokacije moraju biti uključene i dostupne na uređaju da ih aplikacija može koristiti. To može dovesti do povećane potrošnje baterije."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"pristup približnoj lokaciji samo u prvom planu"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ova aplikacija može odrediti vašu približnu lokaciju samo kada je u prvom planu. Usluge lokacije moraju biti uključene i dostupne na uređaju da ih aplikacija može koristiti."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"pristup lokaciji u pozadini"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ako je ovo odobreno, pored pristupa približnoj ili tačnoj lokaciji, aplikacija može pristupiti lokaciji dok radi u pozadini."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ova aplikacija može pristupati lokaciji dok radi u pozadini, pored pristupa lokaciji u prvom planu."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"izmjene postavki zvuka"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Omogućava aplikaciji izmjenu općih postavki zvuka, kao što su jačina zvuka i izbor izlaznog zvučnika."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje audiozapisa"</string>
@@ -497,6 +494,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Omogućava aplikaciji prikaz konfiguracije za Bluetooth na tabletu, kao i uspostavljanje i prihvatanje veza sa uparenim uređajima."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Omogućava aplikaciji da prikaže konfiguraciju Bluetootha na Android TV uređaju te uspostavi i prihvati vezu s uparenim uređajima."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Omogućava aplikaciji prikaz konfiguracije za Bluetooth na telefonu, kao i uspostavljanje i prihvatanje veza sa uparenim uređajima."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"upravljanje NFC-om"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Dozvoljava aplikaciji komuniciranje sa NFC (komunikacija bliskog polja) oznakama, karticama i čitačima."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktivacija zaključavanja ekrana"</string>
@@ -1896,7 +1897,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Povezan na uređaj <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Dodirnite za prikaz fajlova"</string>
<string name="pin_target" msgid="8036028973110156895">"Zakači"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Otkači"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informacije o aplikaciji"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Pokretanje demonstracije…"</string>
@@ -1940,6 +1945,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Ažurirati ove stavke u "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> i <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Sačuvaj"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ne, hvala"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ne sada"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nikada"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Ažuriraj"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Nastavi"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"lozinka"</string>
@@ -2036,5 +2043,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Uključi/isključi podijeljeni ekran"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Zaključavanje ekrana"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Snimak ekrana"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u skočnom prozoru."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Traka za natpis aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 1f7d82e..6e758aa 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta l\'aplicació d\'administració del perfil professional o està malmesa. Com a conseqüència, s\'han suprimit el teu perfil professional i les dades relacionades. Contacta amb l\'administrador per obtenir ajuda."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"El teu perfil professional ja no està disponible en aquest dispositiu"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Has intentat introduir la contrasenya massa vegades"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrador ha cedit el dispositiu per a ús personal"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"El dispositiu està gestionat"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"La teva organització gestiona aquest dispositiu i és possible que supervisi el trànsit de xarxa. Toca per obtenir més informació."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"El contingut del dispositiu s\'esborrarà"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permet que l\'aplicació enviï emissions fixes, que es conserven després de finalitzar l\'emissió. L\'ús excessiu pot provocar que el dispositiu Android TV utilitzi massa memòria i s\'alenteixi o es desestabilitzi."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permet que l\'aplicació enviï emissions permanents, que es conserven després de finalitzar l\'emissió. L\'ús excessiu pot alentir o desestabilitzar el telèfon si li fan utilitzar massa memòria."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"consultar els contactes"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permet que l\'aplicació llegeixi dades sobre els contactes que tinguis emmagatzemats a la tauleta, inclosa la freqüència amb què has trucat, has enviat correus electrònics o t\'has comunicat d\'altres maneres amb persones concretes. Aquest permís permet que les aplicacions desin les dades dels teus contactes, i és possible que les aplicacions malicioses comparteixin dades dels contactes sense el teu coneixement."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permet que l\'aplicació llegeixi dades sobre els contactes emmagatzemades al dispositiu Android TV, inclosa la freqüència amb què has trucat a contactes concrets, els has enviat correus electrònics o t\'hi has comunicat d\'altres maneres. Aquest permís permet que les aplicacions desin les dades dels teus contactes; és possible que les aplicacions malicioses comparteixin dades dels contactes sense que ho sàpigues."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permet que l\'aplicació llegeixi dades sobre els contactes que tinguis emmagatzemats al telèfon, inclosa la freqüència amb què has trucat, has enviat correus electrònics o t\'has comunicat d\'altres maneres amb persones concretes. Aquest permís permet que les aplicacions desin les dades dels teus contactes, i és possible que les aplicacions malicioses comparteixin dades dels contactes sense el teu coneixement."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permet que l\'aplicació llegeixi les dades dels contactes que tens emmagatzemats a la tauleta. Les aplicacions també tindran accés als comptes de la tauleta que hagin creat contactes. Això pot incloure els comptes creats per les aplicacions que hi tens instal·lades. Les aplicacions amb aquest permís poden desar les dades dels teus contactes, i és possible que les aplicacions malicioses comparteixin dades dels contactes sense el teu coneixement."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permet que l\'aplicació llegeixi les dades dels contactes que tens emmagatzemats al dispositiu Android TV. Les aplicacions també tindran accés als comptes del teu dispositiu Android TV que hagin creat contactes. Això pot incloure els comptes creats per les aplicacions que hi tens instal·lades. Les aplicacions amb aquest permís poden desar les dades dels teus contactes, i és possible que les aplicacions malicioses comparteixin dades dels contactes sense el teu coneixement."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permet que l\'aplicació llegeixi les dades dels contactes que tens emmagatzemats al telèfon. Les aplicacions també tindran accés als comptes del telèfon que hagin creat contactes. Això pot incloure els comptes creats per les aplicacions que hi tens instal·lades. Les aplicacions amb aquest permís poden desar les dades dels teus contactes, i és possible que les aplicacions malicioses comparteixin dades dels contactes sense el teu coneixement."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modificar els teus contactes"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permet que l\'aplicació modifiqui les dades sobre contactes emmagatzemades a la tauleta, inclosa la freqüència amb què has trucat, has enviat correus electrònics o t\'has comunicat d\'altres maneres amb contactes concrets. Aquest permís permet que les aplicacions suprimeixin dades de contactes."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permet que l\'aplicació modifiqui les dades sobre els contactes emmagatzemades al dispositiu Android TV, com ara la freqüència amb què has trucat a contactes concrets, els has enviat correus electrònics o t\'hi has comunicat d\'altres maneres. Amb aquest permís, les aplicacions poden suprimir les dades dels contactes."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permet que l\'aplicació modifiqui les dades sobre contactes emmagatzemades al telèfon, inclosa la freqüència amb què has trucat, has enviat correus electrònics o t\'has comunicat d\'altres maneres amb contactes concrets. Aquest permís permet que les aplicacions suprimeixin dades de contactes."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permet que l\'aplicació modifiqui les dades dels contactes que tens emmagatzemats a la tauleta. Amb aquest permís, les aplicacions poden suprimir les dades dels contactes."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permet que l\'aplicació modifiqui les dades dels contactes que tens emmagatzemats al dispositiu Android TV. Amb aquest permís, les aplicacions poden suprimir les dades dels contactes."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permet que l\'aplicació modifiqui les dades dels contactes que tens emmagatzemats al telèfon. Amb aquest permís, les aplicacions poden suprimir les dades dels contactes."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lectura del registre de trucades"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Aquesta aplicació pot llegir el teu historial de trucades."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"escriptura del registre de trucades"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"accedir a ordres del proveïdor d\'ubicació addicionals"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permet que l\'aplicació accedeixi a ordres addicionals del proveïdor d\'ubicacions; per tant, és possible que l\'aplicació pugui interferir en el funcionament del GPS o d\'altres fonts d\'ubicacions."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"accedeix a la ubicació exacta només en primer pla"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Aquesta aplicació pot obtenir la teva ubicació exacta només quan està en primer pla. Aquests serveis d\'ubicació han d\'estar activats i disponibles al telèfon perquè l\'aplicació els pugui utilitzar, i això pot fer que el consum de bateria augmenti."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"accedeix a la ubicació aproximada (basada en xarxa) només en primer pla"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Aquesta aplicació pot obtenir la teva ubicació a partir de fonts de xarxa, com ara torres de telefonia mòbil i xarxes Wi‑Fi, però només quan està en primer pla. Aquests serveis d\'ubicació han d\'estar activats i disponibles a la tauleta perquè l\'aplicació els pugui utilitzar."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Aquesta aplicació pot obtenir la teva ubicació a partir de fonts de xarxa, com ara torres de telefonia mòbil i xarxes Wi‑Fi, però només quan està en primer pla. Aquests serveis d\'ubicació han d\'estar activats i disponibles al dispositiu Android TV perquè l\'aplicació els pugui utilitzar."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Aquesta aplicació pot obtenir la teva ubicació a partir de fonts de xarxa, com ara torres de telefonia mòbil i xarxes Wi‑Fi, però només quan està en primer pla. Aquests serveis d\'ubicació han d\'estar activats i disponibles al telèfon perquè l\'aplicació els pugui utilitzar."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Aquesta aplicació pot obtenir la teva ubicació exacta només quan està en primer pla. Els serveis d\'ubicació han d\'estar activats i disponibles al dispositiu perquè l\'aplicació els pugui utilitzar, i això pot fer que el consum de bateria augmenti."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"accedeix a la ubicació aproximada només en primer pla"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Aquesta aplicació pot obtenir la teva ubicació aproximada només quan està en primer pla. Els serveis d\'ubicació han d\'estar activats i disponibles al dispositiu perquè l\'aplicació els pugui utilitzar."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"accedir a la ubicació en segon pla"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Si es concedeix aquest permís, a més de l\'accés a la ubicació aproximada o exacta, l\'aplicació pot accedir a la ubicació mentre s\'executa en segon pla."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Aquesta aplicació pot accedir a la ubicació mentre s\'executa en segon pla, a més de poder fer-ho en primer pla."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"canviar la configuració d\'àudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet que l\'aplicació modifiqui la configuració d\'àudio general, com ara el volum i l\'altaveu de sortida que es fa servir."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar àudio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permet que l\'aplicació visualitzi la configuració del Bluetooth de la tauleta i que estableixi i accepti connexions amb dispositius sincronitzats."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permet que l\'aplicació visualitzi la configuració del Bluetooth del dispositiu Android TV i que estableixi i accepti connexions amb dispositius vinculats."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permet que una aplicació visualitzi la configuració del Bluetooth del telèfon i que estableixi i accepti connexions amb els dispositius sincronitzats."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controlar Comunicació de camp proper (NFC)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permet que l\'aplicació es comuniqui amb les etiquetes, les targetes i els lectors de Comunicació de camp proper (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"desactivació del bloqueig de pantalla"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"S\'ha connectat a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Toca per veure els fitxers"</string>
<string name="pin_target" msgid="8036028973110156895">"Fixa"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"No fixis"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informació de l\'aplicació"</string>
<string name="negative_duration" msgid="1938335096972945232">"-<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"S\'està iniciant la demostració…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Vols actualitzar <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> i <xliff:g id="TYPE_2">%3$s</xliff:g> a "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Desa"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No, gràcies"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ara no"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Mai"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Actualitza"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continua"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"contrasenya"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Commuta Pantalla dividida"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Pantalla de bloqueig"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Captura de pantalla"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplicació <xliff:g id="APP_NAME">%1$s</xliff:g> a la finestra emergent."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de títol de l\'aplicació <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index e05f299..9dbefad 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplikace pro správu pracovního profilu chybí nebo je poškozena. Váš pracovní profil a související data proto byla smazána. Požádejte o pomoc administrátora."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Váš pracovní profil v tomto zařízení již není k dispozici"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Příliš mnoho pokusů o zadání hesla"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrátor zařízení uvolnil k osobnímu používání"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Zařízení je spravováno"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Toto zařízení je spravováno vaší organizací, která může sledovat síťový provoz. Podrobnosti zobrazíte klepnutím."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Zařízení bude vymazáno"</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Umožňuje aplikaci odesílat trvalá vysílání, která přetrvávají i po skončení. Nadměrné používání může zařízení Android TV zpomalit či způsobit jeho nestabilitu, protože bude používat příliš mnoho paměti."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Umožňuje aplikaci odesílat trvalá vysílání, která přetrvávají i po skončení vysílání. Nadměrné používání může telefon zpomalit či způsobit jeho nestabilitu, protože bude používat příliš mnoho paměti."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"čtení kontaktů"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Umožňuje aplikaci číst údaje o kontaktech uložených v tabletu, včetně toho, jak často voláte, posíláte e‑maily nebo jinak komunikujete s konkrétními osobami. Toto oprávnění umožňuje aplikacím ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Umožňuje aplikaci číst údaje o kontaktech uložených v zařízení Android TV, včetně toho, jak často voláte, posíláte e‑maily nebo jinými způsoby komunikujete s konkrétními osobami. Toto oprávnění aplikacím umožňuje ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Umožňuje aplikaci číst údaje o kontaktech uložených v telefonu, včetně toho, jak často voláte, posíláte e‑maily nebo komunikujete jinými způsoby s konkrétními osobami. Toto oprávnění umožňuje aplikacím ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Umožňuje aplikaci číst údaje o kontaktech uložené v tabletu. Aplikace také budou mít přístup k účtům v tabletu, pomocí kterých byly vytvořeny kontakty. Může se jednat i o účty vytvořené aplikacemi, které jste nainstalovali. Toto oprávnění aplikacím umožňuje ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Umožňuje aplikaci číst údaje o kontaktech uložené v zařízení Android TV. Aplikace také budou mít přístup k účtům v zařízení Android TV, pomocí kterých byly vytvořeny kontakty. Může se jednat i o účty vytvořené aplikacemi, které jste nainstalovali. Toto oprávnění aplikacím umožňuje ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Umožňuje aplikaci číst údaje o kontaktech uložené v telefonu. Aplikace také budou mít přístup k účtům v telefonu, pomocí kterých byly vytvořeny kontakty. Může se jednat i o účty vytvořené aplikacemi, které jste nainstalovali. Toto oprávnění aplikacím umožňuje ukládat údaje o kontaktech. Škodlivé aplikace mohou tyto údaje bez vašeho vědomí sdílet."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"úprava kontaktů"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Umožňuje aplikaci upravit údaje o kontaktech uložených v tabletu včetně toho, jak často voláte, posíláte e‑maily nebo komunikujete jinými způsoby s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Umožňuje aplikaci upravit údaje o kontaktech uložených v zařízení Android TV včetně toho, jak často voláte, posíláte e‑maily nebo komunikujete jinými způsoby s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Umožňuje aplikaci upravit údaje o kontaktech uložených v telefonu včetně toho, jak často voláte, posíláte e‑maily nebo komunikujete jinými způsoby s konkrétními kontakty. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Umožňuje aplikaci upravovat údaje o kontaktech uložené v tabletu. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Umožňuje aplikaci upravovat údaje o kontaktech uložené v zařízení Android TV. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Umožňuje aplikaci upravovat údaje o kontaktech uložení v telefonu. Toto oprávnění aplikacím umožňuje mazat údaje o kontaktech."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"čtení seznamu hovorů"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Tato aplikace může číst historii volání."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"zápis do seznamu hovorů"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"přístup k dalším příkazům poskytovatele polohy"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Umožňuje aplikaci přístup k dalším příkazům poskytovatele polohy. To aplikaci umožní zasahovat do fungování systému GPS a dalších zdrojů polohy."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"přístup k přesné poloze jen na popředí"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Tato aplikace může zjistit vaši přesnou polohu, jen když běží na popředí. Aby tyto služby určování polohy mohla aplikace používat, musí být v telefonu dostupné a musí být zapnuté. Tyto služby mohou způsobit rychlejší vybíjení baterie."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"přístup k přibližené poloze (na základě sítě) jen na popředí"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Tato aplikace může zjistit vaši polohu podle zdrojů sítě, jako jsou vysílací věže nebo sítě Wi-Fi (ale pouze pokud je aplikace na popředí). Aby tyto služby určování polohy mohla aplikace používat, musí být v tabletu dostupné a musí být zapnuté."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Tato aplikace může zjistit vaši polohu podle zdrojů sítě, jako jsou vysílací věže nebo sítě Wi-Fi (ale pouze pokud je aplikace na popředí). Aby tyto služby určování polohy mohla aplikace používat, musí být v zařízení Android TV dostupné a musí být zapnuté."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Tato aplikace může zjistit vaši polohu podle zdrojů sítě, jako jsou vysílací věže nebo sítě Wi-Fi (ale pouze pokud je aplikace na popředí). Aby tyto služby určování polohy mohla aplikace používat, musí být v telefonu dostupné a musí být zapnuté."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Tato aplikace může zjistit vaši přesnou polohu, jen když běží na popředí. Aby aplikace mohla služby určování polohy používat, musí být v zařízení dostupné a musí být zapnuté. To může vést k rychlejšímu vybíjení baterie."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"přístup k přibližné poloze jen na popředí"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Tato aplikace může zjistit vaši přibližnou polohu, jen když běží na popředí. Aby aplikace mohla služby určování polohy používat, musí být v zařízení dostupné a musí být zapnuté."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"přístup k poloze na pozadí"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Bude-li oprávnění uděleno dodatečně k přístupu k přibližné nebo přesné poloze, aplikace bude moci používat polohu při spuštění na pozadí."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Tato aplikace má přístup k poloze, nejen když je v popředí, ale i když běží na pozadí."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"změna nastavení zvuku"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či reproduktor pro výstup zvuku."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"nahrávání zvuku"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Umožňuje aplikaci zobrazit konfiguraci tabletu s rozhraním Bluetooth, vytvářet připojení ke spárovaným zařízením a přijímat tato připojení."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Umožňuje aplikaci zobrazit konfiguraci rozhraním Bluetooth na zařízení Android TV a vytvářet a přijímat připojení ke spárovaným zařízením."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Umožňuje aplikaci zobrazit konfiguraci telefonu s rozhraním Bluetooth, vytvářet připojení ke spárovaným zařízením a přijímat tato připojení."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ovládání technologie NFC"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Umožňuje aplikaci komunikovat se štítky, kartami a čtečkami s podporou technologie NFC."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"vypnutí zámku obrazovky"</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Připojeno k zařízení <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Klepnutím zobrazíte soubory"</string>
<string name="pin_target" msgid="8036028973110156895">"Připnout"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Odepnout"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"O aplikaci"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Spouštění ukázky…"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Aktualizovat tyto položky ve službě "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> a <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Uložit"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ne, děkuji"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Teď ne"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nikdy"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Aktualizovat"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Pokračovat"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"heslo"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Přepnout rozdělenou obrazovku"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Obrazovka uzamčení"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Snímek obrazovky"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> ve vyskakovacím okně."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Popisek aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 772b7b0..151becd 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Administrationsappen til arbejdsprofilen mangler eller er beskadiget. Derfor er din arbejdsprofil og dine relaterede data blevet slettet. Kontakt din administrator for at få hjælp."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Din arbejdsprofil er ikke længere tilgængelig på denne enhed"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"For mange mislykkede adgangskodeforsøg"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratoren har gjort personlig brug af enheden utilgængelig"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Dette er en administreret enhed"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Din organisation administrerer denne enhed og kan overvåge netværkstrafik. Tryk for at se info."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Enheden slettes"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Tillader, at appen kan sende klæbende udsendelser, der forbliver, når udsendelsen er slut. Overdreven brug kan gøre din Android TV-enhed langsom eller ustabil, da det tvinger den til at bruge for meget hukommelse."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Tillader, at appen kan sende klæbende udsendelser, der forbliver tilbage, når udsendelsen er slut. Overdreven brug kan gøre din telefon langsom eller ustabil ved at tvinge den til at bruge for meget hukommelse."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"læse dine kontakter"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Tillader, at appen kan læse data om de kontakter, der er gemt på din tablet, f.eks. hvor ofte du har ringet til, sendt mail til eller på anden måde kommunikeret med bestemte personer. Med denne tilladelse kan apps gemme dine kontaktdata, og skadelige apps kan dele kontaktdata uden din viden."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Tillader, at appen kan læse gemte data på din Android TV-enhed om dine kontakter, bl.a. hvor ofte du har ringet til dem, sendt mail til dem eller på anden måde kommunikeret med bestemte personer. Denne tilladelse giver apps mulighed for at gemme dine kontaktdata, og skadelige apps kan dele kontaktdata uden din viden."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Tillader, at appen kan læse data om de kontakter, der er gemt på din telefon, f.eks. hvor ofte du har ringet til, sendt mail til eller på anden måde kommunikeret med bestemte personer. Med denne tilladelse kan apps gemme dine kontaktdata, og skadelige apps kan dele kontaktdata uden din viden."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tillader, at appen kan læse data om de kontakter, der er gemt på din tablet. Apps får også adgang til de konti på din tablet, som har oprettet kontakter. Dette kan omfatte konti, som er oprettet af apps, du har installeret. Med denne tilladelse kan apps gemme dine kontaktdata, og skadelige apps kan dele kontaktdata uden din viden."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Tillader, at appen kan læse data om de kontakter, der er gemt på din Android TV-enhed. Apps får også adgang til de konti på din Android TV-enhed, som har oprettet kontakter. Dette kan omfatte konti, som er oprettet af apps, du har installeret. Med denne tilladelse kan apps gemme dine kontaktdata, og skadelige apps kan dele kontaktdata uden din viden."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Tillader, at appen læser data om de kontakter, der er gemt på din telefon. Apps får også adgang til de konti på din telefon, som har oprettet kontakter. Dette kan omfatte konti, som er oprettet af apps, du har installeret. Med denne tilladelse kan apps gemme dine kontaktdata, og skadelige apps kan dele kontaktdata uden din viden."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ændre dine kontakter"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Tillader, at appen kan ændre data om de kontakter, der er gemt på din tablet, f.eks. hvor ofte du har ringet til dem, sendt dem en mail eller på anden måde kommunikeret med bestemte kontakter. Med denne tilladelse kan apps slette kontaktoplysninger."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Tillader, at appen kan ændre data om de kontakter, der er gemt på din Android TV-enhed, bl.a. hvor ofte du har ringet til dem, sendt en mail til dem eller på anden måde kommunikeret med bestemte kontakter. Med denne tilladelse kan apps slette kontaktoplysninger."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Tillader, at appen kan ændre data om de kontakter, der er gemt på din telefon, f.eks. hvor ofte du har ringet til dem, sendt en mail til dem eller på anden måde kommunikeret med bestemte kontakter. Med denne tilladelse kan apps slette kontaktoplysninger."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tillader, at appen kan ændre data om de kontakter, der er gemt på din tablet. Denne tilladelse giver apps mulighed for at slette kontaktdata."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Tillader, at appen kan ændre data om de kontakter, der er gemt på din Android TV-enhed. Denne tilladelse giver apps mulighed for at slette kontaktdata."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Tillader, at appen kan ændre data om de kontakter, der er gemt på din telefon. Denne tilladelse giver apps mulighed for at slette kontaktdata."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"læse opkaldsliste"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Denne app kan læse din opkaldshistorik."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"skriv opkaldsliste"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"få adgang til yderligere kommandoer for placeringsudbyder"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Tillader, at appen kan få adgang til yderligere kommandoer for placeringsudbydere. Dette kan gøre det muligt for appen at forstyrre GPS-funktionen eller andre placeringskilder."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"få kun adgang til nøjagtig placering i forgrunden"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Denne app kan kun få din nøjagtige placering, når den er i forgrunden. Disse placeringstjenester skal være aktiverede og tilgængelige på din telefon, før appen kan bruge dem. Dette kan øge batteriforbruget."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"få kun adgang til omtrentlig placering (netværksbaseret) i forgrunden"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Denne app kan fastslå din placering ved hjælp af netværkskilder som f.eks. mobilmaster og Wi-Fi-netværk, men kun når appen er i forgrunden. Disse placeringstjenester skal være aktiverede og tilgængelige på din tablet, før appen kan bruge dem."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Denne app kan fastslå din placering ved hjælp af netværkskilder som f.eks. mobilmaster og Wi-Fi-netværk, men kun når appen er i forgrunden. Disse placeringstjenester skal være aktiverede og tilgængelige på din Android TV-enhed, før appen kan bruge dem."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Denne app kan fastslå din placering ved hjælp af netværkskilder som f.eks. mobilmaster og Wi-Fi-netværk, men kun når appen er i forgrunden. Disse placeringstjenester skal være aktiveret og tilgængelige på din telefon, før appen kan bruge dem."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Denne app kan kun finde din nøjagtige placering, når den er i forgrunden. Placeringstjenester skal være aktiverede og tilgængelige på din telefon, før appen kan anvende dem. Dette kan øge batteriforbruget."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"få kun adgang til omtrentlig placering i forgrunden"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Denne app kan kun finde din omtrentlige placering, når den er i forgrunden. Placeringstjenester skal være aktiverede og tilgængelige på din enhed, før appen kan anvende dem."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"adgang til placering i baggrunden"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Hvis appen godkendes, og der gives adgang til den omtrentlige eller nøjagtige placering, kan appen registrere placeringen, mens den kører i baggrunden."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Foruden adgang til placering ved kørsel i forgrunden har denne app også adgang til din placering, mens den kører i baggrunden."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"skifte dine lydindstillinger"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tillader, at appen kan ændre globale lydindstillinger, som f.eks. lydstyrke og hvilken højttaler der bruges til output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"optage lyd"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Tillader, at appen kan læse konfigurationen af Bluetooth på tabletten samt kan oprette og acceptere forbindelser med parrede enheder."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Tillader, at appen kan se konfigurationen af Bluetooth på din Android TV-enhed samt oprette og acceptere forbindelser med parrede enheder."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Tillader, at appen kan læse konfigurationen af Bluetooth på telefonen samt kan oprette og acceptere forbindelser med parrede enheder."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"administrere Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Tillader, at appen kan kommunikere med NFC-tags (Near Field Communication), -kort og -læsere."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktivere din skærmlås"</string>
@@ -900,7 +901,7 @@
<string name="factorytest_no_action" msgid="339252838115675515">"Der blev ikke fundet nogen pakke, som leverer handlingen FACTORY_TEST."</string>
<string name="factorytest_reboot" msgid="2050147445567257365">"Genstart"</string>
<string name="js_dialog_title" msgid="7464775045615023241">"På siden på \"<xliff:g id="TITLE">%s</xliff:g>\" står der:"</string>
- <string name="js_dialog_title_default" msgid="3769524569903332476">"Javascript"</string>
+ <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
<string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Bekræft navigation"</string>
<string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"Forlad denne side"</string>
<string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Bliv på denne side"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Tilsluttet <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tryk for at se filer"</string>
<string name="pin_target" msgid="8036028973110156895">"Fastgør"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Frigør"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Appinfo"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Starter demoen…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Vil du opdatere disse elementer i "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> og <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Gem"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nej tak"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ikke nu"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Aldrig"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Opdater"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Fortsæt"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"adgangskode"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Slå Opdelt skærm til eller fra"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Låseskærm"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g>-appen i et pop op-vindue."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Titellinje for <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index bb29a0e..06610c7 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Die Admin-App für das Arbeitsprofil fehlt oder ist beschädigt. Daher wurden dein Arbeitsprofil und alle zugehörigen Daten gelöscht. Bitte wende dich für weitere Hilfe an deinen Administrator."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Dein Arbeitsprofil ist auf diesem Gerät nicht mehr verfügbar"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Zu viele falsche Passworteingaben"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator hat das Gerät zur persönlichen Nutzung abgegeben"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Dies ist ein verwaltetes Gerät"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Deine Organisation verwaltet dieses Gerät und überprüft unter Umständen den Netzwerkverkehr. Tippe hier, um weitere Informationen zu erhalten."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Die Daten auf deinem Gerät werden gelöscht."</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Ermöglicht der App, dauerhafte Broadcasts zu senden, die auch nach dem Ende des Broadcasts bestehen bleiben. Ein zu intensiver Einsatz kann das Android TV-Gerät langsam oder instabil machen, weil zu viel Arbeitsspeicher belegt wird."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Ermöglicht der App, dauerhafte Broadcasts zu senden, die auch nach Ende des Broadcasts bestehen bleiben. Ein zu intensiver Einsatz kann das Telefon langsam oder instabil machen, weil zu viel Arbeitsspeicher belegt wird."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"Kontakte lesen"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Ermöglicht der App, Daten zu den auf deinem Tablet gespeicherten Kontakten zu lesen, einschließlich der Häufigkeit, mit der du bestimmte Personen angerufen, diesen E-Mails gesendet oder anderweitig mit ihnen kommuniziert hast. Die Berechtigung erlaubt Apps, deine Kontaktdaten zu speichern, und schädliche Apps können Kontaktdaten ohne dein Wissen weiterleiten."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Ermöglicht der App, Daten zu den auf deinem Android TV-Gerät gespeicherten Kontakten zu lesen, einschließlich der Häufigkeit, mit der du mit den einzelnen Kontakten telefonisch, per E‑Mail, oder anderweitig kommuniziert hast. Diese Berechtigung erlaubt Apps, deine Kontaktdaten zu speichern, weshalb schädliche Apps damit Kontaktdaten ohne dein Wissen teilen können."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Ermöglicht der App, Daten zu den auf deinem Telefon gespeicherten Kontakten zu lesen, einschließlich der Häufigkeit, mit der du bestimmte Personen angerufen, diesen E-Mails gesendet oder anderweitig mit ihnen kommuniziert hast. Die Berechtigung erlaubt Apps, deine Kontaktdaten zu speichern, und schädliche Apps können Kontaktdaten ohne dein Wissen weiterleiten."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Ermöglicht der App, Daten zu den auf deinem Tablet gespeicherten Kontakten zu lesen. Darüber hinaus haben Apps Zugriff auf die Konten auf deinem Tablet, über die Kontakte erstellt wurden. Dabei kann es sich auch um Konten handeln, die von installierten Apps erstellt wurden. Diese Berechtigung erlaubt Apps, deine Kontaktdaten zu speichern. Schädliche Apps könnten dadurch ohne dein Wissen Kontaktdaten teilen."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Ermöglicht der App, Daten zu den auf deinem Android TV-Gerät gespeicherten Kontakten zu lesen. Darüber hinaus haben Apps Zugriff auf die Konten auf deinem Android TV-Gerät, über die Kontakte erstellt wurden. Dabei kann es sich auch um Konten handeln, die von installierten Apps erstellt wurden. Diese Berechtigung erlaubt Apps, deine Kontaktdaten zu speichern. Schädliche Apps könnten dadurch ohne dein Wissen Kontaktdaten teilen."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Ermöglicht der App, Daten zu den auf deinem Smartphone gespeicherten Kontakten zu lesen. Darüber hinaus haben Apps Zugriff auf die Konten auf deinem Smartphone, über die Kontakte erstellt wurden. Dabei kann es sich auch um Konten handeln, die von installierten Apps erstellt wurden. Diese Berechtigung erlaubt Apps, deine Kontaktdaten zu speichern. Schädliche Apps könnten dadurch ohne dein Wissen Kontaktdaten teilen."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"Kontakte ändern"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Ermöglicht der App, Daten zu Kontakten, die auf deinem Tablet gespeichert sind, zu ändern, einschließlich der Häufigkeit, mit der du bestimmte Kontakte angerufen, diesen E-Mails gesendet oder anderweitig mit ihnen kommuniziert hast. Die Berechtigung erlaubt Apps, Kontaktdaten zu löschen."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Ermöglicht der App, Daten zu den auf deinem Android TV-Gerät gespeicherten Kontakten zu ändern, einschließlich der Häufigkeit, mit der du mit den einzelnen Kontakten telefonisch, per E‑Mail, oder anderweitig kommuniziert hast. Diese Berechtigung ermöglicht Apps, Kontaktdaten zu löschen."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Ermöglicht der App, Daten zu Kontakten, die auf deinem Telefon gespeichert sind, zu ändern, einschließlich der Häufigkeit, mit der du bestimmte Kontakte angerufen, diesen E-Mails gesendet oder anderweitig mit ihnen kommuniziert hast. Die Berechtigung erlaubt Apps, Kontaktdaten zu löschen."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Ermöglicht der App, Daten zu den auf deinem Tablet gespeicherten Kontakten zu ändern. Diese Berechtigung erlaubt Apps, Kontaktdaten zu löschen."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Ermöglicht der App, Daten zu den auf deinem Android TV-Gerät gespeicherten Kontakten zu ändern. Diese Berechtigung erlaubt Apps, Kontaktdaten zu löschen."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Ermöglicht der App, Daten zu den auf deinem Smartphone gespeicherten Kontakten zu ändern. Diese Berechtigung erlaubt Apps, Kontaktdaten zu löschen."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"Anrufliste lesen"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Diese App kann deine Anrufliste lesen."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"Anrufliste bearbeiten"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"Auf zusätzliche Dienstanbieterbefehle für Standort zugreifen"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Ermöglicht der App, auf zusätzliche Standortanbieterbefehle zuzugreifen. Damit könnte die App die Funktionsweise von GPS oder anderen Standortquellen beeinträchtigen."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"Nur bei Ausführung im Vordergrund auf den genauen Standort zugreifen"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Diese App kann deinen genauen Standort nur dann ermitteln, wenn sie im Vordergrund ausgeführt wird. Die App kann diese Standortdienste nur verwenden, wenn sie auf deinem Smartphone aktiviert und verfügbar sind. Hierdurch kann sich der Akkuverbrauch erhöhen."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"Nur bei Ausführung im Vordergrund auf den ungefähren Standort (netzwerkbasiert) zugreifen"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Diese App kann deinen Standort anhand von Netzwerkquellen wie Mobilfunkmasten und WLANs ermitteln, allerdings nur, wenn sie im Vordergrund ausgeführt wird. Diese Standortdienste müssen auf deinem Tablet aktiviert und verfügbar sein, damit die App sie nutzen kann."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Diese App kann deinen Standort anhand von Netzwerkquellen wie Mobilfunkmasten und WLANs ermitteln, allerdings nur, wenn sie im Vordergrund ausgeführt wird. Damit die App sie nutzen kann, müssen diese Standortdienste auf deinem Android TV-Gerät aktiviert und verfügbar sein."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Diese App kann deinen Standort anhand von Netzwerkquellen wie Mobilfunkmasten und WLANs ermitteln, allerdings nur, wenn sie im Vordergrund ausgeführt wird. Diese Standortdienste müssen auf deinem Smartphone aktiviert und verfügbar sein, damit die App sie nutzen kann."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Diese App kann deinen genauen Standort nur dann ermitteln, wenn sie im Vordergrund ausgeführt wird. Damit die App Standortdienste nutzen kann, müssen sie auf deinem Gerät aktiviert und verfügbar sein. Hierdurch kann sich der Akkuverbrauch erhöhen."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"Nur bei Ausführung im Vordergrund auf den ungefähren Standort zugreifen"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Diese App kann deinen ungefähren Standort nur dann ermitteln, wenn sie im Vordergrund ausgeführt wird. Damit die App Standortdienste nutzen kann, müssen sie auf deinem Gerät aktiviert und verfügbar sein."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"Im Hintergrund auf den Standort zugreifen"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Wenn zusätzlich zum Zugriff auf den ungefähren oder genauen Standort diese Erlaubnis erteilt wird, kann die App bei Ausführung im Hintergrund auf den Standort zugreifen."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Diese App kann bei Ausführung im Vorder- und Hintergrund auf den Standort zugreifen."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Audio-Einstellungen ändern"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ermöglicht der App, globale Audio-Einstellungen zu ändern, etwa die Lautstärke und den Lautsprecher für die Ausgabe."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"Audio aufnehmen"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Ermöglicht der App, die Bluetooth-Konfiguration eines Tablets einzusehen und Verbindungen zu gekoppelten Geräten herzustellen und zu akzeptieren."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Ermöglicht der App, die Bluetooth-Konfiguration des Android TV-Geräts abzurufen und Verbindungen zu gekoppelten Geräten herzustellen und zu akzeptieren."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Ermöglicht der App, die Bluetooth-Konfiguration des Telefons einzusehen und Verbindungen mit gekoppelten Geräten herzustellen und zu akzeptieren."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"Nahfeldkommunikation steuern"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Ermöglicht der App die Kommunikation mit Tags für die Nahfeldkommunikation, Karten und Readern"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"Displaysperre deaktivieren"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Verbunden mit <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Zum Ansehen der Dateien tippen"</string>
<string name="pin_target" msgid="8036028973110156895">"Markieren"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Markierung entfernen"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"App-Informationen"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Demo wird gestartet…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"<xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> und <xliff:g id="TYPE_2">%3$s</xliff:g> in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" aktualisieren?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Speichern"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nein danke"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Nicht jetzt"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nie"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Aktualisieren"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Fortsetzen"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"Passwort"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"\"Bildschirm teilen\" ein-/ausschalten"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Sperrbildschirm"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"App \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" im Pop-up-Fenster."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Untertitelleiste von <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 807c4c2..4e6d695 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Η εφαρμογή διαχείρισης προφίλ εργασίας είτε λείπει είτε είναι κατεστραμμένη. Ως αποτέλεσμα, διαγράφηκε το προφίλ εργασίας και τα σχετικά δεδομένα. Επικοινωνήστε με τον διαχειριστή σας για βοήθεια."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Το προφίλ εργασίας σας δεν είναι πια διαθέσιμο σε αυτήν τη συσκευή"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Πάρα πολλές προσπάθειες εισαγωγής κωδικού πρόσβασης"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Συσκευή από την οποία αποσύρθηκε ο διαχειριστής για προσωπική χρήση"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Η συσκευή είναι διαχειριζόμενη"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Ο οργανισμός σας διαχειρίζεται αυτήν τη συσκευή και ενδέχεται να παρακολουθεί την επισκεψιμότητα δικτύου. Πατήστε για λεπτομέρειες."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Η συσκευή σας θα διαγραφεί"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Επιτρέπει στην εφαρμογή την αποστολή μεταδόσεων που παραμένουν μετά το τέλος της διαδικασίας μετάδοσης. Η υπερβολική χρήση αυτής της δυνατότητας μπορεί να καταστήσει τη λειτουργία της συσκευής Android TV αργή ή ασταθή λόγω της χρήσης πολλής μνήμης."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Επιτρέπει στην εφαρμογή την αποστολή εκπομπών sticky, οι οποίες παραμένουν μετά το τέλος της εκπομπής. Η υπερβολική χρήση ενδέχεται να καταστήσει τη λειτουργία του τηλεφώνου αργή ή ασταθή, προκαλώντας τη χρήση μεγάλου τμήματος της μνήμης."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"διαβάζει τις επαφές σας"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Επιτρέπει στην εφαρμογή την ανάγνωση δεδομένων σχετικά με τις επαφές σας που είναι αποθηκευμένες στο tablet σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένα άτομα ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτή η άδεια δίνει τη δυνατότητα σε εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας και οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Επιτρέπει στην εφαρμογή να διαβάζει τα δεδομένα που αποθηκεύονται στη συσκευή Android TV σχετικά με τις επαφές σας, συμπεριλαμβανομένης της συχνότητας με την οποία κάνετε κλήσεις, στέλνετε μηνύματα ηλεκτρονικού ταχυδρομείου ή επικοινωνείτε με άλλους τρόπους με συγκεκριμένα άτομα. Αυτή η άδεια δίνει τη δυνατότητα στις εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας. Οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται τα δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Επιτρέπει στην εφαρμογή την ανάγνωση δεδομένων σχετικά με τις επαφές σας που είναι αποθηκευμένες στο τηλέφωνό σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένα άτομα ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτή η άδεια δίνει τη δυνατότητα σε εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας και οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Επιτρέπει στην εφαρμογή να διαβάζει δεδομένα σχετικά με τις επαφές σας που έχουν αποθηκευτεί στο tablet. Οι εφαρμογές θα έχουν επίσης πρόσβαση στους λογαριασμούς στο tablet, οι οποίοι έχουν δημιουργήσει επαφές. Σε αυτούς ενδέχεται να περιλαμβάνονται λογαριασμοί που δημιουργήθηκαν από εφαρμογές που έχετε εγκαταστήσει. Αυτή η άδεια δίνει τη δυνατότητα στις εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας. Οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται τα δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Επιτρέπει στην εφαρμογή να διαβάζει δεδομένα σχετικά με τις επαφές σας που έχουν αποθηκευτεί στη συσκευή σας Android TV. Οι εφαρμογές θα έχουν επίσης πρόσβαση στους λογαριασμούς στη συσκευή σας Android TV, οι οποίοι έχουν δημιουργήσει επαφές. Σε αυτούς ενδέχεται να περιλαμβάνονται λογαριασμοί που δημιουργήθηκαν από εφαρμογές που έχετε εγκαταστήσει. Αυτή η άδεια δίνει τη δυνατότητα στις εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας. Οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται τα δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Επιτρέπει στην εφαρμογή να διαβάζει δεδομένα σχετικά με τις επαφές σας που έχουν αποθηκευτεί στο τηλέφωνό σας. Οι εφαρμογές θα έχουν επίσης πρόσβαση στους λογαριασμούς στο τηλέφωνό σας, οι οποίοι έχουν δημιουργήσει επαφές. Σε αυτούς ενδέχεται να περιλαμβάνονται λογαριασμοί που δημιουργήθηκαν από εφαρμογές που έχετε εγκαταστήσει. Αυτή η άδεια δίνει τη δυνατότητα στις εφαρμογές να αποθηκεύουν τα δεδομένα των επαφών σας. Οι κακόβουλες εφαρμογές ενδέχεται να μοιράζονται τα δεδομένα επαφών χωρίς να το γνωρίζετε."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"τροποποιεί τις επαφές σας"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Επιτρέπει στην εφαρμογή την τροποποίηση των δεδομένων σχετικά με τις επαφές σας που είναι αποθηκευμένες στο tablet σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένες επαφές ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτή η άδεια δίνει τη δυνατότητα σε εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Επιτρέπει στην εφαρμογή να τροποποιεί τα δεδομένα που αποθηκεύονται στη συσκευή Android TV σχετικά με τις επαφές σας, συμπεριλαμβανομένης της συχνότητας με την οποία κάνετε κλήσεις, στέλνετε μηνύματα ηλεκτρονικού ταχυδρομείου ή επικοινωνείτε με άλλους τρόπους με συγκεκριμένες επαφές. Αυτή η άδεια επιτρέπει στις εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Επιτρέπει στην εφαρμογή την τροποποίηση των δεδομένων σχετικά με τις επαφές σας που είναι αποθηκευμένες στο τηλέφωνό σας, συμπεριλαμβανομένης της συχνότητας με την οποία έχετε καλέσει συγκεκριμένες επαφές ή έχετε επικοινωνήσει μαζί τους μέσω ηλεκτρονικού ταχυδρομείου ή άλλου τρόπου. Αυτή η άδεια δίνει τη δυνατότητα σε εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Επιτρέπει στην εφαρμογή να τροποποιεί δεδομένα σχετικά με τις επαφές σας που έχουν αποθηκευτεί στο tablet. Αυτή η άδεια επιτρέπει στις εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Επιτρέπει στην εφαρμογή να τροποποιήσει τα δεδομένα σχετικά με τις επαφές σας που έχουν αποθηκευτεί στη συσκευή σας Android TV. Αυτή η άδεια επιτρέπει στις εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Επιτρέπει στην εφαρμογή να τροποποιεί τα δεδομένα σχετικά με τις επαφές σας που έχουν αποθηκευτεί στο tablet. Αυτή η άδεια επιτρέπει στις εφαρμογές να διαγράφουν δεδομένα επαφών."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"διαβάζει το αρχείο καταγραφής κλήσεων"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Αυτή η εφαρμογή μπορεί να διαβάσει το ιστορικό κλήσεων."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"εγγράφει αρχείο καταγραφής κλήσεων"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"έχει πρόσβαση σε επιπλέον εντολές παρόχου τοποθεσίας"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Επιτρέπει στην εφαρμογή την πρόσβαση σε επιπλέον εντολές παρόχου τοποθεσίας. Αυτό μπορεί να δώσει τη δυνατότητα στην εφαρμογή να παρέμβει στη λειτουργία του GPS ή άλλων πηγών τοποθεσίας."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"πρόσβαση στην ακριβή τοποθεσία μόνο στο προσκήνιο"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Αυτή η εφαρμογή μπορεί να ανιχνεύσει την ακριβή τοποθεσία σας όταν βρίσκεται στο προσκήνιο. Αυτές οι υπηρεσίες τοποθεσίας θα πρέπει να είναι ενεργοποιημένες και διαθέσιμες στο τηλέφωνό σας, προκειμένου να μπορεί να τις χρησιμοποιήσει η εφαρμογή. Με την ενεργοποίηση αυτής της ρύθμισης, μπορεί να αυξηθεί η κατανάλωση μπαταρίας."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"πρόσβαση στην κατά προσέγγιση τοποθεσία (βάσει δικτύου) μόνο στο προσκήνιο"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Αυτή η εφαρμογή μπορεί να ανιχνεύσει την τοποθεσία σας βάσει πηγών δικτύου, όπως κεραίες κινητής τηλεφωνίας και δίκτυα Wi-Fi, αλλά μόνο όταν η εφαρμογή βρίσκεται στο προσκήνιο. Αυτές οι υπηρεσίες τοποθεσίας θα πρέπει να είναι ενεργοποιημένες και διαθέσιμες στο tablet που χρησιμοποιείτε, προκειμένου να μπορεί να τις χρησιμοποιήσει η εφαρμογή."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Αυτή η εφαρμογή μπορεί να ανιχνεύσει την τοποθεσία σας βάσει πηγών δικτύου, όπως οι κεραίες κινητής τηλεφωνίας και τα δίκτυα Wi-Fi, αλλά μόνο όταν η εφαρμογή βρίσκεται στο προσκήνιο. Αυτές οι υπηρεσίες τοποθεσίας θα πρέπει να είναι ενεργοποιημένες και διαθέσιμες στη συσκευή Android TV, προκειμένου να μπορεί να τις χρησιμοποιήσει η εφαρμογή."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Αυτή η εφαρμογή μπορεί να ανιχνεύσει την τοποθεσία σας βάσει πηγών δικτύου, όπως κεραίες κινητής τηλεφωνίας και δίκτυα Wi-Fi, αλλά μόνο όταν η εφαρμογή βρίσκεται στο προσκήνιο. Αυτές οι υπηρεσίες τοποθεσίας θα πρέπει να είναι ενεργοποιημένες και διαθέσιμες στο τηλέφωνό σας, προκειμένου να μπορεί να τις χρησιμοποιήσει η εφαρμογή."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Αυτή η εφαρμογή μπορεί να ανιχνεύσει την ακριβή τοποθεσία σας μόνο όταν βρίσκεται στο προσκήνιο. Οι υπηρεσίες τοποθεσίας θα πρέπει να είναι ενεργοποιημένες και διαθέσιμες στη συσκευή σας, προκειμένου να μπορεί να τις χρησιμοποιήσει η εφαρμογή. Με την ενεργοποίηση αυτής της ρύθμισης, μπορεί να αυξηθεί η κατανάλωση μπαταρίας."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"πρόσβαση στην κατά προσέγγιση τοποθεσία μόνο στο προσκήνιο"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Αυτή η εφαρμογή έχει πρόσβαση στην τοποθεσία κατά προσέγγιση, μόνο όταν βρίσκεται στο προσκήνιο. Οι υπηρεσίες τοποθεσίας πρέπει να έχουν ενεργοποιηθεί και να είναι διαθέσιμες στη συσκευή σας, για να μπορεί να τις χρησιμοποιήσει η εφαρμογή."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"πρόσβαση στην τοποθεσία στο παρασκήνιο"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Εάν εκχωρηθεί επιπρόσθετα σε μια καταπροσέγγιση ή ακριβή πρόσβαση τοποθεσίας, η εφαρμογή μπορεί να έχει πρόσβαση στην τοποθεσία κατά την εκτέλεση στο παρασκήνιο."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Εκτός από την πρόσβαση τοποθεσίας ενώ βρίσκεται στο προσκήνιο, αυτή η εφαρμογή μπορεί να αποκτήσει πρόσβαση στην τοποθεσία ενώ εκτελείται στο παρασκήνιο."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"αλλάζει τις ρυθμίσεις ήχου"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Επιτρέπει στην εφαρμογή την τροποποίηση καθολικών ρυθμίσεων ήχου, όπως η ένταση και ποιο ηχείο χρησιμοποιείται για έξοδο."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"εγγράφει ήχο"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Επιτρέπει στην εφαρμογή να προβάλλει τη διαμόρφωση του Bluetooth στο tablet, καθώς και να πραγματοποιεί και να αποδέχεται συνδέσεις με συνδεδεμένες συσκευές."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Επιτρέπει στην εφαρμογή να βλέπει τη διαμόρφωση του Bluetooth στη συσκευή Android TV και να κάνει και να αποδέχεται συνδέσεις με συζευγμένες συσκευές."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Επιτρέπει στην εφαρμογή να προβάλλει τη διαμόρφωση του Bluetooth στο τηλέφωνο, καθώς και να πραγματοποιεί και να αποδέχεται συνδέσεις με συνδεδεμένες συσκευές."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ελέγχει την Επικοινωνία κοντινού πεδίου (FNC)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Επιτρέπει στην εφαρμογή την επικοινωνία με ετικέτες, κάρτες και αναγνώστες της Επικοινωνίας κοντινού πεδίου (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"απενεργοποιεί το κλείδωμα οθόνης"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Συνδέθηκε με το <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Πατήστε για να δείτε τα αρχεία"</string>
<string name="pin_target" msgid="8036028973110156895">"Καρφίτσωμα"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Ξεκαρφίτσωμα"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Πληροφορίες εφαρμογής"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Έναρξη επίδειξης…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Ενημέρωση αυτών των στοιχείων "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> και <xliff:g id="TYPE_2">%3$s</xliff:g>;"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Αποθήκευση"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Όχι, ευχαριστώ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Όχι τώρα"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Ποτέ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Ενημέρωση"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Συνέχεια"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"κωδικός πρόσβασης"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Εναλλαγή διαχωρισμού οθόνης"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Οθόνη κλειδώματος"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Στιγμιότυπο οθόνης"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> σε αναδυόμενο παράθυρο."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Γραμμή υποτίτλων για την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 8d2f805..c92ea6d 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Your organisation manages this device and may monitor network traffic. Tap for details."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Your device will be erased"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make your Android TV device slow or unstable by causing it to use too much memory."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make the phone slow or unstable by causing it to use too much memory."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"read your contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Allows the app to read data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Allows the app to read data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Allows the app to read data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modify your contacts"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Allows the app to modify the data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Allows the app to modify the data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Allows the app to modify the data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Allows the app to modify the data about your contacts stored on your tablet. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Allows the app to modify the data about your contacts stored on your Android TV device. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Allows the app to modify the data about your contacts stored on your phone. This permission allows apps to delete contact data."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"read call log"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"write call log"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"access extra location provider commands"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Allows the app to access extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"access precise location only in the foreground"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"This app can get your exact location only when it is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"access approximate location (network-based) only in the foreground"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your tablet for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your Android TV device for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"This app can get your exact location only when it is in the foreground. Location Services must be turned on and available on your device for the app to be able to use them. This may increase battery consumption."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"access approximate location only in the foreground"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"This app can get your approximate location only when it is in the foreground. Location Services must be turned on and available on your device for the app to be able to use them."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"access location in the background"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"If this is granted additionally to the approximate or precise location access, the app can access the location while running in the background."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"This app can access location while running in the background, in addition to foreground location access."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone and to make and accept connections with paired devices."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
@@ -1862,7 +1863,9 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Connected to <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tap to view files"</string>
<string name="pin_target" msgid="8036028973110156895">"Pin"</string>
+ <string name="pin_specific_target" msgid="7824671240625957415">"Pin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="unpin_target" msgid="3963318576590204447">"Unpin"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Unpin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"App info"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Starting demo…"</string>
@@ -1905,6 +1908,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Save"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No, thanks"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Not now"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Never"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continue"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"password"</string>
@@ -2000,5 +2005,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Toggle Split Screen"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> app in pop-up window."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index e538587..ea6a702 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Your organisation manages this device and may monitor network traffic. Tap for details."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Your device will be erased"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make your Android TV device slow or unstable by causing it to use too much memory."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make the phone slow or unstable by causing it to use too much memory."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"read your contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Allows the app to read data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Allows the app to read data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Allows the app to read data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modify your contacts"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Allows the app to modify the data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Allows the app to modify the data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Allows the app to modify the data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Allows the app to modify the data about your contacts stored on your tablet. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Allows the app to modify the data about your contacts stored on your Android TV device. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Allows the app to modify the data about your contacts stored on your phone. This permission allows apps to delete contact data."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"read call log"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"write call log"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"access extra location provider commands"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Allows the app to access extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"access precise location only in the foreground"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"This app can get your exact location only when it is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"access approximate location (network-based) only in the foreground"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your tablet for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your Android TV device for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"This app can get your exact location only when it is in the foreground. Location Services must be turned on and available on your device for the app to be able to use them. This may increase battery consumption."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"access approximate location only in the foreground"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"This app can get your approximate location only when it is in the foreground. Location Services must be turned on and available on your device for the app to be able to use them."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"access location in the background"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"If this is granted additionally to the approximate or precise location access, the app can access the location while running in the background."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"This app can access location while running in the background, in addition to foreground location access."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone and to make and accept connections with paired devices."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
@@ -1862,7 +1863,9 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Connected to <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tap to view files"</string>
<string name="pin_target" msgid="8036028973110156895">"Pin"</string>
+ <string name="pin_specific_target" msgid="7824671240625957415">"Pin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="unpin_target" msgid="3963318576590204447">"Unpin"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Unpin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"App info"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Starting demo…"</string>
@@ -1905,6 +1908,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Save"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No, thanks"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Not now"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Never"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continue"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"password"</string>
@@ -2000,5 +2005,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Toggle Split Screen"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> app in pop-up window."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 8d2f805..c92ea6d 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Your organisation manages this device and may monitor network traffic. Tap for details."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Your device will be erased"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make your Android TV device slow or unstable by causing it to use too much memory."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make the phone slow or unstable by causing it to use too much memory."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"read your contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Allows the app to read data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Allows the app to read data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Allows the app to read data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modify your contacts"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Allows the app to modify the data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Allows the app to modify the data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Allows the app to modify the data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Allows the app to modify the data about your contacts stored on your tablet. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Allows the app to modify the data about your contacts stored on your Android TV device. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Allows the app to modify the data about your contacts stored on your phone. This permission allows apps to delete contact data."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"read call log"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"write call log"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"access extra location provider commands"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Allows the app to access extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"access precise location only in the foreground"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"This app can get your exact location only when it is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"access approximate location (network-based) only in the foreground"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your tablet for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your Android TV device for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"This app can get your exact location only when it is in the foreground. Location Services must be turned on and available on your device for the app to be able to use them. This may increase battery consumption."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"access approximate location only in the foreground"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"This app can get your approximate location only when it is in the foreground. Location Services must be turned on and available on your device for the app to be able to use them."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"access location in the background"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"If this is granted additionally to the approximate or precise location access, the app can access the location while running in the background."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"This app can access location while running in the background, in addition to foreground location access."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone and to make and accept connections with paired devices."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
@@ -1862,7 +1863,9 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Connected to <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tap to view files"</string>
<string name="pin_target" msgid="8036028973110156895">"Pin"</string>
+ <string name="pin_specific_target" msgid="7824671240625957415">"Pin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="unpin_target" msgid="3963318576590204447">"Unpin"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Unpin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"App info"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Starting demo…"</string>
@@ -1905,6 +1908,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Save"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No, thanks"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Not now"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Never"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continue"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"password"</string>
@@ -2000,5 +2005,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Toggle Split Screen"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> app in pop-up window."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 8d2f805..c92ea6d 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Your organisation manages this device and may monitor network traffic. Tap for details."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Your device will be erased"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make your Android TV device slow or unstable by causing it to use too much memory."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make the phone slow or unstable by causing it to use too much memory."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"read your contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Allows the app to read data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Allows the app to read data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Allows the app to read data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modify your contacts"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Allows the app to modify the data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Allows the app to modify the data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Allows the app to modify the data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Allows the app to modify the data about your contacts stored on your tablet. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Allows the app to modify the data about your contacts stored on your Android TV device. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Allows the app to modify the data about your contacts stored on your phone. This permission allows apps to delete contact data."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"read call log"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"write call log"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"access extra location provider commands"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Allows the app to access extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"access precise location only in the foreground"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"This app can get your exact location only when it is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"access approximate location (network-based) only in the foreground"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your tablet for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your Android TV device for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"This app can get your location based on network sources such as phone masts and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"This app can get your exact location only when it is in the foreground. Location Services must be turned on and available on your device for the app to be able to use them. This may increase battery consumption."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"access approximate location only in the foreground"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"This app can get your approximate location only when it is in the foreground. Location Services must be turned on and available on your device for the app to be able to use them."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"access location in the background"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"If this is granted additionally to the approximate or precise location access, the app can access the location while running in the background."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"This app can access location while running in the background, in addition to foreground location access."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone and to make and accept connections with paired devices."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
@@ -1862,7 +1863,9 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Connected to <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tap to view files"</string>
<string name="pin_target" msgid="8036028973110156895">"Pin"</string>
+ <string name="pin_specific_target" msgid="7824671240625957415">"Pin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="unpin_target" msgid="3963318576590204447">"Unpin"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Unpin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"App info"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Starting demo…"</string>
@@ -1905,6 +1908,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Save"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No, thanks"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Not now"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Never"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continue"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"password"</string>
@@ -2000,5 +2005,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Toggle Split Screen"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> app in pop-up window."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 1436692..c386e6e 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Your work profile is no longer available on this device"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Too many password attempts"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin relinquished device for personal use"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Your organization manages this device and may monitor network traffic. Tap for details."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Your device will be erased"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make your Android TV device slow or unstable by causing it to use too much memory."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make the phone slow or unstable by causing it to use too much memory."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"read your contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Allows the app to read data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed, or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Allows the app to read data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed, or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Allows the app to read data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed, or communicated in other ways with specific individuals. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modify your contacts"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Allows the app to modify the data about your contacts stored on your tablet, including the frequency with which you\'ve called, emailed, or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Allows the app to modify the data about your contacts stored on your Android TV device, including the frequency with which you\'ve called, emailed, or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Allows the app to modify the data about your contacts stored on your phone, including the frequency with which you\'ve called, emailed, or communicated in other ways with specific contacts. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Allows the app to modify the data about your contacts stored on your tablet. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Allows the app to modify the data about your contacts stored on your Android TV device. This permission allows apps to delete contact data."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Allows the app to modify the data about your contacts stored on your phone. This permission allows apps to delete contact data."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"read call log"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"write call log"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"access extra location provider commands"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Allows the app to access extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"access precise location only in the foreground"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"This app can get your exact location only when it is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"access approximate location (network-based) only in the foreground"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when when the app is in the foreground. These location services must be turned on and available on your tablet for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when when the app is in the foreground. These location services must be turned on and available on your Android TV device for the app to be able to use them."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"This app can get your exact location only when it is in the foreground. Location services must be turned on and available on your device for the app to be able to use them. This may increase battery consumption."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"access approximate location only in the foreground"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"This app can get your approximate location only when it is in the foreground. Location services must be turned on and available on your device for the app to be able to use them."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"access location in the background"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"If this is granted additionally to the approximate or precise location access the app can access the location while running in the background."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"This app can access location while running in the background, in addition to foreground location access."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet, and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device, and to make and accept connections with paired devices."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone, and to make and accept connections with paired devices."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"control Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards, and readers."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
@@ -1862,7 +1863,9 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Connected to <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tap to view files"</string>
<string name="pin_target" msgid="8036028973110156895">"Pin"</string>
+ <string name="pin_specific_target" msgid="7824671240625957415">"Pin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="unpin_target" msgid="3963318576590204447">"Unpin"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Unpin <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"App info"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Starting demo…"</string>
@@ -1905,6 +1908,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, and <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Save"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No thanks"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Not now"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Never"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continue"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"password"</string>
@@ -2000,5 +2005,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Toggle Split Screen"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> app in Pop-up window."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index b04882a..ec758d5 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"La app de administración de perfil de trabajo no se encuentra o está dañada. Por lo tanto, se borraron tu perfil de trabajo y los datos relacionados. Para obtener asistencia, comunícate con el administrador."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Tu perfil de trabajo ya no está disponible en este dispositivo"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Demasiados intentos para ingresar la contraseña"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"El administrador no permite hacer un uso personal del dispositivo"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Dispositivo administrado"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Tu organización administra este dispositivo y es posible que controle el tráfico de red. Presiona para obtener más información."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Se borrarán los datos del dispositivo"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permite que la app envíe transmisiones persistentes que permanecen después de que finaliza la emisión. Un uso excesivo podría ralentizar el dispositivo Android TV o forzarlo a utilizar demasiada memoria, lo que generaría un funcionamiento inestable."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permite que la aplicación envíe transmisiones persistentes que permanecen después de que finaliza la transmisión. Un uso excesivo podría ralentizar el dispositivo o hacer que funcione de manera inestable al forzarlo a utilizar mucha memoria."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"leer tus contactos"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permite que la aplicación consulte información sobre los contactos almacenados en la tablet, incluida la frecuencia con la que los has llamado, les has enviado correos o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para guardar los datos de los contactos, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permite que la app lea datos sobre los contactos almacenados en el dispositivo Android TV, incluida la frecuencia con la que llamaste a una persona específica, le enviaste correos electrónicos o te comunicaste con ella de cualquier otra manera. Las apps pueden utilizar este permiso para guardar los datos de los contactos, y aquellas apps maliciosas podrían compartir esa información sin tu conocimiento."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permite que la aplicación consulte información sobre contactos almacenados en el dispositivo, incluida la frecuencia con la que los has llamado, les has enviado correos o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para guardar los datos de los contactos, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite que la app lea datos sobre los contactos almacenados en la tablet. Las apps también tendrán acceso a las cuentas de tu tablet en las que se hayan creado contactos. Pueden incluirse cuentas creadas por apps que hayas instalado. Las apps pueden utilizar este permiso para guardar los datos de los contactos, y las apps maliciosas podrían compartir esa información sin tu conocimiento."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite que la app lea datos sobre los contactos almacenados en el dispositivo Android TV. Las apps también tendrán acceso a las cuentas de tu dispositivo Android TV en las que se hayan creado contactos. Pueden incluirse cuentas creadas por apps que hayas instalado. Las apps pueden utilizar este permiso para guardar los datos de los contactos, y las apps maliciosas podrían compartir esa información sin tu conocimiento."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite que la app lea datos sobre los contactos almacenados en el teléfono. Las apps también tendrán acceso a las cuentas de tu teléfono en las que se hayan creado contactos. Pueden incluirse cuentas creadas por apps que hayas instalado. Las apps pueden utilizar este permiso para guardar los datos de los contactos, y las apps maliciosas podrían compartir esa información sin tu conocimiento."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modificar tus contactos"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permite que la aplicación modifique los datos de los contactos almacenados en la tablet, incluida la frecuencia con la que los has llamado, les has enviado correos o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para eliminar datos de contactos."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permite que la app modifique datos sobre los contactos almacenados en el dispositivo Android TV, incluida la frecuencia con la que llamaste a una persona específica, le enviaste correos electrónicos o te comunicaste con ella de cualquier otra manera. Las apps pueden utilizar este permiso para borrar los datos de los contactos."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permite que la aplicación modifique los datos de los contactos almacenados en el dispositivo, incluida la frecuencia con la que los has llamado, les has enviado correos o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para eliminar datos de contactos."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permite que la app modifique datos sobre los contactos almacenados en la tablet. Las apps pueden utilizar este permiso para borrar los datos de los contactos."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permite que la app modifique datos sobre los contactos almacenados en el dispositivo Android TV. Las apps pueden utilizar este permiso para borrar los datos de los contactos."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permite que la app modifique datos sobre los contactos almacenados en el teléfono. Las apps pueden utilizar este permiso para borrar los datos de los contactos."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"leer el registro de llamadas"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Esta app puede leer tu historial de llamadas."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"escribir en el registro de llamadas"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"acceder a comandos adicionales del proveedor del lugar"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permite que la aplicación acceda a comandos adicionales del proveedor de ubicación. Esto puede permitirle a la aplicación interferir con el funcionamiento del GPS o de otras fuentes de ubicación."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"acceder a la ubicación exacta solo en primer plano"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Esta app puede obtener tu ubicación exacta solo cuando está en primer plano. Los servicios de ubicación deben estar activados y disponibles en el teléfono para que la app pueda usarlos. Es posible que aumente el consumo de batería."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"acceso a la ubicación aproximada (mediante red) solo en primer plano"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Esta app puede obtener tu ubicación desde fuentes de red, como torres de telefonía celular y redes Wi-Fi, pero solo en primer plano. Los servicios de ubicación deben estar activados y disponibles en tu tablet para que la app pueda usarlos."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Esta app puede obtener tu ubicación desde fuentes de red, como torres de telefonía celular y redes Wi-Fi, pero solo cuando está en primer plano. Los servicios de ubicación deben estar activados y disponibles en el dispositivo Android TV para que pueda usarlos la app."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Esta app puede obtener tu ubicación desde fuentes de red, como torres de telefonía celular y redes Wi-Fi, pero solo en primer plano. Los servicios de ubicación deben estar activados y disponibles en tu teléfono para que la app pueda usarlos."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Esta app puede obtener tu ubicación exacta solo cuando está en primer plano. Los servicios de ubicación deben estar activados y disponibles en el dispositivo para que la app pueda usarlos. Es posible que aumente el consumo de batería."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"acceder a la ubicación aproximada solo en primer plano"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Esta app puede obtener tu ubicación aproximada únicamente cuando está en primer plano. Los servicios de ubicación deben estar activados y disponibles en el dispositivo para que la app pueda usarlos."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"acceder a la ubicación en segundo plano"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Si este permiso se otorga de manera adicional para aproximar o precisar el acceso a la ubicación, la app podrá acceder a la ubicación mientras se ejecuta en segundo plano."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Esta app puede acceder a una ubicación mientras se ejecuta en segundo plano, además de proporcionar acceso a la ubicación en primer plano."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar tu configuración de audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y el altavoz de salida."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"grabar audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite que la aplicación vea la configuración de Bluetooth de la tablet y que cree y acepte conexiones con los dispositivos sincronizados."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite que la app vea la configuración de Bluetooth del dispositivo Android TV, así como que cree y acepte conexiones con los dispositivos sincronizados."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite que la aplicación vea la configuración de Bluetooth del dispositivo y que cree y acepte conexiones con los dispositivos sincronizados."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controlar la Transmisión de datos en proximidad"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permite que la aplicación se comunique con lectores, tarjetas y etiquetas de Comunicación de campo cercano (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"desactivar el bloqueo de pantalla"</string>
@@ -1298,7 +1299,7 @@
<string name="usb_supplying_notification_title" msgid="5378546632408101811">"Cargando el dispositivo conectado mediante USB"</string>
<string name="usb_mtp_notification_title" msgid="1065989144124499810">"Se activó la transferencia de archivos mediante USB"</string>
<string name="usb_ptp_notification_title" msgid="5043437571863443281">"Se activó el modo PTP mediante USB"</string>
- <string name="usb_tether_notification_title" msgid="8828527870612663771">"Se activó la conexión mediante dispositivo portátil por USB"</string>
+ <string name="usb_tether_notification_title" msgid="8828527870612663771">"Se activó la conexión mediante dispositivo móvil por USB"</string>
<string name="usb_midi_notification_title" msgid="7404506788950595557">"Se activó el modo MIDI mediante USB"</string>
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"Accesorio USB conectado"</string>
<string name="usb_notification_message" msgid="4715163067192110676">"Presiona para ver más opciones."</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Conectado a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Presiona para ver archivos"</string>
<string name="pin_target" msgid="8036028973110156895">"Fijar"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"No fijar"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Información de apps"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demostración…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"¿Quieres actualizar estos elementos en "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> y <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Guardar"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No, gracias"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ahora no"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nunca"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Actualizar"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuar"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"contraseña"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Activar o desactivar pantalla dividida"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Bloquear pantalla"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Captura de pantalla"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"App de <xliff:g id="APP_NAME">%1$s</xliff:g> en una ventana emergente."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de subtítulos de <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 81f210b..22599b0 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta la aplicación de administración del perfil de trabajo o está dañada. Por ello, se han eliminado tu perfil de trabajo y los datos relacionados. Ponte en contacto con el administrador para obtener ayuda."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Tu perfil de trabajo ya no está disponible en este dispositivo"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Has fallado demasiadas veces al introducir la contraseña"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"El administrador no permite hacer un uso personal del dispositivo"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"El dispositivo está administrado"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Tu organización administra este dispositivo y puede supervisar el tráfico de red. Toca la notificación para obtener más información."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Tu dispositivo se borrará"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permite que la aplicación envíe emisiones que permanecen en el dispositivo una vez finalizadas. Si esta función se utiliza en exceso, podría ralentizar tu dispositivo Android TV o volverlo inestable al hacer que se ocupe demasiada memoria."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permite que la aplicación envíe emisiones que permanecen en el dispositivo una vez que la emisión ha finalizado. Un uso excesivo podría ralentizar el teléfono o volverlo inestable al hacer que use demasiada memoria."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"consultar tus contactos"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permite que la aplicación consulte información sobre contactos almacenados en el tablet, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto con ellos de otro modo. Este permiso permite guardar los datos de los contactos, y las aplicaciones malintencionadas pueden utilizarlo para compartir datos de contactos del usuario sin su consentimiento."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permite que la aplicación consulte los datos de los contactos almacenados en tu dispositivo Android TV, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto de cualquier otro modo con cada uno. Las aplicaciones que tengan este permiso pueden guardar los datos de tus contactos, y las aplicaciones maliciosas puede que compartan estos datos sin tu conocimiento."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permite que la aplicación consulte información sobre contactos almacenados en el teléfono, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto con ellos de otro modo. Este permiso permite guardar los datos de los contactos, y las aplicaciones malintencionadas pueden utilizarlo para compartir datos de contactos del usuario sin su consentimiento."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite que la aplicación consulte datos de los contactos almacenados en tu tablet. Las aplicaciones también podrán acceder a las cuentas de tu tablet que hayan creado contactos, y quizá tengan acceso a las cuentas creadas por aplicaciones que hayas descargado. Las aplicaciones que tengan este permiso pueden guardar los datos de tus contactos, y las aplicaciones maliciosas puede que compartan estos datos sin que lo sepas."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite que la aplicación consulte datos de los contactos almacenados en tu dispositivo Android TV. Las aplicaciones también podrán acceder a las cuentas de tu dispositivo Android TV que hayan creado contactos, y quizá tengan acceso a las cuentas creadas por aplicaciones que hayas descargado. Las aplicaciones que tengan este permiso pueden guardar los datos de tus contactos, y las aplicaciones maliciosas puede que compartan estos datos sin que lo sepas."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite que la aplicación consulte datos de los contactos almacenados en tu teléfono. Las aplicaciones también podrán acceder a las cuentas de tu teléfono que hayan creado contactos, y quizá tengan acceso a las cuentas creadas por aplicaciones que hayas descargado. Las aplicaciones que tengan este permiso pueden guardar los datos de tus contactos, y las aplicaciones maliciosas puede que compartan estos datos sin que lo sepas."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modificar tus contactos"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permite que la aplicación modifique los datos de los contactos almacenados en el tablet, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para eliminar datos de contactos."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permite que la aplicación cambie los datos de los contactos almacenados en tu dispositivo Android TV, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto de cualquier otro modo con cada uno. Las aplicaciones que tengan este permiso pueden eliminar datos de contactos."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permite que la aplicación modifique los datos de los contactos almacenados en el teléfono, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para eliminar datos de contactos."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permite que la aplicación cambie los datos de los contactos almacenados en tu tablet. Las aplicaciones que tengan este permiso pueden eliminar datos de contactos."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permite que la aplicación cambie los datos de los contactos almacenados en tu dispositivo Android TV. Las aplicaciones que tengan este permiso pueden eliminar datos de contactos."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permite que la aplicación cambie los datos de los contactos almacenados en tu teléfono. Las aplicaciones que tengan este permiso pueden eliminar datos de contactos."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"leer el registro de llamadas"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Esta aplicación puede leer tu historial de llamadas."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"escribir en el registro de llamadas"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"acceder a comandos de proveedor de ubicación adicional"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permite que la aplicación acceda a otros comandos del proveedor de ubicación. De esta forma, la aplicación podrá interferir en el funcionamiento del GPS o de otras fuentes de ubicación."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"acceder a la ubicación exacta solo en primer plano"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Esta aplicación solo puede obtener tu ubicación exacta cuando está en primer plano. Estos servicios de ubicación deben estar activados y disponibles en tu teléfono para que la aplicación pueda utilizarlos. Es posible que aumente el consumo de batería."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"acceder a la ubicación aproximada (a partir de la red) solo en primer plano"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Esta aplicación puede obtener tu ubicación a partir de fuentes de red como las antenas de telefonía móvil y las redes Wi‑Fi, pero únicamente si la aplicación está en primer plano. Estos servicios de ubicación deben estar activados y disponibles en el tablet para que la aplicación pueda usarlos."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Esta aplicación puede obtener tu ubicación a partir de fuentes de red como las antenas de telefonía móvil y las redes Wi‑Fi, pero únicamente si la aplicación está en primer plano. Estos servicios de ubicación deben estar activados y disponibles en tu dispositivo Android TV para que la aplicación pueda usarlos."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Esta aplicación puede obtener tu ubicación a partir de fuentes de red como las antenas de telefonía móvil y las redes Wi‑Fi, pero únicamente si la aplicación está en primer plano. Estos servicios de ubicación deben estar activados y disponibles en el teléfono para que la aplicación pueda usarlos."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Esta aplicación solo puede obtener tu ubicación exacta cuando está en primer plano. Los servicios de ubicación deben estar activados y disponibles en tu dispositivo para que la aplicación pueda utilizarlos. Es posible que aumente el consumo de batería."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"acceder a la ubicación aproximada solo al estar en primer plano"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Esta aplicación puede obtener tu ubicación aproximada solo cuando está en primer plano. Para que la aplicación pueda utilizar los servicios de ubicación, deben estar activados y disponibles en tu dispositivo."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"acceder a la ubicación en segundo plano"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Si se concede este permiso además del acceso a la ubicación exacta o aproximada, la aplicación podrá acceder a la ubicación mientras se ejecuta en segundo plano."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Esta aplicación puede acceder a la ubicación tanto cuando se ejecuta en segundo plano como cuando está en primer plano."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar la configuración de audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global (por ejemplo, el volumen y el altavoz de salida)."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"grabar sonido"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite que la aplicación acceda a la configuración de Bluetooth del tablet y que establezca y acepte conexiones con los dispositivos sincronizados."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite que la aplicación vea la configuración de Bluetooth de tu dispositivo Android TV y que cree y acepte conexiones con los dispositivos vinculados."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite que la aplicación acceda a la configuración de Bluetooth del teléfono y que establezca y acepte conexiones con los dispositivos sincronizados."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controlar Comunicación de campo cercano (NFC)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permite que la aplicación se comunique con lectores, tarjetas y etiquetas de Comunicación de campo cercano (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"inhabilitar el bloqueo de pantalla"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Conectado a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Toca para ver archivos"</string>
<string name="pin_target" msgid="8036028973110156895">"Fijar"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"No fijar"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Acerca de la aplicación"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demostración…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"¿Actualizar estos elementos en "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" (<xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> y <xliff:g id="TYPE_2">%3$s</xliff:g>)?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Guardar"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No, gracias"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ahora no"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nunca"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Actualizar"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuar"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"contraseña"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Activar o desactivar la pantalla dividida"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Pantalla de bloqueo"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Captura de pantalla"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"La aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> se muestra en una ventana emergente."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de subtítulos de <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 61b18bb..9fdc8e0 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Tööprofiili administraatori rakendus puudub või on rikutud. Seetõttu on teie tööprofiil ja seotud andmed kustutatud. Abi saamiseks võtke ühendust administraatoriga."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Teie tööprofiil pole selles seadmes enam saadaval"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Liiga palju paroolikatseid"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administraator keelas seadme isikliku kasutamise"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Seade on hallatud"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Teie organisatsioon haldab seda seadet ja võib jälgida võrguliiklust. Puudutage üksikasjade vaatamiseks."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Seade kustutatakse"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Võimaldab rakendusel saata püsivaid teadaandeid, mis jäävad pärast ülekande lõppemist alles. Ülemäärane kasutamine võib muuta teie Android TV seadme aeglaseks või ebastabiilseks, põhjustades selle liiga suure mälukasutuse."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Võimaldab rakendusel saata püsivaid edastusi, mis jäävad pärast saate lõppemist alles. Ülemäärane kasutamine võib muuta telefoni aeglaseks või ebastabiilseks, põhjustades selle liiga suure mälukasutuse."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"Kontaktide lugemine"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Võimaldab rakendusel lugeda andmeid teie tahvelarvutisse salvestatud kontaktide kohta, näiteks seda, kui tihti te kellelegi helistate, meilite või nendega muul viisil suhtlete. See luba võimaldab rakendustel salvestada teie kontaktandmeid ja pahatahtlikud rakendused võivad teie teadmata kontaktandmeid jagada."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Lubab rakendusel lugeda kõikide Android TV seadmesse salvestatud kontaktide andmeid, sh seda, kui sageli olete nendele isikutele helistanud, meili saatnud või nendega muul viisil suhelnud. See luba võimaldab rakendustel salvestada teie kontaktandmeid ja pahatahtlikud rakendused võivad teie teadmata kontaktandmeid jagada."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Võimaldab rakendusel lugeda andmeid teie telefoni salvestatud kontaktide kohta, näiteks seda, kui tihti te kellelegi helistate, meilite või nendega muul viisil suhtlete. See luba võimaldab rakendustel salvestada teie kontaktandmeid ja pahatahtlikud rakendused võivad teie teadmata kontaktandmeid jagada."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Lubab rakendusel lugeda tahvelarvutisse salvestatud kontaktide andmeid. Rakendustel on ka juurdepääs teie tahvelarvutis olevatele kontodele, millel on loodud kontakte. See võib hõlmata kontosid, mille lõid teie installitud rakendused. See luba võimaldab rakendustel salvestada teie kontaktandmeid ja pahatahtlikud rakendused võivad teie teadmata kontaktandmeid jagada."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Lubab rakendusel lugeda Android TV seadmesse salvestatud kontaktide andmeid. Rakendustel on ka juurdepääs teie Android TV seadmes olevatele kontodele, millel on loodud kontakte. See võib hõlmata kontosid, mille lõid teie installitud rakendused. See luba võimaldab rakendustel salvestada teie kontaktandmeid ja pahatahtlikud rakendused võivad teie teadmata kontaktandmeid jagada."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Lubab rakendusel lugeda telefoni salvestatud kontaktide andmeid. Rakendustel on ka juurdepääs teie telefonis olevatele kontodele, millel on loodud kontakte. See võib hõlmata kontosid, mille lõid teie installitud rakendused. See luba võimaldab rakendustel salvestada teie kontaktandmeid ja pahatahtlikud rakendused võivad teie teadmata kontaktandmeid jagada."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"muutke oma kontakte"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Võimaldab rakendusel muuta tahvelarvutisse salvestatud kontaktide andmeid, sealhulgas seda, kui tihti olete konkreetsetele kontaktidele helistanud, meilinud või nendega muul viisil suhelnud. See luba võimaldab rakendustel kustutada kontaktandmeid."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Lubab rakendusel muuta kõikide Android TV seadmesse salvestatud kontaktide andmeid, sh seda, kui sageli olete nendele kontaktidele helistanud, meili saatnud või nendega muul viisil suhelnud. See luba võimaldab rakendustel kontaktandmeid kustutada."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Võimaldab rakendusel muuta telefoni salvestatud kontaktide andmeid, sealhulgas seda, kui tihti olete konkreetsetele kontaktidele helistanud, meilinud või nendega muul viisil suhelnud. See luba võimaldab rakendustel kustutada kontaktandmeid."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Lubab rakendusel muuta tahvelarvutisse salvestatud kontaktide andmeid. See luba võimaldab rakendustel kontaktandmeid kustutada."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Lubab rakendusel muuta Android TV seadmesse salvestatud kontaktide andmeid. See luba võimaldab rakendustel kontaktandmeid kustutada."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Lubab rakendusel muuta telefoni salvestatud kontaktide andmeid. See luba võimaldab rakendustel kontaktandmeid kustutada."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"kõnelogi lugemine"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"See rakendus saab teie kõneajalugu lugeda."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"kõnelogi kirjutamine"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"juurdepääs asukohapakkuja lisakäskudele"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Võimaldab rakendusel juurde pääseda asukohapakkuja erikäskudele. See võib lubada rakendusel mõjutada GPS-i või muude asukohaallikate tööd."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"juurdepääs täpsele asukohale ainult esiplaanil"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"See rakendus hangib teie täpse asukoha ainult siis, kui see töötab esiplaanil. Need asukohateenused peavad olema sisse lülitatud ja teie telefonis saadaval, et rakendus saaks neid kasutada. See võib suurendada akutoite tarbimist."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"pääseda juurde ligikaudsele asukohale (võrgupõhiselt) ainult esiplaanil"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"See rakendus näeb võrguallikate (nt mobiilimastid ja WiFi-võrgud) abil teie asukohta, kuid ainult siis, kui rakendus töötab esiplaanil. Need asukohateenused peavad olema sisse lülitatud ja teie tahvelarvutis saadaval, et rakendus saaks neid kasutada."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"See rakendus näeb võrguallikate (nt mobiilimastid ja WiFi-võrgud) abil teie asukohta, kuid ainult siis, kui rakendus töötab esiplaanil. Need asukohateenused peavad olema sisse lülitatud ja teie Android TV seadmes saadaval, et rakendus saaks neid kasutada."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"See rakendus näeb võrguallikate (nt mobiilimastid ja WiFi-võrgud) abil teie asukohta, kuid ainult siis, kui rakendus töötab esiplaanil. Need asukohateenused peavad olema sisse lülitatud ja teie telefonis saadaval, et rakendus saaks neid kasutada."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"See rakendus hangib teie täpse asukoha ainult siis, kui see töötab esiplaanil. Asukohateenused peavad olema sisse lülitatud ja teie seadmes saadaval, et rakendus saaks neid kasutada. See võib suurendada akutoite tarbimist."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"juurdepääs ligikaudsele asukohale ainult esiplaanil"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"See rakendus hangib teie ligikaudse asukoha ainult siis, kui see töötab esiplaanil. Asukohateenused peavad olema sisse lülitatud ja teie seadmes saadaval, et rakendus saaks neid kasutada."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"juurdepääs asukohale taustal"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Kui see on antud ka umbkaudsele või täpsele asukohale juurdepääsu puhul, saab rakendus taustal käitamisel juurdepääsu asukohale."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"See rakendus pääseb asukohale lisaks esiplaanil töötamise ajal juurde ka taustal töötamise ajal."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"muuda heliseadeid"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Võimaldab rakendusel muuta üldiseid heliseadeid, näiteks helitugevust ja seda, millist kõlarit kasutatakse väljundiks."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"salvesta heli"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Võimaldab rakendusel vaadata tahvelarvuti Bluetooth-konfiguratsiooni ning luua ja heaks kiita ühendusi seotud seadmetega."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Võimaldab rakendusel vaadata Android TV seadme Bluetoothi seadistust ning luua ja vastu võtta ühendusi seotud seadmetega."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Võimaldab rakendusel vaadata telefoni Bluetooth-konfiguratsiooni ning luua ja heaks kiita ühendusi seotud seadmetega."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"lähiväljaside juhtimine"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Võimaldab rakendusel suhelda lähiväljaside (NFC) märgendite, kaartide ja lugeritega."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"keelake ekraanilukk"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Ühendatud seadmega <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Failide vaatamiseks puudutage"</string>
<string name="pin_target" msgid="8036028973110156895">"Kinnita"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Vabasta"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Rakenduse teave"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Demo käivitamine …"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Kas värskendada teenuses "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" neid üksusi: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ja <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Salvesta"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Tänan, ei"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Hiljem"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Mitte kunagi"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Värskenda"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Jätka"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"parool"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Vaheta jagatud ekraanikuva"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lukustuskuva"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Ekraanipilt"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> on hüpikaknas."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> pealkirjariba."</string>
</resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index fda8565..c5e09ae 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Laneko profila administratzeko aplikazioa falta da edo hondatuta dago. Ondorioz, ezabatu egin dira laneko profila bera eta harekin erlazionatutako datuak. Laguntza lortzeko, jarri administratzailearekin harremanetan."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Laneko profila ez dago erabilgarri gailu honetan"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Gehiegitan saiatu zara pasahitza idazten"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Erabilera pertsonalerako utzi du gailua administratzaileak"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Jabeak kudeatzen du gailua"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Erakundeak kudeatzen du gailua eta baliteke sareko trafikoa gainbegiratzea. Sakatu hau xehetasunak ikusteko."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Gailuko datuak ezabatu egingo dira"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Igorpen iraunkorrak egiteko baimena ematen die aplikazioei. Igorpena amaitu ondoren ere igortzen jarraitzen dute igorpen iraunkorrek. Gehiegi erabiliz gero, Android TV gailua motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Igorpen iraunkorrak emateko baimena ematen die; horiek igorpena amaitu ondoren mantentzen dira. Gehiegi erabiliz gero, telefonoa motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"irakurri kontaktuak"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Tabletan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen die aplikazioei, besteak beste, pertsona zehatzei zer maiztasunekin deitu diezun, mezu elektronikoak bidali dizkiezun edo haiekin harremanetan beste modutara nola jarri zaren. Baimen horrekin, aplikazioek kontaktuen datuak gorde ditzakete, eta aplikazio gaiztoek haiek parteka ditzakete zuk jakin gabe."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Android TV gailuan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen die aplikazioei; besteak beste, pertsona zehatzei zer maiztasunekin deitu diezun edo bidali dizkiezun mezu elektronikoak, edo haiekin zer beste modutara jarri zaren harremanetan. Baimen horrekin, aplikazioek kontaktuen datuak gorde ditzakete, eta baliteke aplikazio gaiztoek zuk jakin gabe partekatzea datu horiek."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Telefonoan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen die aplikazioei, besteak beste, pertsona zehatzei zer maiztasunekin deitu diezun, mezu elektronikoak bidali dizkiezun edo haiekin harremanetan beste modutara nola jarri zaren. Baimen horrekin, aplikazioek kontaktuen datuak gorde ditzakete, eta aplikazio gaiztoek haiek parteka ditzakete zuk jakin gabe."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke aplikazio gaiztoek zuk jakin gabe partekatzea datu horiek."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke aplikazio gaiztoek zuk jakin gabe partekatzea datu horiek."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko dituzte aplikazioek. Horrek barnean hartuko ditu instalatutako aplikazioek sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzakete aplikazioek, eta baliteke aplikazio gaiztoek zuk jakin gabe partekatzea datu horiek."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"aldatu kontaktuak"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Tabletan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen die aplikazioei, besteak beste, kontatu zehatzei zer maiztasunekin deitu diezun, mezu elektronikoak bidali dizkiezun edo haiekin harremanetan beste modutara nola jarri zaren. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Android TV gailuan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen die aplikazioei; besteak beste, kontaktu zehatzei zer maiztasunekin deitu diezun edo bidali dizkiezun mezu elektronikoak, edo haiekin harremanetan zer beste modutara jarri zaren. Baimen horrekin, kontaktuen datuak ezaba ditzakete aplikazioek."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Telefonoan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen die aplikazioei, besteak beste, kontatu zehatzei zer maiztasunekin deitu diezun, mezu elektronikoak bidali dizkiezun edo haiekin harremanetan beste modutara nola jarri zaren. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tabletan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV gailuan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Telefonoan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioek kontaktuen datuak ezaba ditzakete."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"irakurri deien erregistroa"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Aplikazioak deien historia irakur dezake."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"idatzi deien erregistroan"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"atzitu kokapen-hornitzaileen komando gehigarriak"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Kokapen-hornitzailearen agindu gehigarriak atzitzeko baimena ematen die aplikazioei. Horrela, agian aplikazioek GPSaren edo bestelako kokapenaren iturburuen funtzionamenduan eragina izan dezakete."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"lortu kokapen zehatza aurreko planoan bakarrik"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Aplikazioak zure kokapen zehatza lor dezake aurreko planoan funtzionatzen duenean bakarrik. Kokapen-zerbitzu horiek aktibatuta eta erabilgarri izan behar dituzu telefonoan, aplikazioak erabil ditzan. Baliteke bateria gehiago erabiltzea."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"Atzitu sarean oinarritutako gutxi gorabeherako kokapena aurreko planoan bakarrik"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Aplikazioa aurreko planoan dagoenean, zure kokapenaren berri izan dezake sareen iturburuak erabilita; adibidez, telefonia mugikorreko dorreak eta Wi-Fi sareak. Kokapen-zerbitzu horiek aktibatuta eta erabilgarri izan behar dituzu tabletan, aplikazioak erabil ditzan."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Aurreko planoan daudenean, aplikazioek zure kokapenaren berri izan dezakete sareen iturburuak erabilita; adibidez, telefonia mugikorreko dorreak eta wifi-sareak. Kokapen-zerbitzu horiek aktibatuta eta erabilgarri izan behar dituzu Android TV gailuan, aplikazioek erabil ditzaten."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Aplikazioa aurreko planoan dagoenean, zure kokapenaren berri izan dezake sareen iturburuak erabilita; adibidez, telefonia mugikorreko dorreak eta Wi-Fi sareak. Kokapen-zerbitzu horiek aktibatuta eta erabilgarri izan behar dituzu telefonoan, aplikazioak erabil ditzan."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Aplikazioak aurreko planoan funtzionatzen duenean bakarrik lor dezake zure kokapen zehatza. Kokapen-zerbitzuak aktibatuta eta erabilgarri eduki behar dituzu gailuan, aplikazioak erabil ditzan. Baliteke bateria gehiago erabiltzea."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"atzitu gutxi gorabeherako kokapena aurreko planoan bakarrik"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Aplikazioak aurreko planoan funtzionatzen duenean bakarrik lor dezake zure gutxi gorabeherako kokapena. Kokapen-zerbitzuak aktibatuta eta erabilgarri eduki behar dituzu gailuan, aplikazioak erabil ditzan."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"Atzitu kokapena atzeko planoan"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Baimen hau ematen bada kokapen zehatz edo gutxi gorabeherakorako sarbideaz gain, atzeko planoan abian den bitartean atzitu ahalko du aplikazioak kokapena."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Aurreko planoan egotean bezala, aplikazioak kokapena atzi dezake atzeko planoan egotean."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"aldatu audio-ezarpenak"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Audio-ezarpen orokorrak aldatzeko baimena ematen dio; besteak beste, bolumena eta irteerarako zer bozgorailu erabiltzen den."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"grabatu audioa"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Tabletaren Bluetooth konfigurazioa ikusteko eta parekatutako gailuekin konexioak egiteko eta onartzeko baimena ematen die aplikazioei."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Android TV gailuaren Bluetooth konexioaren konfigurazioa ikusteko eta parekatutako gailuekin konexioak sortzeko eta onartzeko baimena ematen die aplikazioei."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Telefonoaren Bluetooth konfigurazioa ikusteko eta parekatutako gailuekin konexioak egiteko eta onartzeko baimena ematen die aplikazioei."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontrolatu Near Field Communication komunikazioa"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Near Field Communication (NFC) etiketekin, txartelekin eta irakurgailuekin komunikatzea baimentzen die aplikazioei."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"desgaitu pantailaren blokeoa"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> zerbitzura konektatuta"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Sakatu fitxategiak ikusteko"</string>
<string name="pin_target" msgid="8036028973110156895">"Ainguratu"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Kendu aingura"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Aplikazioari buruzko informazioa"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Demoa abiarazten…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" zerbitzuan eguneratu nahi dituzu <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> eta <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Gorde"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ez, eskerrik asko"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Orain ez"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Inoiz ez"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Eguneratu"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Egin aurrera"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"pasahitza"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Aktibatu/Desaktibatu pantaila zatitua"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Pantaila blokeatua"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Pantaila-argazkia"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioa dago leiho gainerakor batean"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioko azpitituluen barra."</string>
</resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 433d4f3..96efcab 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"برنامه سرپرست نمایه کاری یا وجود ندارد یا خراب است. در نتیجه، نمایه کاری شما و دادههای مرتبط با آن حذف شده است. برای دریافت راهنمایی با سرپرست سیستم تماس بگیرید."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"نمایه کاری شما دیگر در این دستگاه دردسترس نیست"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"تلاشهای بسیار زیادی برای وارد کردن گذرواژه انجام شده است"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"سرپرست از این دستگاه برای استفاده شخصی چشمپوشی کرد"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"دستگاه مدیریت میشود"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"سازمانتان این دستگاه را مدیریت میکند و ممکن است ترافیک شبکه را پایش کند. برای اطلاع از جزئیات، ضربه بزنید."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"دستگاهتان پاک خواهد شد"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"به برنامه اجازه میدهد همهفرستیهای چسبان ارسال کند که پس از اتمام همهفرستی باقی میمانند. استفاده بیشازحد از این مجوز میتواند باعث استفاده خیلی زیاد از حافظه شود که به کندی یا ناپایداری Android TV منجر میشود."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"به برنامه اجازه میدهد تا پخشهای ماندگار را که پس از اتمام پخش باقی میمانند ارسال کند. استفاده بیش از حد این ویژگی ممکن است باعث مصرف بیش از حد حافظه و در نتیجه کندی یا ناپایداری تلفن شود."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"خواندن مخاطبین شما"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیره شده در رایانهٔ لوحی شما را بخواند از جمله، تعداد دفعات تماسهایی که برقرار کردهاید، ایمیلهایی که ارسال کردهاید یا به روشهای دیگری به افراد خاصی ارتباط برقرار کردهاید. این با برنامهها امکان میدهد دادههای مخاطب شما را ذخیره کنند و برنامههای مخرب ممکن است دادههای مخاطب را بدون اطلاع شما به اشتراک بگذارند."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیرهشده در دستگاه Android TV شما را بخواند، ازجمله تعداد دفعاتی که با افراد خاصی تماس گرفتهاید، برایشان ایمیل ارسال کردهاید، یا به روشهای دیگری با آنها ارتباط برقرار کردهاید. این مجوز به برنامهها اجازه میدهد دادههای مخاطب شما را ذخیره کنند، و ممکن است برنامههای مخرب بدون اطلاع شما دادههای مخاطب را همرسانی کنند."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیره شده در تلفن شما را بخواند از جمله، تعداد دفعات تماسهایی که برقرار کردهاید، ایمیلهایی که ارسال کردهاید یا به روشهای دیگری با افراد خاصی ارتباط برقرار کردهاید. این به برنامهها امکان میدهد دادههای مخاطب شما را ذخیره کنند و برنامههای مخرب ممکن است دادههای مخاطب را بدون اطلاع شما به اشتراک بگذارند."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیرهشده در رایانه لوحی شما را بخواند. برنامهها همچنین به حسابهای موجود در رایانه لوحی که مخاطبین ایجاد کردهاند دسترسی خواهند داشت. میتواند شامل حسابهایی باشد که توسط برنامههایی که نصب کردهاید، ایجاد شده است. این مجوز به برنامهها اجازه میدهد دادههای مخاطب شما را ذخیره کنند، و ممکن است برنامههای مخرب بدون اطلاع شما دادههای مخاطب را همرسانی کنند."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیرهشده در دستگاه Android TV شما را بخواند. برنامهها همچنین به حسابهای موجود در دستگاه Android TV که مخاطبین ایجاد کردهاند دسترسی خواهد داشت. میتواند شامل حسابهایی باشد که توسط برنامههایی که نصب کردهاید، ایجاد شده است. این مجوز به برنامهها اجازه میدهد دادههای مخاطب شما را ذخیره کنند، و ممکن است برنامههای مخرب بدون اطلاع شما دادههای مخاطب را همرسانی کنند."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیرهشده در تلفن را بخواند. برنامهها همچنین به حسابهای موجود در تلفن که مخاطبین ایجاد کردهاند دسترسی خواهند داشت. میتواند شامل حسابهایی باشد که توسط برنامههایی که نصب کردهاید، ایجاد شده است. این مجوز به برنامهها اجازه میدهد دادههای مخاطب شما را ذخیره کنند، و ممکن است برنامههای مخرب بدون اطلاع شما دادههای مخاطب را همرسانی کنند."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"تغییر مخاطبین"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیره شده در رایانهٔ لوحی شما را از جمله تعداد تماسهایی که برقرار کردهاید، ایمیلهایی که ارسال کردهاید یا ارتباطاتی را که به هر شکل با مخاطبین خاصی برقرار کردید تغییر دهد. این مجوز به برنامه اجازه میدهد دادههای مخاطب را حذف نماید."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیرهشده در دستگاه Android TV شما را تغییر دهد، ازجمله تعداد دفعاتی که با مخاطبین خاصی تماس گرفتهاید، برایشان ایمیل ارسال کردهاید، یا به روشهای دیگری با آنها ارتباط برقرار کردهاید. این مجوز به برنامهها اجازه میدهد دادههای مخاطب را حذف کنند."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیره شده در تلفن شما را از جمله تعداد تماسهایی که برقرار کردهاید، ایمیلهایی که ارسال کردهاید یا ارتباطاتی را که به هر شکل با مخاطبین خاصی برقرار کردید تغییر دهد. این مجوز به برنامه اجازه میدهد دادههای مخاطب را حذف نماید."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیرهشده در رایانه لوحی شما را تغییر دهد. این مجوز به برنامهها اجازه میدهد دادههای مخاطب را حذف کنند."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیرهشده در دستگاه Android TV شما را تغییر دهد. این مجوز به برنامهها اجازه میدهد دادههای مخاطب را حذف کنند."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"به برنامه اجازه میدهد دادههای مربوط به مخاطبین ذخیرهشده در تلفنتان را تغییر دهد. این مجوز به برنامهها اجازه میدهد دادههای مخاطب را حذف کنند."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"خواندن گزارش تماس"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"این برنامه میتواند سابقه تماس شما را بخواند."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"نوشتن گزارش تماس"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"دسترسی به فرمانهای بیشتر ارائه دهنده مکان"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"به برنامه اجازه میدهد به دستورات ارائهدهنده مکان تکمیلی دسترسی داشته باشد. این کار ممکن است به برنامه امکان دهد با کارکرد GPS یا منابع دیگر مکان تداخل داشته باشد."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"دسترسی به مکان دقیق فقط در پیشزمینه"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"این برنامه فقط زمانی میتواند موقعیت مکانی دقیق شما را دریافت کند که در پیشزمینه باشد. برای اینکه برنامه بتواند از خدمات مکان استفاده کند، این خدمات باید در تلفنتان روشن و دردسترس باشد. ممکن است با این کار مصرف باتری افزایش یابد."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"دسترسی به مکان تقریبی (مبتنی بر شبکه) فقط در پیشزمینه"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"این برنامه میتواند براساس منابع شبکه مانند دکلهای مخابراتی و شبکههای Wi-Fi، مکانتان را تشخیص دهد، اما فقط درصورتیکه برنامه در پیشزمینه باشد. این خدمات مکان باید روشن و در رایانه لوحی شما دردسترس باشند تا برنامه بتواند از آنها استفاده کند."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"این برنامه میتواند براساس منابع شبکه، مانند دکلهای مخابراتی و شبکههای Wi-Fi، مکانتان را تشخیص دهد، اما فقط درصورتیکه برنامه در پیشزمینه باشد. این خدمات مکان باید در دستگاه Android TV شما روشن و دردسترس باشد تا برنامه بتواند از آنها استفاده کند."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"این برنامه میتواند براساس منابع شبکه مانند دکلهای مخابراتی و شبکههای Wi-Fi، مکانتان را تشخیص دهد، اما فقط درصورتیکه برنامه در پیشزمینه است. این خدمات مکان باید روشن و در تلفن شما دردسترس باشند تا برنامه بتواند از آنها استفاده کند."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"این برنامه فقط زمانی میتواند موقعیت مکانی دقیق شما را دریافت کند که در پیشزمینه باشد. برای اینکه برنامه بتواند از خدمات مکان استفاده کند، این خدمات باید در دستگاهتان روشن و در دسترس باشد. ممکن است با این کار مصرف باتری افزایش یابد."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"دسترسی به مکان تقریبی فقط در پیشزمینه"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"این برنامه فقط هنگامیکه در پیشزمینه است میتواند مکان تقریبی شما را دریافت کند. برای اینکه برنامه بتواند از خدمات مکان استفاده کند، این خدمات باید روشن و در دستگاهتان در دسترس باشند."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"دسترسی به مکان در پسزمینه"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"اگر این مجوز نیز برای دسترسی دقیق یا تقریبی به مکان داده شود، برنامه میتواند درحین اجرا در پسزمینه به مکان دسترسی پیدا کند."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"این برنامه علاوهبر دسترسی به مکان در پیشزمینه، میتواند هنگام اجرا در پسزمینه نیز به مکان دسترسی داشته باشد."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"تغییر تنظیمات صوتی"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"به برنامه امکان میدهد تنظیمات صوتی کلی مانند میزان صدا و بلندگوی مورد استفاده برای پخش صدا را تغییر دهد."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ضبط صدا"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"به برنامه اجازه میدهد تا پیکربندی بلوتوث در رایانهٔ لوحی را مشاهده کند و اتصال با دستگاههای مرتبط را برقرار کرده و بپذیرد."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"به برنامه اجازه میدهد پیکربندی بلوتوث را در دستگاه Android TV شما ببیند، و اتصالات با دستگاههای مرتبطشده را بپذیرد یا این اتصالات را برقرار کند."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"به برنامه اجازه میدهد تا پیکربندی بلوتوث در تلفن را مشاهده کند، و اتصالات دستگاههای مرتبط را برقرار کرده و بپذیرد."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"کنترل ارتباط راه نزدیک"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"به برنامه اجازه میدهد تا با تگهای «ارتباط میدان نزدیک» (NFC)، کارتها و فایلخوان ارتباط برقرار کند."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"غیرفعال کردن قفل صفحه شما"</string>
@@ -1177,7 +1178,7 @@
<string name="screen_compat_mode_scale" msgid="8627359598437527726">"مقیاس"</string>
<string name="screen_compat_mode_show" msgid="5080361367584709857">"همیشه نشان داده شود"</string>
<string name="screen_compat_mode_hint" msgid="4032272159093750908">"در تنظیمات سیستم >برنامهها > مورد بارگیری شده آن را دوباره فعال کنید."</string>
- <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> از تنظیم فعلی اندازه نمایشگر پشتیبانی نمیکند و ممکن است رفتار غیرمنتظرهای داشته باشد."</string>
+ <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> از تنظیم فعلی اندازه نمایش پشتیبانی نمیکند و ممکن است رفتار غیرمنتظرهای داشته باشد."</string>
<string name="unsupported_display_size_show" msgid="980129850974919375">"همیشه نشان داده شود"</string>
<string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> برای نسخه ناسازگار سیستمعامل Android ساخته شده است و ممکن است رفتاری برخلاف انتظار داشته باشد. ممکن است نسخه بهروزی از برنامه در دسترس باشد."</string>
<string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"همیشه نشان داده شود"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"به <xliff:g id="PRODUCT_NAME">%1$s</xliff:g> متصل شد"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"برای دیدن فایلها، ضربه بزنید"</string>
<string name="pin_target" msgid="8036028973110156895">"پین کردن"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"برداشتن پین"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"اطلاعات برنامه"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"در حال شروع نسخه نمایشی…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"این موارد در "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>، <xliff:g id="TYPE_1">%2$s</xliff:g> و <xliff:g id="TYPE_2">%3$s</xliff:g> بهروزرسانی شود؟"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"ذخیره"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"نه سپاسگزارم"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"حالا نه"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"هیچوقت"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"بهروزرسانی"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ادامه"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"گذرواژه"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"تغییر وضعیت صفحهٔ دونیمه"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"صفحه قفل"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"عکس صفحهنمایش"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"برنامه <xliff:g id="APP_NAME">%1$s</xliff:g> در پنجره بالاپر."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"نوار شرح <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 0c7bd77..f7a54fb 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Työprofiilin hallintasovellus puuttuu tai se on vioittunut. Tästä syystä työprofiilisi ja siihen liittyvät tiedot on poistettu. Pyydä ohjeita järjestelmänvalvojaltasi."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Työprofiilisi ei ole enää käytettävissä tällä laitteella."</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Liikaa salasanayrityksiä"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Järjestelmänvalvoja luovutti laitteen henkilökohtaiseen käyttöön"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Hallinnoitu laite"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Organisaatiosi hallinnoi tätä laitetta ja voi tarkkailla verkkoliikennettä. Katso lisätietoja napauttamalla."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Laitteen tiedot poistetaan"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Antaa sovelluksen lähettää pysyviä lähetyksiä, jotka säilyvät lähetyksen päätyttyä. Liiallinen käyttö voi tehdä Android TV ‑laitteesta hitaan tai epävakaan lisäämällä sen muistinkäyttöä liikaa."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Antaa sovelluksen lähettää pysyviä lähetyksiä, jotka säilyvät lähetyksen päätyttyä. Liiallinen käyttö voi tehdä puhelimesta hitaan tai epävakaan kasvattamalla sen muistinkäyttöä liikaa."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lue yhteystietoja"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Antaa sovelluksen lukea tablet-laitteeseesi tallennettuja kontaktitietoja sekä tarkastella, kuinka usein olet soittanut, lähettänyt sähköpostia tai muuten viestinyt tiettyjen kontaktien kanssa. Tämän luvan saaneet sovellukset voivat tallentaa kontaktitietoja. Haitalliset sovellukset voivat jakaa kontaktitietoja ilman lupaasi."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Antaa sovelluksen lukea Android TV ‑laitteeseen tallennettuja kontaktitietoja sekä nähdä, kuinka usein olet soitellut, lähettänyt sähköpostia tai muuten viestinyt tiettyjen kontaktien kanssa. Tämän luvan saaneet sovellukset voivat tallentaa kontaktitietoja. Haitalliset sovellukset voivat jakaa kontaktitietoja ilman lupaasi."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Antaa sovelluksen lukea puhelimeesi tallennettuja kontaktitietoja sekä tarkastella, kuinka usein olet soittanut, lähettänyt sähköpostia tai muuten viestinyt tiettyjen kontaktien kanssa. Tämän luvan saaneet sovellukset voivat tallentaa kontaktitietoja. Haitalliset sovellukset voivat jakaa kontaktitietoja ilman lupaasi."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Antaa sovelluksen lukea tabletille tallennettujen kontaktien tietoja. Sovellukset saavat myös pääsyn tablettisi tileille, joilla on luotu kontaktitietoja. Tilit voivat olla myös asentamiesi sovellusten luomia. Tämän luvan saaneet sovellukset voivat tallentaa kontaktitietoja. Haitalliset sovellukset voivat jakaa kontaktitietoja ilman lupaasi."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Antaa sovelluksen lukea Android TV ‑laitteelle tallennettujen kontaktien tietoja. Sovellukset saavat myös pääsyn Android TV ‑laitteesi tileille, joilla on luotu kontaktitietoja. Tilit voivat olla myös asentamiesi sovellusten luomia. Tämän luvan saaneet sovellukset voivat tallentaa kontaktitietoja. Haitalliset sovellukset voivat jakaa kontaktitietoja ilman lupaasi."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Antaa sovelluksen lukea puhelimelle tallennettujen kontaktien tietoja. Sovellukset saavat myös pääsyn puhelimen tileille, joilla on luotu kontaktitietoja. Tilit voivat olla myös asentamiesi sovellusten luomia. Tämän luvan saaneet sovellukset voivat tallentaa kontaktitietoja. Haitalliset sovellukset voivat jakaa kontaktitietoja ilman lupaasi."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"muokkaa yhteystietoja"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Antaa sovelluksen muokata tablet-laitteeseesi tallennettuja kontaktitietoja sekä tarkastella, kuinka usein olet soittanut, lähettänyt sähköpostia tai muuten viestinyt tiettyjen kontaktien kanssa. Tämän luvan saaneet sovellukset voivat poistaa kontaktitietoja."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Antaa sovelluksen muokata Android TV ‑laitteeseen tallennettujen kontaktiesi dataa sekä nähdä, kuinka usein olet soitellut, lähettänyt sähköpostia tai muuten viestinyt tiettyjen kontaktien kanssa. Tämän luvan saaneet sovellukset voivat poistaa kontaktitietoja."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Antaa sovelluksen muokata puhelimeesi tallennettuja kontaktitietoja sekä tarkastella, kuinka usein olet soittanut, lähettänyt sähköpostia tai muuten viestinyt tiettyjen kontaktien kanssa. Tämän luvan saaneet sovellukset voivat poistaa kontaktitietoja."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Antaa sovelluksen muokata tabletille tallennettujen kontaktien tietoja. Käyttöluvan saaneet sovellukset voivat myös poistaa kontaktitietoja."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Antaa sovelluksen muokata Android TV ‑laitteelle tallennettujen kontaktien tietoja. Käyttöluvan saaneet sovellukset voivat myös poistaa kontaktitietoja."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Antaa sovelluksen muokata puhelimelle tallennettujen kontaktien tietoja. Käyttöluvan saaneet sovellukset voivat myös poistaa kontaktitietoja."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lue puhelulokia"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Tämä sovellus voi lukea puheluhistoriaasi."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"kirjoita puhelulokiin"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"käytä lisää sijainnintarjoajakomentoja"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Antaa sovelluksen käyttää ylimääräisiä sijaintipalvelukomentoja. Sovellus saattaa tällöin häiritä GPS:n tai muiden sijaintilähteiden toimintaa."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"käyttää tarkkaa sijaintia vain etualalla"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Tämä sovellus saa tarkat sijaintitietosi käyttöönsä vain etualalla. Näiden sijaintipalveluiden tulee olla käytössä ja käytettävissä puhelimellasi, jotta sovellus voi käyttää niitä. Tämä voi lisätä akun kulutusta."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"käyttää karkeaa verkkoon perustuvaa sijaintia vain etualalla"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Tämä sovellus voi määrittää sijaintisi matkapuhelinverkon tukiasemien, Wi-Fi-verkkojen ja muiden verkkolähteiden perusteella vain jos sovellus on etualalla. Näiden sijaintipalveluiden tulee olla päällä ja käytettävissä tabletilla, jotta sovellus voi käyttää niitä."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Tämä sovellus voi määrittää sijaintisi matkapuhelinverkon tukiasemien, Wi-Fi-verkkojen ja muiden verkkolähteiden perusteella vain jos sovellus on etualalla. Näiden sijaintipalveluiden tulee olla päällä ja käytettävissä Android TV ‑laitteessa, jotta sovellus voi käyttää niitä."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Tämä sovellus voi määrittää sijaintisi matkapuhelinverkon tukiasemien, Wi-Fi-verkkojen ja muiden verkkolähteiden perusteella vain jos sovellus on etualalla. Näiden sijaintipalveluiden tulee olla päällä ja käytettävissä puhelimessa, jotta sovellus voi käyttää niitä."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Tämä sovellus saa tarkat sijaintitietosi käyttöönsä vain etualalla. Sijaintipalveluiden tulee olla päällä ja käytettävissä laitteella, jotta sovellus voi käyttää niitä. Tämä voi lisätä akun kulutusta."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"käyttää karkeaa sijaintia vain etualalla"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Tämä sovellus saa karkean sijaintisi käyttöönsä vain ollessaan etualalla. Sijaintipalveluiden tulee olla päällä ja käytettävissä laitteellasi, jotta sovellus voi käyttää niitä."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"käytä sijaintia taustalla"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Jos tämä myönnetään karkean tai tarkan sijainnin käyttöoikeuden lisäksi, sovellus voi käyttää sijaintia toimiessaan taustalla."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Tämä sovellus voi käyttää sijaintia taustalla ja etualalla."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"muuta ääniasetuksia"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Antaa sovelluksen muokata yleisiä ääniasetuksia, kuten äänenvoimakkuutta ja käytettävää kaiutinta."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"tallentaa ääntä"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Antaa sovelluksen tarkastella tablet-laitteen Bluetooth-asetuksia sekä muodostaa ja hyväksyä laitepariyhteyksiä."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Antaa sovelluksen nähdä Android TV ‑laitteen Bluetooth-asetukset sekä muodostaa ja hyväksyä laitepariyhteyksiä."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Antaa sovelluksen tarkastella puhelimen Bluetooth-asetuksia sekä muodostaa ja hyväksyä laitepariyhteyksiä muihin laitteisiin."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"hallitse Near Field Communication -tunnistusta"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Antaa sovelluksen kommunikoida NFC (Near Field Communication) -tagien, -korttien ja -lukijoiden kanssa."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"poista näytön lukitus käytöstä"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> yhdistetty"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Näytä tiedostot koskettamalla"</string>
<string name="pin_target" msgid="8036028973110156895">"Kiinnitä"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Irrota"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Sovelluksen tiedot"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Aloitetaan esittelyä…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Päivitetäänkö nämä ("<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"): <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ja <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Tallenna"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ei kiitos"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ei nyt"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Ei koskaan"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Muuta"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Jatka"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"salasana"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Jaettu näyttö päälle/pois"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lukitusnäyttö"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Kuvakaappaus"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Sovellus (<xliff:g id="APP_NAME">%1$s</xliff:g>) ponnahdusikkunassa"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Tekstityspalkki: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 3c8f75f..e7eafa7 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Le profil professionnel de l\'application d\'administration est manquant ou corrompu. Votre profil professionnel et ses données connexes ont donc été supprimés. Communiquez avec votre administrateur pour obtenir de l\'assistance."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Votre profil professionnel n\'est plus accessible sur cet appareil"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Trop de tentatives d\'entrée du mot de passe"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrateur a libéré l\'appareil pour un usage personnel"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"L\'appareil est géré"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Votre organisation gère cet appareil et peut surveiller le trafic réseau. Touchez ici pour obtenir plus d\'information."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Le contenu de votre appareil sera effacé"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permet à l\'application d\'envoyer des intentions de diffusion persistantes, qui perdurent une fois la diffusion terminée. Une utilisation excessive peut ralentir votre appareil Android TV ou le rendre instable en l\'obligeant à utiliser trop de mémoire."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permet à l\'application d\'envoyer des intentions de diffusion \"persistantes\", qui perdurent une fois la diffusion terminée. Une utilisation excessive peut ralentir le téléphone ou le rendre instable en l\'obligeant à utiliser trop de mémoire."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lire vos contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permet à l\'application de lire les données relatives aux contacts stockés sur votre tablette, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des courriels ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications d\'enregistrer ces données. Les applications malveillantes peuvent les partager à votre insu."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permet à l\'application de lire les données relatives aux contacts stockés sur votre appareil Android TV, y compris la fréquence à laquelle vous avez appelé certaines personnes, leur avez envoyé des courriels ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications d\'enregistrer ces données. Les applications malveillantes peuvent les partager à votre insu."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permet à l\'application de lire les données relatives aux contacts stockés sur votre téléphone, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des courriels ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications d\'enregistrer ces données. Les applications malveillantes peuvent les partager à votre insu."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permet à l\'application de lire les données relatives aux contacts stockés sur votre tablette. Les applications auront aussi accès aux comptes sur votre tablette qui ont créé des contacts. Cela peut comprendre des comptes créés par des applications que vous avez installées. Cette autorisation permet aux applications d\'enregistrer ces données. Les applications malveillantes peuvent les partager à votre insu."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permet à l\'application de lire les données relatives aux contacts stockés sur votre appareil Android TV. Les applications auront aussi accès aux comptes sur votre appareil Android TV qui ont créé des contacts. Cela peut comprendre des comptes créés par des applications que vous avez installées. Cette autorisation permet aux applications d\'enregistrer ces données. Les applications malveillantes peuvent les partager à votre insu."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permet à l\'application de lire les données relatives aux contacts stockés sur votre téléphone. Les applications auront aussi accès aux comptes sur votre téléphone qui ont créé des contacts. Cela peut comprendre des comptes créés par des applications que vous avez installées. Cette autorisation permet aux applications d\'enregistrer ces données. Les applications malveillantes peuvent les partager à votre insu."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modifier vos contacts"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permet à l\'application de modifier les données relatives aux contacts stockés sur votre tablette, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des courriels ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications de supprimer ces données."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permet à l\'application de modifier les données relatives aux contacts stockés sur votre appareil Android TV, y compris la fréquence à laquelle vous avez appelé certaines personnes, leur avez envoyé des courriels ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications de supprimer ces données."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permet à l\'application de modifier les données relatives aux contacts stockés sur votre téléphone, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des courriels ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications de supprimer ces données."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permet à l\'application de modifier les données relatives aux contacts stockés sur votre tablette. Cette autorisation permet aux applications de supprimer des données relatives aux contacts."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permet à l\'application de modifier les données relatives aux contacts stockés sur votre appareil Android TV. Cette autorisation permet aux applications de supprimer des données relatives aux contacts."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permet à l\'application de modifier les données relatives aux contacts stockés sur votre téléphone. Cette autorisation permet aux applications de supprimer des données relatives aux contacts."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lire le journal d\'appels"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Cette application peut lire votre historique d\'appel."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"modifier le journal d\'appels"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"accéder aux commandes de fournisseur de position géographique supplémentaires"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permet à l\'application d\'accéder à des commandes de localisation supplémentaires offertes par le fournisseur. Elle est ainsi susceptible d\'interférer avec le bon fonctionnement du GPS ou de toute autre source de localisation."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"accéder à votre position précise seulement en avant-plan"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Cette application peut obtenir votre position exacte seulement lorsqu\'elle fonctionne en avant-plan. Ces services de localisation doivent être activés et accessibles sur votre téléviseur pour que l\'application puisse les utiliser. Cela peut entraîner une utilisation accrue de la pile."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"accéder à la position approximative (selon les données réseau), mais uniquement lorsque l\'application s\'exécute au premier plan"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Cette application peut déterminer votre position à l\'aide de différentes sources de localisation sur le réseau, comme les tours de téléphonie cellulaire et les réseaux Wi-Fi, mais uniquement lorsqu\'elle s\'exécute au premier plan. Ces services de localisation doivent être activés et accessibles sur votre tablette pour que l\'application puisse les utiliser."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Cette application peut déterminer votre position à l\'aide de différentes sources de localisation sur le réseau, comme les tours de téléphonie cellulaire et les réseaux Wi-Fi, mais uniquement lorsqu\'elle s\'exécute au premier plan. Ces services de localisation doivent être activés et accessibles sur votre appareil Android TV pour que l\'application puisse les utiliser."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Cette application peut déterminer votre position à l\'aide de différentes sources de localisation sur le réseau, comme les tours de téléphonie cellulaire et les réseaux Wi-Fi, mais uniquement lorsqu\'elle s\'exécute au premier plan. Ces services de localisation doivent être activés et accessibles sur votre téléphone pour que l\'application puisse les utiliser."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Cette application peut obtenir votre position exacte seulement lorsqu\'elle fonctionne en avant-plan. Les services de localisation doivent être activés et accessibles sur votre appareil pour que l\'application puisse les utiliser. Cela peut entraîner une utilisation accrue de la pile."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"accéder à votre position approximative seulement en avant-plan"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Cette application peut seulement obtenir votre position approximative lorsqu\'elle fonctionne en avant-plan. Les services de localisation doivent être activés et accessibles sur votre appareil pour que l\'application puisse les utiliser."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"accès à la localisation en arrière-plan"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Si cette autorisation est accordée en plus de l\'accès approximatif ou précis à la localisation, alors l\'application peut accéder à la localisation lorsqu\'elle fonctionne en arrière-plan."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Cette application peut accéder à la position en arrière-plan, en plus d\'en avant-plan."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifier vos paramètres audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"enregistrer des fichiers audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permet à l\'application d\'accéder à la configuration du Bluetooth sur la tablette, et d\'établir et accepter des connexions avec les appareils associés."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permet à l\'application d\'afficher la configuration du Bluetooth sur votre appareil Android TV, de se connecter à des appareils associés et d\'accepter leur connexion."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permet à l\'application d\'accéder à la configuration du Bluetooth sur le téléphone, et d\'établir et accepter des connexions avec les appareils associés."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"gérer la communication en champ proche"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permet à l\'application de communiquer avec des bornes, des cartes et des lecteurs compatibles avec la technologie NFC (communication en champ proche)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"désactiver le verrouillage de l\'écran"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Connecté à <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Touchez ici pour afficher les fichiers"</string>
<string name="pin_target" msgid="8036028973110156895">"Épingler"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Annuler l\'épinglage"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Détails de l\'application"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Démarrage de la démonstration en cours…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Mettre à jour ces éléments sous "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" : <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> et <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Enregistrer"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Non, merci"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Pas maintenant"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Jamais"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Mettre à jour"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuer"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"mot de passe"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Basculer l\'écran partagé"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Écran de verrouillage"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Capture d\'écran"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Application <xliff:g id="APP_NAME">%1$s</xliff:g> dans une fenêtre contextuelle."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barre de légende de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 1b53bd9..f9ec0c2 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"L\'application d\'administration du profil professionnel est manquante ou endommagée. Par conséquent, votre profil professionnel et toutes les données associées ont été supprimés. Pour obtenir de l\'aide, contactez l\'administrateur."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Votre profil professionnel n\'est plus disponible sur cet appareil"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Trop de tentatives de saisie du mot de passe"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrateur a mis l\'appareil à disposition pour un usage personnel"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"L\'appareil est géré"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Votre organisation gère cet appareil et peut surveiller le trafic réseau. Appuyez ici pour obtenir plus d\'informations."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Les données de votre appareil vont être effacées"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permet à l\'application d\'envoyer des diffusions \"persistantes\", qui perdurent une fois la diffusion effectuée. Une utilisation excessive peut ralentir votre appareil Android TV ou le rendre instable en l\'obligeant à utiliser trop de mémoire."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permet à l\'application d\'envoyer des intentions de diffusion \"persistantes\", qui perdurent une fois la diffusion terminée. Une utilisation excessive peut ralentir le téléphone ou le rendre instable en l\'obligeant à utiliser trop de mémoire."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"Voir les contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permet à l\'application de lire les données relatives aux contacts stockés sur votre tablette, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des e-mails ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications d\'enregistrer ces données. Les applications malveillantes peuvent les partager à votre insu."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permet à l\'application de lire les données liées aux contacts stockés sur votre appareil Android TV, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des e-mails ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications d\'enregistrer ces données. Des applications malveillantes peuvent exploiter cette fonctionnalité pour partager ces données à votre insu."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permet à l\'application de lire les données relatives aux contacts stockés sur votre téléphone, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des e-mails ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications d\'enregistrer ces données. Les applications malveillantes peuvent les partager à votre insu."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permet à l\'application de lire les données liées aux contacts stockés sur votre tablette. Les applications auront également accès aux comptes de votre tablette pour lesquels des contacts ont été créés. Cela peut inclure les comptes créés via des applications que vous avez installées. Cette autorisation permet aux applications d\'enregistrer les données liées à vos contacts. Des applications malveillantes peuvent exploiter cette fonctionnalité pour partager ces données à votre insu."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permet à l\'application de lire les données liées aux contacts stockés sur votre appareil Android TV. Les applications auront également accès aux comptes de votre appareil Android TV pour lesquels des contacts ont été créés. Cela peut inclure les comptes créés via des applications que vous avez installées. Cette autorisation permet aux applications d\'enregistrer les données liées à vos contacts. Des applications malveillantes peuvent exploiter cette fonctionnalité pour partager ces données à votre insu."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permet à l\'application de lire les données liées aux contacts stockés sur votre téléphone. Les applications auront également accès aux comptes de votre téléphone pour lesquels des contacts ont été créés. Cela peut inclure les comptes créés via des applications que vous avez installées. Cette autorisation permet aux applications d\'enregistrer les données liées à vos contacts. Des applications malveillantes peuvent exploiter cette fonctionnalité pour partager ces données à votre insu."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modifier les contacts"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permet à l\'application de modifier les données relatives aux contacts stockés sur votre tablette, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des e-mails ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications de supprimer ces données."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permet à l\'application de modifier les données liées aux contacts stockés sur votre appareil Android TV, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des e-mails ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications de supprimer ces données."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permet à l\'application de modifier les données relatives aux contacts stockés sur votre téléphone, y compris la fréquence à laquelle vous avez appelé des personnes spécifiques, leur avez envoyé des e-mails ou avez communiqué avec elles par d\'autres moyens. Cette autorisation permet aux applications de supprimer ces données."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permet à l\'application de modifier les données liées aux contacts stockés sur votre tablette. Cette autorisation permet aux applications de supprimer ces données."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permet à l\'application de modifier les données liées aux contacts stockés sur votre appareil Android TV. Cette autorisation permet aux applications de supprimer ces données."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permet à l\'application de modifier les données liées aux contacts stockés sur votre téléphone. Cette autorisation permet aux applications de supprimer ces données."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lire le journal d\'appels"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Cette application peut lire l\'historique de vos appels."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"modifier le journal d\'appels"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"Accès aux commandes de fournisseur de position géographique supplémentaires"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permet à l\'application d\'accéder à des commandes de localisation supplémentaires offertes par le fournisseur. Elle est ainsi susceptible d\'interférer avec le bon fonctionnement du GPS ou de toute autre source de localisation."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"accéder à la position exacte au premier plan uniquement"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Cette application peut obtenir votre position exacte uniquement lorsqu\'elle s\'exécute au premier plan. Ces services de localisation doivent être activés et disponibles sur votre téléphone pour que l\'application puisse les utiliser. Ceci peut réduire l\'autonomie de la batterie."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"accéder à la position approximative (à l\'aide des réseaux) au premier plan uniquement"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Cette application peut obtenir votre position à l\'aide de sources de réseau telles que les antennes-relais et les réseaux Wi-Fi, mais uniquement lorsqu\'elle s\'exécute au premier plan. Ces services de localisation doivent être activés et disponibles sur votre tablette pour que l\'application puisse les utiliser."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Cette application peut obtenir votre position à l\'aide de sources de réseau telles que les antennes-relais et les réseaux Wi-Fi, mais uniquement lorsqu\'elle s\'exécute au premier plan. Ces services de localisation doivent être activés et disponibles sur votre appareil Android TV pour que l\'application puisse les utiliser."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Cette application peut obtenir votre position à l\'aide de sources de réseau telles que les antennes-relais et les réseaux Wi-Fi, mais uniquement lorsqu\'elle s\'exécute au premier plan. Ces services de localisation doivent être activés et disponibles sur votre téléphone pour que l\'application puisse les utiliser."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Cette application peut obtenir votre position exacte uniquement lorsqu\'elle s\'exécute au premier plan. Les services de localisation doivent être activés et disponibles sur votre appareil pour que l\'application puisse les utiliser. Ceci peut réduire l\'autonomie de la batterie."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"accéder à la position approximative au premier plan uniquement"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Cette application peut obtenir votre position approximative uniquement lorsqu\'elle s\'exécute au premier plan. Les services de localisation doivent être activés et disponibles sur votre appareil pour que l\'application puisse les utiliser."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"accéder à la position en arrière-plan"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Si vous lui accordez cette autorisation en plus de l\'accès à la position approximative ou précise, l\'application peut accéder à votre position lorsqu\'elle est s\'exécute en arrière-plan."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"En plus de l\'accès à la position en premier plan, cette application peut y accéder lorsqu\'elle s\'exécute en arrière-plan."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifier vos paramètres audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"enregistrer des fichiers audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permet à l\'application d\'accéder à la configuration du Bluetooth sur la tablette, et d\'établir et accepter des connexions avec les appareils associés."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permet à l\'application d\'afficher la configuration du Bluetooth sur votre appareil Android TV, de se connecter à des appareils associés et d\'accepter leur connexion."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permet à l\'application d\'accéder à la configuration du Bluetooth sur le téléphone, et d\'établir et accepter des connexions avec les appareils associés."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"contrôler la communication en champ proche"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permet à l\'application de communiquer avec des tags, des cartes et des lecteurs compatibles avec la technologie NFC (communication en champ proche)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"Désactiver le verrouillage de l\'écran"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Connecté à <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Appuyez ici pour voir les fichiers."</string>
<string name="pin_target" msgid="8036028973110156895">"Épingler"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Retirer"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Infos sur l\'appli"</string>
<string name="negative_duration" msgid="1938335096972945232">"− <xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Lancement de la démo…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Mettre à jour les éléments <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> et <xliff:g id="TYPE_2">%3$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Enregistrer"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Non, merci"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Pas maintenant"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Jamais"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Mettre à jour"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuer"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"mot de passe"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Activer/Désactiver l\'écran partagé"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Verrouiller l\'écran"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Capture d\'écran"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Application <xliff:g id="APP_NAME">%1$s</xliff:g> dans la fenêtre pop-up."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barre de légende de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 4572db7..4ddce8b 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta a aplicación de administración do perfil de traballo ou ben está danada. Como resultado, eliminouse o teu perfil de traballo e os datos relacionados. Para obter asistencia, contacta co administrador."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"O teu perfil de traballo xa non está dispoñible neste dispositivo"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Demasiados intentos de introdución do contrasinal"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador renunciou ao dispositivo para uso persoal"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"O dispositivo está xestionado"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"A túa organización xestiona este dispositivo e pode controlar o tráfico de rede. Toca para obter máis detalles."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Borrarase o teu dispositivo"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permite que a aplicación envíe emisións permanentes que continúan unha vez finalizada a emisión. Un uso excesivo pode provocar que o dispositivo Android TV funcione con lentitude ou de forma inestable debido á necesidade de utilizar demasiada memoria."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permite á aplicación enviar difusións permanentes que continúan unha vez finalizada a difusión. Un uso excesivo pode provocar que o teléfono funcione con lentitude ou de forma inestable debido á necesidade de utilizar demasiada memoria."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ler os teus contactos"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permite á aplicación ler datos acerca dos teus contactos almacenados na tableta, incluída a frecuencia coa que os chamaches, lles enviaches un correo electrónico ou te comunicaches con individuos específicos doutras formas. Con este permiso as aplicacións poden gardar os teus datos de contacto e as aplicacións maliciosas poden compartir os datos de contacto sen o teu coñecemento."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permite que a aplicación lea datos acerca dos contactos que tes almacenados no dispositivo Android TV, incluída a frecuencia coa que lles chamaches, lles enviaches un correo electrónico ou te comunicaches con individuos específicos doutras formas. Con este permiso as aplicacións poden gardar os teus datos de contacto e as aplicacións maliciosas poden compartir os datos de contacto sen o teu coñecemento."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permite á aplicación ler datos acerca dos teus contactos almacenados no teléfono, incluída a frecuencia coa que os chamaches, lles enviaches un correo electrónico ou te comunicaches con individuos específicos doutras formas. Con este permiso as aplicacións poden gardar os teus datos de contacto e as aplicacións maliciosas poden compartir os datos de contacto sen o teu coñecemento."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite que a aplicación lea datos sobre os contactos almacenados na túa tableta. As aplicacións tamén terán acceso ás contas da túa tableta que creasen contactos, entre as que se poden incluír as creadas polas aplicacións que instalases. Con este permiso as aplicacións poden gardar os teus datos de contacto e as aplicacións maliciosas poden compartir os datos mencionados sen o teu coñecemento."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite que a aplicación lea datos sobre os contactos almacenados no teu dispositivo Android TV. As aplicacións tamén terán acceso ás contas do teu dispositivo Android TV que creasen contactos, entre as que se poden incluír as creadas polas aplicacións que instalases. Con este permiso as aplicacións poden gardar os teus datos de contacto e as aplicacións maliciosas poden compartir os datos mencionados sen o teu coñecemento."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite que a aplicación lea datos sobre os contactos almacenados no teu teléfono. As aplicacións tamén terán acceso ás contas do teu teléfono que creasen contactos, entre as que se poden incluír as creadas polas aplicacións que instalases. Con este permiso as aplicacións poden gardar os teus datos de contacto e as aplicacións maliciosas poden compartir os datos mencionados sen o teu coñecemento."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modificar os teus contactos"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permite á aplicación modificar os datos acerca dos teus contactos almacenados na tableta, incluído a frecuencia coa que os chamaches, lles enviaches un correo electrónico ou te comunicaches con contactos específicos doutras formas. Con este permiso as aplicacións poden eliminar datos de contactos."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permite que a aplicación modifique os datos sobre os contactos almacenados no dispositivo Android TV, incluída a frecuencia coa que lles chamaches, lles enviaches un correo electrónico ou te comunicaches con eles doutra forma. Con este permiso as aplicacións poden eliminar datos de contactos."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permite á aplicación modificar os datos acerca dos teus contactos almacenados no teléfono, incluída a frecuencia coa que chamaches, enviaches correos electrónicos ou te comunicaches doutras maneiras con contactos específicos. Con este permiso as aplicacións poden eliminar datos de contactos."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permite que a aplicación modifique datos sobre os contactos almacenados na túa tableta. Con este permiso as aplicacións poden eliminar datos de contactos."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permite que a aplicación modifique datos sobre os contactos almacenados no teu dispositivo Android TV. Con este permiso as aplicacións poden eliminar datos de contactos."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permite que a aplicación modifique datos sobre os contactos almacenados no teu teléfono. Con este permiso as aplicacións poden eliminar datos de contactos."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ler rexistro de chamadas"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Esta aplicación pode ler o teu historial de chamadas."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"escribir no rexistro de chamadas"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"acceder a comandos adicionais do provedor de localización"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permite á aplicación acceder a comandos adicionais de fornecedor de localizacións. É posible que isto provoque que a aplicación interfira co funcionamento do GPS ou doutras fontes da localización."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"acceder á localización exacta só en primeiro plano"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Esta aplicación pode obter a túa localización exacta só cando se atope en primeiro plano. É necesario activar estes servizos de localización e deben estar dispoñibles no teléfono para que a aplicación poida utilizalos. Ademais, poden supoñer un aumento do consumo de batería."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"acceder á localización aproximada a partir de fontes de rede só en primeiro plano"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Esta aplicación pode obter a túa localización a partir de fontes de rede, como torres de telecomunicacións e redes wifi, pero só mentres está en primeiro plano. Para que a aplicación poida utilizar os servizos de localización, deben estar activados e dispoñibles na túa tableta."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Esta aplicación pode obter a túa localización a partir de fontes de rede, como antenas de telefonía móbil e redes wifi, pero só mentres está en primeiro plano. É necesario activar estes servizos de localización e deben estar dispoñibles no dispositivo Android TV para que a aplicación poida utilizalos."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Esta aplicación pode obter a túa localización a partir de fontes de rede, como torres de telecomunicacións e redes wifi, pero só mentres está en primeiro plano. Para que a aplicación poida utilizar os servizos de localización, deben estar activados e dispoñibles no teu teléfono."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Esta aplicación pode obter a túa localización exacta só cando se atope en primeiro plano. É necesario activar os servizos de localización e deben estar dispoñibles no teléfono para que a aplicación poida utilizalos. Ademais, poden supoñer un aumento do consumo de batería."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"acceder á localización aproximada só en primeiro plano"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Esta aplicación pode obter a túa localización aproximada, pero só mentres está en primeiro plano. Para que a aplicación poida utilizar os servizos de localización, deben estar activados e dispoñibles no teu dispositivo."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"acceder á localización en segundo plano"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Se a aplicación ten acceso á localización aproximada ou exacta e lle concedes este permiso, poderá consultar onde te atopas cando estea en segundo plano."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Esta aplicación pode acceder á localización mentres se executa en segundo plano, ademais de acceder á localización en primeiro plano."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar a configuración de son"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite á aplicación modificar a configuración de audio global, como o volume e que altofalante se utiliza para a saída."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite á aplicación ver a configuración do Bluetooth na tableta e efectuar e aceptar conexións con dispositivos sincronizados."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite que a aplicación consulte a configuración do Bluetooth no dispositivo Android TV, e efectúe e acepte conexións con dispositivos vinculados."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite á aplicación ver a configuración do Bluetooth no teléfono e efectuar e aceptar conexións con dispositivos sincronizados."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controlar Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permite á aplicación comunicarse con etiquetas, tarxetas e lectores Near Field Communication (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"desactivar o bloqueo da pantalla"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Conectado a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Toca para ver os ficheiros"</string>
<string name="pin_target" msgid="8036028973110156895">"Fixar"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Deixar de fixar"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Info. da aplicación"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demostración…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Queres actualizar <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> e <xliff:g id="TYPE_2">%3$s</xliff:g> en "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Gardar"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Non, grazas"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Agora non"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nunca"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Actualizar"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuar"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"contrasinal"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Activar/desactivar pantalla dividida"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Pantalla de bloqueo"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Captura de pantalla"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> nunha ventá emerxente."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de subtítulos de <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 5e4f753..fdaddc1 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"કાર્ય પ્રોફાઇલ વ્યવસ્થાપક ઍપ્લિકેશન ખૂટે છે અથવા તો દૂષિત છે. પરિણામે, તમારી કાર્યાલયની પ્રોફાઇલ અને તે સંબંધિત ડેટા કાઢી નાખવામાં આવ્યો છે. સહાયતા માટે તમારા વ્યવસ્થાપકનો સંપર્ક કરો."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"આ ઉપકરણ પર તમારી કાર્યાલયની પ્રોફાઇલ હવે ઉપલબ્ધ નથી"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"પાસવર્ડના ઘણા વધુ પ્રયત્નો"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"વ્યવસ્થાપકે ડિવાઇસ વ્યક્તિગત ઉપયોગ માટે આપી દીધું છે"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"ઉપકરણ સંચાલિત છે"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"તમારી સંસ્થા આ ઉપકરણનું સંચાલન કરે છે અને નેટવર્ક ટ્રાફિફનું નિયમન કરી શકે છે. વિગતો માટે ટૅપ કરો."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"તમારું ઉપકરણ કાઢી નાખવામાં આવશે"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ઍપને સ્ટીકી બ્રોડકાસ્ટ મોકલવાની મંજૂરી આપે છે, જે બ્રોડકાસ્ટ સમાપ્ત થયા પછી પણ રહે છે. અતિશય ઉપયોગ તમારા Android TV ડિવાઇસને વધુ પડતી મેમરીનો ઉપયોગ કરવાને કારણે તેને ધીમું અથવા અસ્થિર બનાવી શકે છે."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"એપ્લિકેશનને સ્ટિકી બ્રોડકાસ્ટ્સ મોકલવાની મંજૂરી આપે છે, જે બ્રોડકાસ્ટ્સ સમાપ્ત થયા પછી પણ રહે છે. અતિરિક્ત ઉપયોગ ફોનને વધુ પડતી મેમરીનો ઉપયોગ કરવાને કારણે તેને ધીમું અથવા અસ્થિર બનાવી શકે છે."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"તમારા સંપર્કો વાંચો"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"એપ્લિકેશનને તમે કઈ આવૃત્તિ પર કૉલ કર્યો, ઇમેઇલ કરી અથવા વિશિષ્ટ વ્યક્તિઓ સાથે અન્ય રીતે સંચાર કર્યો તે સહિત તમારા ટેબ્લેટ પર સંગ્રહિત તમારા સંપર્કો વિશેનો ડેટા વાંચવાની મંજૂરી આપે છે. આ પરવાનગી ઍપ્લિકેશનોને તમારો સંપર્ક ડેટા સાચવવાની મંજૂરી આપે છે અને દુર્ભાવનાપૂર્ણ ઍપ્લિકેશનો તમારી જાણ વગર સંપર્ક ડેટાને શેર કરી શકે છે."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"ઍપને તમે કઈ આવૃત્તિ પર કૉલ કર્યો, ઇમેઇલ કર્યો અથવા વિશિષ્ટ વ્યક્તિઓ સાથે અન્ય રીતે સંચાર કર્યો તે સહિત તમારા Android TV ડિવાઇસ પર સંગ્રહિત તમારા સંપર્કો વિશેનો ડેટા વાંચવાની મંજૂરી આપે છે. આ પરવાનગી ઍપને તમારો સંપર્ક ડેટા સાચવવાની મંજૂરી આપે છે અને દુર્ભાવનાપૂર્ણ ઍપ તમારી જાણ વગર સંપર્ક ડેટા શેર કરી શકે છે."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"એપ્લિકેશનને તમે કઈ આવૃત્તિ પર કૉલ કર્યો, ઇમેઇલ કરી અથવા વિશિષ્ટ વ્યક્તિઓ સાથે અન્ય રીતે સંચાર કર્યો તે સહિત તમારા ફોન પર સંગ્રહિત તમારા સંપર્કો વિશેનો ડેટા વાંચવાની મંજૂરી આપે છે. આ પરવાનગી ઍપ્લિકેશનોને તમારો સંપર્ક ડેટા સાચવવાની મંજૂરી આપે છે અને દુર્ભાવનાપૂર્ણ ઍપ્લિકેશનો તમારી જાણ વગર સંપર્ક ડેટાને શેર કરી શકે છે."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"ઍપને તમારા ટૅબ્લેટમાં સ્ટોર કરેલા તમારા સંપર્કો વિશેનો ડેટા વાંચવાની મંજૂરી આપે છે. ઍપને તમારા ટૅબ્લેટ પર સંપર્કો બનાવનારાં એકાઉન્ટનો પણ ઍક્સેસ રહેશે. તેમાં તમે ઇન્સ્ટૉલ કરેલી ઍપ દ્વારા બનાવાયેલાં એકાઉન્ટનો પણ સમાવેશ થઈ શકે છે. આ પરવાનગી ઍપને તમારા સંપર્કનો ડેટા સાચવવાની મંજૂરી આપે છે અને દુર્ભાવનાપૂર્ણ ઍપ તમારી જાણ બહાર સંપર્કનો ડેટા શેર કરે તેમ બની શકે છે."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"ઍપને તમારા Android TV ડિવાઇસમાં સ્ટોર કરેલા તમારા સંપર્કો વિશેનો ડેટા વાંચવાની મંજૂરી આપે છે. ઍપને તમારા Android TV ડિવાઇસ પર સંપર્કો બનાવનારાં એકાઉન્ટનો પણ ઍક્સેસ રહેશે. તેમાં તમે ઇન્સ્ટૉલ કરેલી ઍપ દ્વારા બનાવાયેલાં એકાઉન્ટનો પણ સમાવેશ થઈ શકે છે. આ પરવાનગી ઍપને તમારા સંપર્કનો ડેટા સાચવવાની મંજૂરી આપે છે અને દુર્ભાવનાપૂર્ણ ઍપ તમારી જાણ બહાર સંપર્કનો ડેટા શેર કરે તેમ બની શકે છે."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ઍપને તમારા ફોનમાં સ્ટોર કરેલા તમારા સંપર્કો વિશેનો ડેટા વાંચવાની મંજૂરી આપે છે. ઍપને તમારા ફોન પર સંપર્કો બનાવનારાં એકાઉન્ટનો પણ ઍક્સેસ રહેશે. તેમાં તમે ઇન્સ્ટૉલ કરેલી ઍપ દ્વારા બનાવાયેલાં એકાઉન્ટનો પણ સમાવેશ થઈ શકે છે. આ પરવાનગી ઍપને તમારા સંપર્કનો ડેટા સાચવવાની મંજૂરી આપે છે અને દુર્ભાવનાપૂર્ણ ઍપ તમારી જાણ બહાર સંપર્કનો ડેટા શેર કરે તેમ બની શકે છે."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"તમારા સંપર્કો સંશોધિત કરો"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"એપ્લિકેશનને તમે કઈ આવૃત્તિ પર કૉલ કર્યો, ઇમેઇલ કરી અથવા વિશિષ્ટ સંપર્કો સાથે અન્ય રીતે સંચાર કર્યો તે સહિત તમારા ટેબ્લેટ પર સંગ્રહિત તમારા સંપર્કો વિશેનો ડેટા સંશોધિત કરવાની મંજૂરી આપે છે. આ પરવાનગી એપ્લિકેશન્સને સંપર્ક ડેટા કાઢી નાખવાની મંજૂરી આપે છે."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"ઍપને તમે કઈ આવૃત્તિ પર કૉલ કર્યો, ઇમેઇલ કર્યો અથવા વિશિષ્ટ સંપર્કો સાથે અન્ય રીતે સંચાર કર્યો તે સહિત તમારા Android TV ડિવાઇસ પર સંગ્રહિત તમારા સંપર્કો વિશેનો ડેટા સંશોધિત કરવાની મંજૂરી આપે છે. આ પરવાનગી ઍપને સંપર્ક ડેટા ડિલીટ કરવાની મંજૂરી આપે છે."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"એપ્લિકેશનને તમે કઈ આવૃત્તિ પર કૉલ કર્યો, ઇમેઇલ કરી અથવા વિશિષ્ટ સંપર્કો સાથે અન્ય રીતે સંચાર કર્યો તે સહિત તમારા ફોન પર સંગ્રહિત તમારા સંપર્કો વિશેનો ડેટા સંશોધિત કરવાની મંજૂરી આપે છે. આ પરવાનગી એપ્લિકેશન્સને સંપર્ક ડેટા કાઢી નાખવાની મંજૂરી આપે છે."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"ઍપને તમારા ટૅબ્લેટ પર સ્ટોર કરેલા તમારા સંપર્કો વિશેનો ડેટા સંશોધિત કરવાની મંજૂરી આપે છે. આ પરવાનગી ઍપને સંપર્ક ડેટા ડિલીટ કરવાની મંજૂરી આપે છે."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"ઍપને તમારા Android TV ડિવાઇસ પર સ્ટોર કરેલા તમારા સંપર્કો વિશેનો ડેટા સંશોધિત કરવાની મંજૂરી આપે છે. આ પરવાનગી ઍપને સંપર્ક ડેટા ડિલીટ કરવાની મંજૂરી આપે છે."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ઍપને તમારા ફોનમાં સ્ટોર કરેલા તમારા સંપર્કો વિશેનો ડેટા સંશોધિત કરવાની મંજૂરી આપે છે. આ પરવાનગી ઍપને સંપર્ક ડેટા ડિલીટ કરવાની મંજૂરી આપે છે."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"કૉલ લૉગ વાંચો"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"આ ઍપ્લિકેશન, તમારો કૉલ ઇતિહાસ વાંચી શકે છે."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"કૉલ લૉગ લખો"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"વધારાના સ્થાન પ્રદાતા આદેશોને ઍક્સેસ કરો"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"એપ્લિકેશનને વધારાના સ્થાન પ્રદાતા આદેશોને ઍક્સેસ કરવાની મંજૂરી આપે છે. આ એપ્લિકેશનને GPS અથવા અન્ય સ્થાન સ્રોતોના ઓપરેશનમાં દખલ કરવાની મંજૂરી આપી શકે છે."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ફૉરગ્રાઉન્ડમાં ફક્ત ચોક્કસ સ્થાન ઍક્સેસ કરો"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"આ ઍપ ફક્ત બૅકગ્રાઉન્ડમાં હોય ત્યારે જ તમારું ચોક્કસ સ્થાન મેળવી શકે છે. ઍપ આ સ્થાન સેવાઓનો ઉપયોગ કરી શકે તે માટે તમારા ફોન પર આ સેવાઓ ઉપલબ્ધ અને ચાલુ હોવી આવશ્યક છે. આ બૅટરી વપરાશ વધારી શકે છે."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"અંદાજિત જગ્યાને (નેટવર્ક આધારિત) માત્ર ફૉરગ્રાઉન્ડમાંથી ઍક્સેસ કરો"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"સેલ ટાવર અને વાઇ-ફાઇ નેટવર્ક જેવા નેટવર્ક સૉર્સનો ઉપયોગ કરીને આ અૅપ તમારી જગ્યા જાણી શકે છે, પરંતુ આ માત્ર ત્યારે શક્ય છે જ્યારે આ અૅપ ફૉરગ્રાઉન્ડમાં ચાલુ હોય. ઍપ આ જગ્યાની સેવાઓનો ઉપયોગ કરી શકે તે માટે તમારા ટૅબ્લેટ પર આ સેવાઓ ઉપલબ્ધ અને ચાલુ હોવી આવશ્યક છે."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"સેલ ટાવર અને વાઇ-ફાઇ નેટવર્ક જેવા નેટવર્ક સૉર્સનો ઉપયોગ કરીને આ ઍપ તમારું સ્થાન જાણી શકે છે, પરંતુ આ માત્ર ત્યારે શક્ય છે જ્યારે આ ઍપ ફૉરગ્રાઉન્ડમાં ચાલુ હોય. ઍપ આ સ્થાન સેવાઓનો ઉપયોગ કરી શકે તે માટે તમારા Android TV ડિવાઇસ પર આ સેવાઓ ઉપલબ્ધ અને ચાલુ હોવી જરૂરી છે."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"સેલ ટાવર અને વાઇ-ફાઇ નેટવર્ક જેવા નેટવર્ક સૉર્સનો ઉપયોગ કરીને આ અૅપ તમારી જગ્યા જાણી શકે છે, પરંતુ આ માત્ર ત્યારે શક્ય છે જ્યારે આ અૅપ ફૉરગ્રાઉન્ડમાં ચાલુ હોય. ઍપ આ જગ્યાની સેવાઓનો ઉપયોગ કરી શકે તે માટે તમારા ફોન પર આ સેવાઓ ઉપલબ્ધ અને ચાલુ હોવી આવશ્યક છે."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"આ ઍપ ફક્ત ફૉરગ્રાઉન્ડમાંમાં હોય ત્યારે જ તમારું ચોક્કસ સ્થાન મેળવી શકે છે. ઍપ આ સ્થાન સેવાઓનો ઉપયોગ કરી શકે તે માટે તે સેવાઓ ચાલુ કરેલી અને તમારા ડિવાઇસમાં ઉપલબ્ધ હોવી જોઈએ. આ બૅટરી વપરાશ વધારી શકે છે."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"ફૉરગ્રાઉન્ડમાં ફક્ત અંદાજિત સ્થાન ઍક્સેસ કરો"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"આ ઍપ ફોરગ્રાઉન્ડમાં હોય, ત્યારે જ તે તમારું અંદાજિત સ્થાન મેળવી શકે છે. ઍપ આ સ્થાન સેવાઓનો ઉપયોગ કરી શકે તે માટે તે સેવાઓ ચાલુ કરેલી અને તમારા ડિવાઇસમાં ઉપલબ્ધ હોવી જોઈએ."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"બૅકગ્રાઉન્ડમાં સ્થાન ઍક્સેસ કરો"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"જો અંદાજિત અથવા ચોક્કસ સ્થાનના ઍક્સેસ ઉપરાંત આને પરવાનગી આપવામાં આવી હશે, તો ઍપ બૅકગ્રાઉન્ડમાં ચાલતી હોય ત્યારે સ્થાનને ઍક્સેસ કરી શકશે."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"આ ઍપ ફૉરગ્રાઉન્ડમાં સ્થાનને ઍક્સેસ કરવા ઉપરાંત બૅકગ્રાઉન્ડમાં ચાલતી હોય ત્યારે પણ સ્થાનને ઍક્સેસ કરી શકે છે."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"તમારી ઑડિઓ સેટિંગ્સ બદલો"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"એપ્લિકેશનને વૈશ્વિક ઑડિઓ સેટિંગ્સને સંશોધિત કરવાની મંજૂરી આપે છે, જેમ કે વૉલ્યૂમ અને આઉટપુટ માટે કયા સ્પીકરનો ઉપયોગ કરવો."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ઑડિઓ રેકોર્ડ કરવાની"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"એપ્લિકેશનને ટેબ્લેટ પર બ્લૂટૂથ ની ગોઠવણી જોવાની અને જોડી કરેલ ઉપકરણો સાથે કનેક્શન્સ કરવાની અને સ્વીકારવાની મંજૂરી આપે છે."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ઍપને તમારા Android TV ડિવાઇસ પર બ્લૂટૂથની ગોઠવણી જોવાની અને જોડાણ કરેલા ડિવાઇસની સાથે કનેક્શન કરવાની અને સ્વીકારવાની મંજૂરી આપે છે."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"એપ્લિકેશનને ફોન પર બ્લૂટૂથ ની ગોઠવણી જોવાની અને જોડી કરેલ ઉપકરણો સાથે કનેક્શન્સ કરવાની અને સ્વીકારવાની મંજૂરી આપે છે."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"નિઅર ફીલ્ડ કમ્યુનિકેશન નિયંત્રિત કરો"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ઍપ્લિકેશનને નિઅર ફીલ્ડ કમ્યુનિકેશન (NFC) ટૅગ, કાર્ડ અને રીડર સાથે સંચાર કરવાની મંજૂરી આપે છે."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"તમારું સ્ક્રીન લૉક અક્ષમ કરો"</string>
@@ -900,7 +901,7 @@
<string name="factorytest_no_action" msgid="339252838115675515">"FACTORY_TEST ક્રિયા પ્રદાન કરનાર કોઈ પૅકેજ મળ્યું નહોતું."</string>
<string name="factorytest_reboot" msgid="2050147445567257365">"રીબૂટ કરો"</string>
<string name="js_dialog_title" msgid="7464775045615023241">"\"<xliff:g id="TITLE">%s</xliff:g>\" પરનું પૃષ્ઠ કહે છે કે:"</string>
- <string name="js_dialog_title_default" msgid="3769524569903332476">"Javascript"</string>
+ <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
<string name="js_dialog_before_unload_title" msgid="7012587995876771246">"નેવિગેશનની પુષ્ટિ કરો"</string>
<string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"આ પૃષ્ઠ છોડો"</string>
<string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"આ પૃષ્ઠ પર રહો"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> થી કનેક્ટ કરેલું છે"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ફાઇલો જોવા માટે ટૅપ કરો"</string>
<string name="pin_target" msgid="8036028973110156895">"પિન"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"અનપિન કરો"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ઍપ્લિકેશન માહિતી"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ડેમો પ્રારંભ કરી રહ્યાં છે…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"માંની: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> અને <xliff:g id="TYPE_2">%3$s</xliff:g> બાબતોને અપડેટ કરીએ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"સાચવો"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"ના, આભાર"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"હમણાં નહીં"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ક્યારેય નહીં"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"અપડેટ કરો"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ચાલુ રાખો"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"પાસવર્ડ"</string>
@@ -1972,7 +1979,7 @@
<string name="mime_type_audio" msgid="4933450584432509875">"ઑડિયો"</string>
<string name="mime_type_audio_ext" msgid="2615491023840514797">"<xliff:g id="EXTENSION">%1$s</xliff:g> ઑડિયો"</string>
<string name="mime_type_video" msgid="7071965726609428150">"વીડિયો"</string>
- <string name="mime_type_video_ext" msgid="185438149044230136">"<xliff:g id="EXTENSION">%1$s</xliff:g> વીડિઓ"</string>
+ <string name="mime_type_video_ext" msgid="185438149044230136">"<xliff:g id="EXTENSION">%1$s</xliff:g> વીડિયો"</string>
<string name="mime_type_image" msgid="2134307276151645257">"છબી"</string>
<string name="mime_type_image_ext" msgid="5743552697560999471">"<xliff:g id="EXTENSION">%1$s</xliff:g> છબી"</string>
<string name="mime_type_compressed" msgid="8737300936080662063">"આર્કાઇવ"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"સ્ક્રીનને વિભાજિત કરવાની ક્રિયા ટૉગલ કરો"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"લૉક સ્ક્રીન"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"સ્ક્રીનશૉટ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"પૉપ-અપ વિંડોમાં <xliff:g id="APP_NAME">%1$s</xliff:g> ઍપ."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>નું કૅપ્શન બાર."</string>
</resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index eecfd5b..407e9de 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"वर्क प्रोफ़ाइल व्यवस्थापक ऐप्लिकेशन या तो मौजूद नहीं है या वह खराब हो गया है. परिणामस्वरूप, आपकी वर्क प्रोफ़ाइल और उससे जुड़े डेटा को हटा दिया गया है. सहायता के लिए अपने व्यवस्थापक से संपर्क करें."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"आपकी वर्क प्रोफ़ाइल अब इस डिवाइस पर उपलब्ध नहीं है"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"कई बार गलत पासवर्ड डाला गया"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"एडमिन ने निजी इस्तेमाल के लिए डिवाइस दे दिया है"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"डिवाइस प्रबंधित है"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"आपका संगठन इस डिवाइस का प्रबंधन करता है और वह नेटवर्क ट्रैफ़िक की निगरानी भी कर सकता है. विवरण के लिए टैप करें."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"आपके डिवाइस को मिटा दिया जाएगा"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"यह ऐप्लिकेशन को स्टिकी ब्रॉडकास्ट भेजने की अनुमति देता है जो ब्रॉडकास्ट खत्म होने के बाद भी बने रहते हैं. इस सुविधा के ज़्यादा इस्तेमाल से आपके Android TV डिवाइस की मेमोरी कम हो सकती है जिससे टीवी की परफ़ॉर्मेंस पर असर पड़ सकता है और उसे इस्तेमाल करने में समस्याएं आ सकती हैं."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ऐप्स को स्टिकी प्रसारण भेजने देता है, जो प्रसारण खत्म होने के बाद भी बने रहते हैं. अत्यधिक उपयोग, फ़ोन की बहुत ज़्यादा मेमोरी का उपयोग करके उसे धीमा या अस्थिर कर सकता है."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"अपने संपर्क पढ़ें"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"ऐप को आपके टैबलेट पर मौजूद आपके संपर्कों का डेटा पढ़ने देती है, जिसमें ये भी शामिल है कि आपने कुछ ख़ास लोगों से कितनी बार कॉल, ईमेल, या कुछ और तरीकों से बातचीत की. यह अनुमति ऐप को आपका संपर्क डेटा सेव करने देती है और धोखा देने वाले ऐप संपर्क डेटा को आपकी जानकारी के बिना शेयर कर सकते हैं."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"यह ऐप्लिकेशन को आपके Android TV डिवाइस पर सेव किए हुए संपर्कों के डेटा में बदलाव करने की अनुमति देता है. साथ ही, संपर्क में शामिल कुछ खास लोगों को आप कितना कॉल करते हैं, कितने ईमेल भेजते हैं या संपर्क करने के दूसरे तरीकों का कितना इस्तेमाल करते हैं, इससे जुड़ी जानकारी भी हासिल कर सकता है. यह अनुमति देने के बाद ऐप्लिकेशन आपके संपर्क का डेटा सेव कर सकता है. हालांकि, नुकसान पहुंचाने वाले ऐप्लिकेशन, बिना आपको बताए संपर्क से जुड़ा डेटा शेयर कर सकते हैं."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"ऐप को आपके फ़ोन पर मौजूद आपके संपर्कों का डेटा पढ़ने देती है, जिसमें ये भी शामिल है कि आपने कुछ ख़ास लोगों से कितनी बार कॉल, ईमेल, या कुछ और तरीकों से बातचीत की. यह अनुमति ऐप को आपका संपर्क डेटा सेव करने देती है और धोखा देने वाले ऐप, संपर्क डेटा को आपकी जानकारी के बिना शेयर कर सकते हैं."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"यह ऐप्लिकेशन को आपके टैबलेट पर मौजूद संपर्कों का डेटा देखने की अनुमति देता है. ऐप्लिकेशन को आपके टैबलेट पर मौजूद उन खातों को ऐक्सेस करने की अनुमति भी होगी जिनसे संपर्क बनाए गए हैं. इसमें वे खाते भी शामिल हो सकते हैं जिन्हें आपके इंस्टॉल किए हुए ऐप्लिकेशन ने बनाया है. इस अनुमति के बाद, ऐप्लिकेशन आपके संपर्कों का डेटा सेव कर सकते हैं. हालांकि, नुकसान पहुंचाने वाले ऐप्लिकेशन, आपको बताए बिना ही संपर्कों का डेटा शेयर कर सकते हैं."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"यह ऐप्लिकेशन को आपके Android TV डिवाइस पर सेव किए संपर्कों का डेटा देखने की अनुमति देता है. ऐप्लिकेशन को आपके Android TV डिवाइस पर मौजूद उन खातों को ऐक्सेस करने की अनुमति भी होगी जिनसे संपर्क बनाए गए हैं. इसमें वे खाते भी शामिल हो सकते हैं जिन्हें आपके इंस्टॉल किए हुए ऐप्लिकेशन ने बनाया है. इस अनुमति के बाद, ऐप्लिकेशन आपके संपर्कों का डेटा सेव कर सकते हैं. हालांकि, नुकसान पहुंचाने वाले ऐप्लिकेशन, आपको बताए बिना ही संपर्कों का डेटा शेयर कर सकते हैं."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"यह ऐप्लिकेशन को आपके फ़ोन पर मौजूद संपर्कों का डेटा देखने की अनुमति देता है. ऐप्लिकेशन को आपके फ़ोन पर मौजूद उन खातों को ऐक्सेस करने की अनुमति भी होगी जिनसे संपर्क बनाए गए हैं. इसमें वे खाते भी शामिल हो सकते हैं जिन्हें आपके इंस्टॉल किए हुए ऐप्लिकेशन ने बनाया है. इस अनुमति के बाद, ऐप्लिकेशन आपके संपर्कों का डेटा सेव कर सकते हैं. हालांकि, नुकसान पहुंचाने वाले ऐप्लिकेशन, आपको बताए बिना ही संपर्कों का डेटा शेयर कर सकते हैं."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"अपने संपर्क बदलें"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"ऐप्स को आपके टैबलेट में संग्रहित संपर्कों के डेटा को, साथ ही विशिष्ट व्यक्तियों को कॉल करने, ईमेल करने, या अन्य तरीके से डॉयलॉग करने की आवृत्ति को संशोधित करने देता है. यह अनुमति ऐप्स को आपके संपर्क डेटा को हटाने देती है."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"यह ऐप्लिकेशन को आपके Android TV डिवाइस पर सेव किए संपर्कों के डेटा में बदलाव करने की अनुमति देता है. साथ ही, संपर्क में शामिल कुछ खास लोगों को आप कितना कॉल करते हैं, कितने ईमेल भेजते हैं या संपर्क करने के दूसरे तरीकों का कितना इस्तेमाल करते हैं, इससे जुड़ी जानकारी में भी बदलाव कर सकता है. इस अनुमति से, ऐप्लिकेशन आपके संपर्क के डेटा को मिटा सकता है."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"ऐप्स को आपके फ़ोन में संग्रहित संपर्कों के डेटा को, साथ ही विशिष्ट व्यक्तियों को कॉल करने, ईमेल करने, या अन्य तरीके से डॉयलॉग करने की आवृत्ति को संशोधित करने देता है. यह अनुमति ऐप्स को आपके संपर्क डेटा को हटाने देती है."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"यह ऐप्लिकेशन को आपके टैबलेट पर सेव किए संपर्कों के डेटा में बदलाव करने की अनुमति देता है. इस अनुमति के बाद, ऐप्लिकेशन आपके संपर्कों का डेटा मिटा सकते हैं."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"यह ऐप्लिकेशन को आपके Android TV डिवाइस पर सेव किए संपर्कों के डेटा में बदलाव करने की अनुमति देता है. इस अनुमति के बाद, ऐप्लिकेशन आपके संपर्कों का डेटा मिटा सकते हैं."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"यह ऐप्लिकेशन को आपके फ़ोन पर सेव किए संपर्कों के डेटा में बदलाव करने की अनुमति देता है. इस अनुमति के बाद, ऐप्लिकेशन आपके संपर्कों का डेटा मिटा सकते हैं."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"कॉल लॉग पढ़ें"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"यह ऐप्लिकेशन आपका कॉल इतिहास पढ़ सकता है."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"कॉल लॉग लिखें"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"कुछ और जगह बताने वाले आदेशों तक पहुंच"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ऐप को कुछ और जगह की जानकारी देने वाले आदेशों की पहुंच पाने देता है. इससे ऐप जीपीएस या जगह की जानकारी देने वाले दूसरे स्रोतों के काम में रोक-टोक कर सकता है."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ऐप्लिकेशन \'जगह की सटीक जानकारी\' सिर्फ़ सामने खुली होने पर ऐक्सेस करे"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"यह ऐप्लिकेशन सिर्फ़ तब आपकी \'जगह की सटीक जानकारी\' का इस्तेमाल कर सकता है जब यह स्क्रीन पर दिखाई दे रहा हो. यह ज़रूरी है कि \'जगह की जानकारी\' वाली ये सेवाएं आपके फ़ोन में मौजूद हों और चालू की गई हों ताकि ऐप्लिकेशन उनका इस्तेमाल कर पाए. ऐसा करने से ज़्यादा बैटरी खर्च हो सकती है."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"स्क्रीन पर दिखाई देते समय \'जगह की अनुमानित जानकारी\' (नेटवर्क-आधारित) ऐक्सेस करें"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"यह ऐप्लिकेशन सेल टावर और वाई-फ़ाई नेटवर्क जैसे नेटवर्क स्रोतों के आधार पर आपकी जगह का पता लगा सकता है, लेकिन सिर्फ़ तब, जब ऐप्लिकेशन स्क्रीन पर दिखाई दे रहा हो. यह ज़रूरी है कि \'जगह की जानकारी\' वाली ये सेवाएं आपके टैबलेट में मौजूद हों और चालू की गई हों ताकि ऐप्लिकेशन उनका इस्तेमाल कर पाए."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"यह ऐप्लिकेशन मोबाइल टावर और वाई-फ़ाई नेटवर्क जैसे स्रोतों के आधार पर आपकी जगह की जानकारी ले सकता है. हालांकि, ऐसा तब होगा, जब ऐप्लिकेशन स्क्रीन पर दिखाई दे रहा हो. जगह की जानकारी वाली ये सुविधाएं आपके Android TV डिवाइस पर उपलब्ध होनी चाहिए और चालू स्थिति में होनी चाहिए, ताकि यह ऐप्लिकेशन उनका इस्तेमाल कर सके."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"यह ऐप्लिकेशन सेल टावर और वाई-फ़ाई नेटवर्क जैसे नेटवर्क स्रोतों के आधार पर आपकी जगह का पता लगा सकता है, लेकिन सिर्फ़ तब, जब ऐप्लिकेशन स्क्रीन पर दिखाई दे रहा हो. यह ज़रूरी है कि \'जगह की जानकारी\' वाली ये सेवाएं आपके फ़ोन में मौजूद हों और चालू की गई हों ताकि ऐप्लिकेशन उनका इस्तेमाल कर पाए."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"यह ऐप्लिकेशन सिर्फ़ तब आपकी जगह की सटीक जानकारी का इस्तेमाल कर सकता है, जब यह स्क्रीन पर खुला हो. यह ज़रूरी है कि जगह की जानकारी वाली ये सुविधाएं आपके फ़ोन में मौजूद हों और चालू की गई हों, ताकि ऐप्लिकेशन उनका इस्तेमाल कर पाए. ऐसा करने से ज़्यादा बैटरी खर्च हो सकती है."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"अनुमानित जगह की जानकारी सिर्फ़ तब ऐक्सेस करें, जब ऐप्लिकेशन स्क्रीन पर खुला हो"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"यह ऐप्लिकेशन आपकी अनुमानित जगह की जानकारी का इस्तेमाल सिर्फ़ तब कर सकता है, जब यह स्क्रीन पर दिखाई दे रहा हो. यह ज़रूरी है कि आपके डिवाइस में, जगह की जानकारी वाली सुविधाएं हों और उन्हें चालू किया गया हो, ताकि ऐप्लिकेशन उनका इस्तेमाल कर पाए."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"बैकग्राउंड में जगह की जानकारी ऐक्सेस करना"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"अनुमानित या बिल्कुल सही जगह की जानकारी का ऐक्सेस करने की अनुमति अलग से दिए जाने पर, बैकग्राउंड में चलने के दौरान ऐप्लिकेशन आपकी जगह की जानकारी ऐक्सेस कर सकता है."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"यह ऐप्लिकेशन स्क्रीन पर खुले होने के साथ-साथ बैकग्राउंड में चलते हुए भी जगह की जानकारी ऐक्सेस कर सकता है."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"अपनी ऑडियो सेटिंग बदलें"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ऐप्स को वैश्विक ऑडियो सेटिंग, जैसे वॉल्यूम और कौन-सा स्पीकर आउटपुट के लिए उपयोग किया गया, संशोधित करने देता है."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ऑडियो रिकॉर्ड करने"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ऐप्स को टैबलेट पर ब्लूटूथ का कॉन्फ़िगरेशन देखने, और युग्मित डिवाइस के साथ कनेक्शन बनाने और स्वीकार करने देता है."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"यह ऐप्लिकेशन को आपके Android TV डिवाइस पर ब्लूटूथ कॉन्फ़िगरेशन देखने की अनुमति देता है. साथ ही, यह ब्लूटूथ कनेक्शन से दूसरे डिवाइस को जोड़ने की सुविधा भी देता है."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ऐप्स को फ़ोन पर ब्लूटूथ का कॉन्फ़िगरेशन देखने, और युग्मित डिवाइस के साथ कनेक्शन बनाने और स्वीकार करने देता है."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"नियर फ़ील्ड कम्यूनिकेशन नियंत्रित करें"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ऐप्स को नियर फ़ील्ड कम्यूनिकेशन (NFC) टैग, कार्ड, और रीडर के साथ संचार करने देता है."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"अपना स्क्रीन लॉक अक्षम करें"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> से कनेक्ट किया गया"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"फ़ाइलें देखने के लिए टैप करें"</string>
<string name="pin_target" msgid="8036028973110156895">"पिन करें"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"अनपिन करें"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ऐप्लिकेशन की जानकारी"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"डेमो प्रारंभ हो रहा है…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"क्या आप "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" में इन आइटम को अपडेट करना चाहते हैं: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, और <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"सेव करें"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"नहीं, धन्यवाद"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"अभी नहीं"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"कभी नहीं"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"अपडेट करें"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"जारी रखें"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"पासवर्ड"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"स्प्लिट स्क्रीन पर टॉगल करें"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"स्क्रीन लॉक करें"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"स्क्रीनशॉट लें"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"पॉप-अप विंडो में <xliff:g id="APP_NAME">%1$s</xliff:g> ऐप्लिकेशन."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> का कैप्शन बार."</string>
</resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index e8f02b2..0d25a77 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -190,8 +190,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Administratorska aplikacija radnog profila nedostaje ili je oštećena. Zbog toga su radni profil i povezani podaci izbrisani. Za pomoć se obratite svom administratoru."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Vaš radni profil više nije dostupan na ovom uređaju"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Previše pokušaja unosa zaporke"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator je ustupio uređaj za osobnu upotrebu"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Uređaj je upravljan"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Vaša organizacija upravlja ovim uređajem i može nadzirati mrežni promet. Dodirnite za pojedinosti."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Uređaj će se izbrisati"</string>
@@ -384,13 +383,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Aplikaciji omogućuje slanje \"ljepljivih\" emitiranja koja se zadržavaju nakon završetka emitiranja. Prekomjerna upotreba može usporiti Android TV uređaj ili ga učiniti nestabilnim uzrokujući pretjeranu upotrebu memorije."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Aplikaciji omogućuje slanje \"ljepljivih\" emitiranja koja se zadržavaju nakon završetka emitiranja. Prekomjerna upotreba može usporiti telefon ili ga učiniti nestabilnim uzrokujući pretjeranu upotrebu memorije."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"čitanje kontakata"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Aplikaciji omogućuje čitanje podataka o vašim kontaktima pohranjenim na tabletnom računalu, uključujući učestalost poziva, e-poruka ili drugih načina komunikacije s određenim pojedincima. Ta dozvola aplikaciji omogućuje spremanje podataka kontakata, a zlonamjerne aplikacije mogu dijeliti podatke kontakata bez vašeg znanja."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Aplikaciji omogućuje čitanje podataka o vašim kontaktima pohranjenima na vašem Android TV uređaju, uključujući učestalost poziva, e-poruka ili drugih načina komunikacije s određenim osobama. To dopuštenje omogućuje aplikaciji spremanje vaših podataka o kontaktima, a zlonamjerne aplikacije mogu dijeliti podatke o kontaktima bez vašeg znanja."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Aplikaciji omogućuje čitanje podataka o vašim kontaktima pohranjenim na telefonu, uključujući učestalost poziva, e-poruka ili drugih načina komunikacije s određenim pojedincima. Ta dozvola aplikaciji omogućuje spremanje podataka kontakata, a zlonamjerne aplikacije mogu dijeliti podatke kontakata bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Aplikaciji omogućuje čitanje podataka o vašim kontaktima pohranjenim na vašem tabletu. Aplikacije će također imati pristup računima na vašem tabletu na kojima su izrađeni kontakti. To može uključivati račune izrađene u aplikacijama koje ste instalirali. To dopuštenje omogućuje aplikacijama spremanje podataka o kontaktima, a zlonamjerne aplikacije mogu dijeliti podatke o kontaktima bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Aplikaciji omogućuje čitanje podataka o vašim kontaktima pohranjenim na vašem Android TV uređaju. Aplikacije će također imati pristup računima na vašem Android TV uređaju na kojima su izrađeni kontakti. To može uključivati račune izrađene u aplikacijama koje ste instalirali. To dopuštenje omogućuje aplikacijama spremanje podataka o kontaktima, a zlonamjerne aplikacije mogu dijeliti podatke o kontaktima bez vašeg znanja."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Aplikaciji omogućuje čitanje podataka o vašim kontaktima pohranjenim na vašem telefonu. Aplikacije će također imati pristup računima na vašem telefonu na kojima su izrađeni kontakti. To može uključivati račune izrađene u aplikacijama koje ste instalirali. To dopuštenje omogućuje aplikacijama spremanje podataka o kontaktima, a zlonamjerne aplikacije mogu dijeliti podatke o kontaktima bez vašeg znanja."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"izmjena kontakata"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Aplikaciji omogućuje izmjenu podataka o vašim kontaktima pohranjenim na tabletnom računalu, uključujući učestalost poziva, e-poruka ili drugih načina komunikacije s određenim kontaktima. Ta dozvola aplikacijama omogućuje brisanje kontaktnih podataka."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Aplikaciji omogućuje izmjenu podataka o vašim kontaktima pohranjenim na Android TV uređaju, uključujući učestalost poziva, e-poruka ili drugih načina komunikacije s određenim kontaktima. To dopuštenje aplikacijama omogućuje brisanje podataka o kontaktima."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Aplikaciji omogućuje izmjenu podataka o vašim kontaktima pohranjenim na telefonu, uključujući učestalost poziva, e-poruka ili drugih načina komunikacije s određenim kontaktima. Ta dozvola aplikacijama omogućuje brisanje kontaktnih podataka."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Aplikaciji omogućuje izmjenu podataka o vašim kontaktima pohranjenim na vašem tabletu. To dopuštenje aplikacijama omogućuje da brišu podatke o kontaktima."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Aplikaciji omogućuje izmjenu podataka o vašim kontaktima pohranjenim na vašem Android TV uređaju. To dopuštenje aplikacijama omogućuje da brišu podatke o kontaktima."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Aplikaciji omogućuje izmjenu podataka o vašim kontaktima pohranjenim na vašem telefonu. To dopuštenje aplikacijama omogućuje da brišu podatke o kontaktima."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"čitanje dnevnika poziva"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Aplikacija može čitati vašu povijest poziva."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"pisanje u dnevnik poziva"</string>
@@ -410,13 +409,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"pristup dodatnim naredbama davatelja lokacije"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Omogućuje aplikaciji pristup dodatnim naredbama davatelja usluga lokacije. To može omogućiti aplikaciji ometanje rada GPS-a ili drugih izvora lokacije."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"pristupiti preciznoj lokaciji samo u prednjem planu"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Aplikacija može dobiti vašu točnu lokaciju samo kada je u prednjem planu. Te usluge lokacije moraju biti uključene i dostupne na telefonu da bi ih aplikacija mogla upotrebljavati. To može pojačati potrošnju baterije."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"pristupiti približnoj lokaciji (na temelju mreže) samo u prednjem planu"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ova aplikacija može dobiti vašu lokaciju na temelju mrežnih izvora kao što su bazne stanice i Wi-Fi mreže, no samo kad je u prednjem planu. Te usluge lokacije moraju biti uključene i dostupne na vašem tabletu da bi ih aplikacija mogla upotrebljavati."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ova aplikacija može dobiti vašu lokaciju na temelju mrežnih izvora kao što su bazne stanice i Wi-Fi mreže, no samo kad je u prednjem planu. Te usluge lokacije moraju biti uključene i dostupne na vašem Android TV uređaju da bi ih aplikacija mogla upotrebljavati."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ova aplikacija može dobiti vašu lokaciju na temelju mrežnih izvora kao što su bazne stanice i Wi-Fi mreže, no samo kad je u prednjem planu. Te usluge lokacije moraju biti uključene i dostupne na vašem telefonu da bi ih aplikacija mogla upotrebljavati."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Aplikacija može dobiti vašu točnu lokaciju samo kad je u prednjem planu. Usluge lokacije moraju biti uključene i dostupne na uređaju da bi ih aplikacija mogla upotrebljavati. To može pojačati potrošnju baterije."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"pristupiti približnoj lokaciji samo u prednjem planu"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Aplikacija može dobiti vašu približnu lokaciju samo kad je u prednjem planu. Usluge lokacije moraju biti uključene i dostupne na uređaju da bi ih aplikacija mogla upotrebljavati."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"pristup lokaciji u pozadini"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ako se ovo odobrava dodatno za pristup približnoj ili preciznoj lokaciji, aplikacija može pristupiti lokaciji tijekom rada u pozadini."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ta aplikacija može pristupiti lokaciji dok se izvodi u pozadini, uz pristup lokaciji u prednjem planu."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"promjena postavki zvuka"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Aplikaciji omogućuje izmjenu globalnih postavki zvuka, primjerice glasnoće i zvučnika koji se upotrebljava za izlaz."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje zvuka"</string>
@@ -497,6 +494,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Aplikaciji omogućuje pregled konfiguracije Bluetootha na tabletnom računalu te uspostavljanje i prihvaćanje veza s uparenim uređajima."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Aplikaciji omogućuje pregled konfiguracije Bluetootha na Android TV uređaju te uspostavljanje i prihvaćanje veza s uparenim uređajima."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Aplikaciji omogućuje pregled konfiguracije Bluetootha na telefonu te uspostavljanje i prihvaćanje veza s uparenim uređajima."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"upravljanje beskontaktnom komunikacijom (NFC)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Aplikaciji omogućuje komunikaciju s oznakama, karticama i čitačima komunikacije kratkog dometa (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"onemogućavanje zaključavanja zaslona"</string>
@@ -1894,7 +1895,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> – veza je uspostavljena"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Dodirnite da biste pregledali datoteke"</string>
<string name="pin_target" msgid="8036028973110156895">"Prikvači"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Otkvači"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informacije o aplikaciji"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Pokretanje demo-načina..."</string>
@@ -1938,6 +1943,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Želite li ažurirati ove stavke u oznaci "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> i <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Spremi"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ne, hvala"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ne sad"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nikad"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Ažuriraj"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Nastavi"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"zaporku"</string>
@@ -2034,5 +2041,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Uključite ili isključite podijeljeni zaslon"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Zaključajte zaslon"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Snimka zaslona"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u skočnom prozoru."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Traka naslova aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 1d1b994..df228ed 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"A munkaprofil rendszergazdai alkalmazása hiányzik vagy sérült. A rendszer ezért törölte a munkaprofilt, és az ahhoz kapcsolódó adatokat. Ha segítségre van szüksége, vegye fel a kapcsolatot rendszergazdájával."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Munkaprofilja már nem hozzáférhető ezen az eszközön."</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Túl sok jelszómegadási kísérlet"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Az adminisztrátor átadta az eszközt személyes használatra"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Felügyelt eszköz"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Ezt az eszközt szervezete kezeli, és lehetséges, hogy a hálózati forgalmat is figyelik. További részletekért koppintson."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"A rendszer törölni fogja eszközét"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Lehetővé teszi az alkalmazás számára ragadós üzenetek küldését, amelyek az üzenetszórás után is megmaradnak. A túlzott használat nagy mennyiségű memóriát igényel, ezért lelassíthatja vagy instabillá teheti az Android TV eszközt."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Lehetővé teszi az alkalmazás számára ragadós üzenetek küldését, amelyek a sugárzás után is megmaradnak. A túlzott használat lelassíthatja vagy instabillá teheti a telefont a nagymértékű memóriahasználattal."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"saját névjegyek olvasása"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Lehetővé teszi az alkalmazás számára a táblagépen tárolt névjegyekre vonatkozó összes adat -- például az egyes személyekkel telefonon, e-mailben vagy más módon folytatott kommunikáció gyakoriságára vonatkozó adatok -- beolvasását. Az engedéllyel rendelkező alkalmazások menthetik a névjegyadatokat, és a rosszindulatú alkalmazások az Ön tudta nélkül oszthatják meg a névjegyadatokat."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Lehetővé teszi az alkalmazás számára, hogy beolvassa az Android TV eszközön tárolt névjegyek adatait, beleértve az egyes személyekkel kapcsolatos hívások, e-mailek és egyéb kommunikáció gyakoriságára vonatkozó adatokat. Ez az engedély lehetővé teszi az alkalmazások számára a névjegyadatok mentését, így a rosszindulatú alkalmazások az Ön tudta nélkül megoszthatják őket."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Lehetővé teszi az alkalmazás számára a telefonon tárolt névjegyekre vonatkozó összes adat -- például az egyes személyekkel telefonon, e-mailben vagy más módon folytatott kommunikáció gyakoriságára vonatkozó adatok -- beolvasását. Az engedéllyel rendelkező alkalmazások menthetik a névjegyadatokat, és a rosszindulatú alkalmazások az Ön tudta nélkül oszthatják meg a névjegyadatokat."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Lehetővé teszi az alkalmazás számára a táblagépen tárolt névjegyekre vonatkozó adatok olvasását. Az alkalmazások ezenkívül hozzáférnek azokhoz a fiókokhoz is a táblagépen, amelyek létrehoztak névjegyeket. Ebbe beletartozhatnak a telepített alkalmazások által létrehozott fiókok is. Ez az engedély lehetővé teszi az alkalmazások számára a névjegyadatok mentését, így a rosszindulatú alkalmazások az Ön tudta nélkül megoszthatják őket."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Lehetővé teszi az alkalmazás számára az Android TV-n tárolt névjegyekre vonatkozó adatok olvasását. Az alkalmazások ezenkívül hozzáférnek azokhoz a fiókokhoz is az Android TV-eszközön, amelyek létrehoztak névjegyeket. Ebbe beletartozhatnak a telepített alkalmazások által létrehozott fiókok is. Ez az engedély lehetővé teszi az alkalmazások számára a névjegyadatok mentését, így a rosszindulatú alkalmazások az Ön tudta nélkül megoszthatják őket."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Lehetővé teszi az alkalmazás számára a telefonon tárolt névjegyekre vonatkozó adatok olvasását. Az alkalmazások ezenkívül hozzáférnek azokhoz a fiókokhoz is a telefonon, amelyek létrehoztak névjegyeket. Ebbe beletartozhatnak a telepített alkalmazások által létrehozott fiókok is. Ez az engedély lehetővé teszi az alkalmazások számára a névjegyadatok mentését, így a rosszindulatú alkalmazások az Ön tudta nélkül megoszthatják őket."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"kapcsolatok módosítása"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Lehetővé teszi az alkalmazás számára a táblagépen tárolt névjegyekre vonatkozó adatok -- például az egyes személyekkel telefonon, e-mailben vagy más módon folytatott kommunikáció gyakoriságára vonatkozó adatok -- módosítását. Az engedéllyel rendelkező alkalmazás törölheti a névjegyadatokat."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Lehetővé teszi az alkalmazás számára az Android TV eszközön tárolt névjegyekre vonatkozó adatok módosítását – ilyenek például az egyes személyekkel telefonon, e-mailben vagy más módon folytatott kommunikáció gyakoriságára vonatkozó adatok. Ezzel az engedéllyel az alkalmazások törölhetik a névjegyadatokat."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Lehetővé teszi az alkalmazás számára a telefonon tárolt névjegyekre vonatkozó adatok -- például az egyes személyekkel telefonon, e-mailben vagy más módon folytatott kommunikáció gyakoriságára vonatkozó adatok -- módosítását. Az engedéllyel rendelkező alkalmazás törölheti a névjegyadatokat."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Lehetővé teszi az alkalmazás számára a táblagépen tárolt névjegyekre vonatkozó adatok módosítását. Ezzel az engedéllyel az alkalmazások törölhetik a névjegyadatokat."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Lehetővé teszi az alkalmazás számára az Android TV-n tárolt névjegyekre vonatkozó adatok módosítását. Ezzel az engedéllyel az alkalmazások törölhetik a névjegyadatokat."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Lehetővé teszi az alkalmazás számára a telefonon tárolt névjegyekre vonatkozó adatok módosítását. Ezzel az engedéllyel az alkalmazások törölhetik a névjegyadatokat."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"Híváslista beolvasása"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Az alkalmazás olvashatja az Ön híváslistáját."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"Híváslista készítése"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"további helyszolgáltatói parancsok elérése"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Lehetővé teszi az alkalmazás számára további helyszolgáltatói parancsok elérését. Ezáltal az alkalmazás beavatkozhat a GPS vagy más helyforrások működésébe."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"pontos helyadatokhoz való hozzáférés csak előtérben"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Ez az alkalmazás csak akkor férhet hozzá az eszköz pontos helyadataihoz, amikor az előtérben fut. A helyszolgáltatásoknak bekapcsolt és hozzáférhető állapotban kell lenniük a telefonon ahhoz, hogy az alkalmazás használhassa őket. Ez növelheti az akkumulátorhasználatot."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"hozzávetőleges (hálózaton alapuló) helyadatokhoz való hozzáférés csak előtérben"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ez az alkalmazás hozzáférhet a hálózati forrásokon (például az adótornyokon és a Wi-Fi-hálózatokon) alapuló helyadataihoz, de csak akkor, amikor az előtérben fut. A helyszolgáltatásoknak bekapcsolt és hozzáférhető állapotban kell lenniük a táblagépen ahhoz, hogy az alkalmazás használhassa őket."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ez az alkalmazás hozzáférhet a hálózati forrásokon (például az adótornyokon és a Wi-Fi-hálózatokon) alapuló helyadataihoz, de csak akkor, amikor az előtérben fut. A helyszolgáltatásoknak bekapcsolt és hozzáférhető állapotban kell lenniük az Android TV eszközön ahhoz, hogy az alkalmazás használhassa őket."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ez az alkalmazás hozzáférhet a hálózati forrásokon (például az adótornyokon és a Wi-Fi-hálózatokon) alapuló helyadataihoz, de csak akkor, amikor az előtérben fut. A helyszolgáltatásoknak bekapcsolt és hozzáférhető állapotban kell lenniük a telefonon ahhoz, hogy az alkalmazás használhassa őket."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ez az alkalmazás csak akkor férhet hozzá az eszköz pontos helyadataihoz, amikor az előtérben fut. Az alkalmazás csak akkor használhatja a helyszolgáltatásokat, ha be vannak kapcsolva, és hozzáférhetők az eszköz számára. Ez növelheti az akkumulátorhasználatot."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"megközelítőleges helyadatokhoz való hozzáférés csak előtérben"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ez az alkalmazás csak akkor férhet hozzá az eszköz megközelítőleges helyadataihoz, amikor az előtérben fut. Az alkalmazás csak akkor használhatja a helyszolgáltatásokat, ha be vannak kapcsolva, és hozzáférhetők az eszköz számára."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"Hozzáférés a helyadatokhoz a háttérben"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ha engedélyezi ezt a hozzávetőleges vagy pontos helyadatokhoz való hozzáférés mellett, akkor az alkalmazás hozzáférhet a helyadatokhoz, miközben a háttérben fut."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ez az alkalmazás nem csak akkor férhet hozzá a helyadatokhoz, amikor az előtérben van, hanem akkor is, amikor a háttérben fut."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"hangbeállítások módosítása"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lehetővé teszi az alkalmazás számára az általános hangbeállítások, például a hangerő és a használni kívánt kimeneti hangszóró módosítását."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"hanganyag rögzítése"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Lehetővé teszi az alkalmazás számára a táblagépen lévő Bluetooth beállításainak megtekintését, valamint kapcsolatok kezdeményezését és fogadását a párosított eszközökkel."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Lehetővé teszi az alkalmazás számára az Android TV eszköz Bluetooth-konfigurációjának megtekintését, valamint párosított eszközökkel való kapcsolatok kezdeményezését és fogadását."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Lehetővé teszi az alkalmazás számára a telefonon lévő Bluetooth beállításainak megtekintését, valamint kapcsolatok kezdeményezését és fogadását a párosított eszközökkel."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"NFC technológia vezérlése"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Lehetővé teszi az alkalmazás számára, hogy NFC (Near Field Communication - kis hatósugarú vezeték nélküli kommunikáció) technológiát használó címkékkel, kártyákkal és leolvasókkal kommunikáljon."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"képernyőzár kikapcsolása"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Csatlakoztatva a(z) <xliff:g id="PRODUCT_NAME">%1$s</xliff:g> eszközhöz"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Koppintson ide a fájlok megtekintéséhez"</string>
<string name="pin_target" msgid="8036028973110156895">"Rögzítés"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Feloldás"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Alkalmazásinformáció"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Bemutató indítása…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Frissíti a(z) "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" szolgáltatásban a következőket: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> és <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Mentés"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nem, köszönöm"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ne most"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Soha"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Frissítés"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Tovább"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"jelszó"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Osztott képernyő be- vagy kikapcsolása"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lezárási képernyő"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Képernyőkép"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás előugró ablakban."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás címsora."</string>
</resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 599ce86..5b49ac4 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Աշխատանքային պրոֆիլի ադմինիստրատորի հավելվածը բացակայում է կամ վնասված է: Արդյունքում ձեր աշխատանքային պրոֆիլը և առնչվող տվյալները ջնջվել են: Օգնության համար դիմեք ձեր ադմինիստրատորին:"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Ձեր աշխատանքային պրոֆիլն այս սարքում այլևս հասանելի չէ"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Գաղտնաբառը մուտքագրելու չափից շատ փորձեր են կատարվել"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Ադմինիստրատորը տրամադրել է սարքը անձնական օգտագործման համար"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Սարքը կառավարվում է"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Ձեր կազմակերպությունը կառավարում է այս սարքը և կարող է վերահսկել ցանցի թրաֆիկը: Հպեք՝ մանրամասները դիտելու համար:"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Ձեր սարքը ջնջվելու է"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Թույլ է տալիս հավելվածին կատարել անընդմեջ հեռարձակումներ, որոնցից հետո տվյալները հասանելի են մնում: Չափից դուրս օգտագործումը կարող է դանդաղեցնել Android TV սարքի աշխատանքը կամ դարձնել այն անկայուն՝ ավելացնելով հիշողության ծախսը:"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Թույլ է տալիս հավելվածին ուղարկել կպչուն հաղորդումներ, որոնք մնում են հաղորդման ավարտից հետո: Չափազանց շատ օգտագործումը կարող է հեռախոսի աշխատանքը դանդաղեցնել կամ դարձնել անկայուն` պատճառ դառնալով չափազանց մեծ հիշողության օգտագործման:"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"կարդալ ձեր կոնտակտները"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Թույլ է տալիս հավելվածին կարդալ ձեր պլանշետում պահված կոնտակտների մասին տվյալները, այդ թվում` ձեր կատարած զանգերի, գրած նամակների կամ որոշակի անհատների հետ այլ եղանակով շփման հաճախականությունը: Այս թույլտվությունը հնարավորություն է տալիս հավելվածներին պահել ձեր կոնտակտային տվյալները, իսկ վնասարար հավելվածները կարող են տարածել կոնտակտային տվյալները` առանց ձեր իմացության:"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Թույլ է տալիս հավելվածին կարդալ Android TV սարքում պահված կոնտակտների տվյալները, այդ թվում նաև՝ թե ինչ հաճախականությամբ եք զանգեր կատարել, օգտվել էլփոստից կամ այլ կերպ հաղորդակցվել որոշակի մարդկանց հետ: Այս թույլտվության միջոցով հավելվածները կարող են պահել ձեր կոնտակտների տվյալները, իսկ վնասարար հավելվածները կարող են օգտագործել դրանք առանց ձեր իմացության:"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Թույլ է տալիս հավելվածին կարդալ ձեր հեռախոսում պահված կոնտակտների մասին տվյալները, այդ թվում` ձեր կատարած զանգերի, գրած նամակների կամ որոշակի անհատների հետ այլ եղանակով շփման հաճախականությունը: Այս թույլտվությունը հնարավորություն է տալիս հավելվածներին պահել ձեր կոնտակտային տվյալները, իսկ վնասարար հավելվածները կարող են տարածել կոնտակտային տվյալները` առանց ձեր իմացության:"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Թույլ է տալիս հավելվածին կարդալ ձեր պլանշետում պահված կոնտակտների մասին տվյալները։ Հավելվածներին նաև հասանելի կլինեն հաշիվները ձեր պլանշետում, որտեղ ստեղծվել են կոնտակտներ։ Այդ հաշիվները կարող են ստեղծված լինել ձեր տեղադրած հավելվածների կողմից։ Այս թույլտվության միջոցով հավելվածները կարող են պահել ձեր կոնտակտների մասին տվյալները, իսկ վնասարար հավելվածները կարող են օգտագործել դրանք առանց ձեր իմացության։"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Թույլ է տալիս հավելվածին կարդալ Android TV սարքում պահված կոնտակտների մասին տվյալները։ Հավելվածներին նաև հասանելի կլինեն հաշիվներն Android TV սարքում, որտեղ ստեղծվել են կոնտակտներ։ Այդ հաշիվները կարող են ստեղծված լինել ձեր տեղադրած հավելվածների կողմից։ Այս թույլտվության միջոցով հավելվածները կարող են պահել ձեր կոնտակտների մասին տվյալները, իսկ վնասարար հավելվածները կարող են օգտագործել դրանք առանց ձեր իմացության։"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Թույլ է տալիս հավելվածին կարդալ ձեր հեռախոսում պահված կոնտակտների մասին տվյալները։ Հավելվածներին նաև հասանելի կլինեն հաշիվները ձեր հեռախոսում, որտեղ ստեղծվել են կոնտակտներ։ Այդ հաշիվները կարող են ստեղծված լինել ձեր տեղադրած հավելվածների կողմից։ Այս թույլտվության միջոցով հավելվածները կարող են պահել ձեր կոնտակտների մասին տվյալները, իսկ վնասարար հավելվածները կարող են օգտագործել դրանք առանց ձեր իմացության։"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"փոփոխել ձեր կոնտակտները"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Թույլ է տալիս հավելվածին փոփոխել ձեր պլանշետում պահված կոնտակտների մասին տվյալները, այդ թվում` ձեր կատարած զանգերի, գրած նամակների կամ որոշակի անհատների հետ այլ եղանակով շփման հաճախականությունը: Այս թույլտվությունը հնարավորություն է տալիս հավելվածներին ջնջել կոնտակտային տվյալները:"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Թույլ է տալիս հավելվածին փոփոխել Android TV սարքում պահված կոնտակտների տվյալները, այդ թվում նաև՝ թե ինչ հաճախականությամբ եք զանգեր կատարել, օգտվել էլփոստից կամ այլ կերպ հաղորդակցվել որոշակի մարդկանց հետ: Այս թույլտվությունը հնարավորություն է տալիս հավելվածներին ջնջել կոնտակտային տվյալները:"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Թույլ է տալիս հավելվածին փոփոխել ձեր պլանշետում պահված կոնտակտների տվյալները, այդ թվում` ձեր կատարած զանգերի, գրած նամակների կամ որոշակի անհատների հետ այլ եղանակով շփման հաճախականությունը: Այս թույլտվությունը հնարավորություն է տալիս հավելվածներին ջնջել կոնտակտային տվյալները:"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Թույլ է տալիս հավելվածին փոփոխել ձեր պլանշետում պահված կոնտակտների մասին տվյալները։ Այս թույլտվությունը հնարավորություն է տալիս հավելվածներին ջնջել կոնտակտների մասին տվյալները։"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Թույլ է տալիս հավելվածին փոփոխել ձեր Android TV սարքում պահված կոնտակտների մասին տվյալները։ Այս թույլտվությունը հնարավորություն է տալիս հավելվածներին ջնջել կոնտակտների մասին տվյալները։"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Թույլ է տալիս հավելվածին փոփոխել ձեր հեռախոսում պահված կոնտակտների մասին տվյալները։ Այս թույլտվությունը հնարավորություն է տալիս հավելվածներին ջնջել կոնտակտների մասին տվյալները։"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"կարդալ զանգերի մատյանը"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Այս հավելվածը կարող է կարդալ ձեր զանգերի պատմությունը:"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"տեսնել զանգերի գրանցամատյանը"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"օգտագործել տեղադրություն տրամադրող հավելվյալ հրամաններ"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Ծրագրին թույլ է տալիս օգտագործել տեղադրության մասին տվյալների աղբյուրների կառավարման լրացուցիչ հրահանգներ: Սա կարող է ծրագրին թույլ տալ միջամտել GPS-ի կամ տեղադրության մասին տվյալների այլ աղբյուրների գործառույթներին:"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"Տեղադրության ճշգրիտ տվյալների հասանելիություն միայն ֆոնային ռեժիմում"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Հավելվածը կարող է ստանալ ձեր տեղադրության տվյալները ֆոնային ռեժիմում։ Այս տեղորոշման ծառայությունները պետք է միացված և հասանելի լինեն ձեր հեռախոսում, որպեսզի հավելվածը կարողանա օգտագործել դրանք: Սա կարող է արագացնել մարտկոցի լիցքի սպառումը:"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"Մոտավոր տեղադրությունը (ցանցային) հասանելի է միայն ֆոնային ռեժիմում"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Այս հավելվածը կարող է ստանալ ձեր տեղադրության տվյալները ցանցային տարբեր աղբյուրներից, օրինակ՝ բջջային աշտարակներից և Wi-Fi ցանցերից, սակայն միայն երբ հավելվածն աշխատում է ֆոնային ռեժիմում: Այս տեղորոշման ծառայությունները պետք է միացված և հասանելի լինեն ձեր պլանշետում, որպեսզի հավելվածը կարողանա օգտագործել դրանք:"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Այս հավելվածը կարող է ստանալ ձեր տեղադրության տվյալները ցանցային տարբեր աղբյուրներից, օրինակ՝ բջջային աշտարակներից և Wi-Fi ցանցերից, սակայն միայն երբ հավելվածն աշխատում է ֆոնային ռեժիմում: Այս տեղորոշման ծառայությունները պետք է միացված և հասանելի լինեն ձեր Android TV սարքում, որպեսզի հավելվածը կարողանա օգտագործել դրանք:"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Այս հավելվածը կարող է ստանալ ձեր տեղադրության տվյալները ցանցային տարբեր աղբյուրներից, օրինակ՝ բջջային աշտարակներից և Wi-Fi ցանցերից, սակայն միայն երբ հավելվածն աշխատում է ֆոնային ռեժիմում: Այս տեղորոշման ծառայությունները պետք է միացված և հասանելի լինեն ձեր հեռախոսում, որպեսզի հավելվածը կարողանա օգտագործել դրանք:"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Հավելվածը կարող է ստանալ ձեր տեղադրության տվյալները ակտիվ ռեժիմում։ Տեղորոշման ծառայությունները պետք է միացված և հասանելի լինեն ձեր սարքում, որպեսզի հավելվածը կարողանա օգտագործել դրանք։ Սա կարող է արագացնել մարտկոցի լիցքի սպառումը։"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"տեղադրության մոտավոր տվյալների հասանելիություն միայն ակտիվ ռեժիմում"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Հավելվածը կարող է ստանալ ձեր մոտավոր տեղադրության տվյալները ակտիվ ռեժիմում։ Տեղորոշման ծառայությունները պետք է միացված և հասանելի լինեն ձեր սարքում, որպեսզի հավելվածը կարողանա օգտագործել դրանք։"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"տեղադրության մասին տվյալների հասանելիություն ֆոնային ռեժիմում"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Եթե բացի մոտավոր կամ ճշգրիտ տեղորոշման տվյալների հասանելիությունից հավելվածին տրամադրեք այս թույլտվությունը, հավելվածին տեղադրության մասին տվյալները հասանելի կլինեն ֆոնային ռեժիմում։"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Այս հավելվածը ինչպես ակտիվ, այնպես էլ ֆոնային ռեժիմում աշխատելիս կարող է տեսնել տեղադրության տվյալները։"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"փոխել ձեր աուդիո կարգավորումները"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Թույլ է տալիս հավելվածին փոփոխել ձայնանյութի գլոբալ կարգավորումները, ինչպես օրինակ` ձայնը և թե որ խոսափողն է օգտագործված արտածման համար:"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ձայնագրել աուդիո ֆայլ"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Թույլ է տալիս հավելվածին տեսնել Bluetooth-ի կարգավորումը պլանշետի վրա և կապվել ու կապեր ընդունել զուգակցված սարքերի հետ:"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Թույլ է տալիս հավելվածին տեսնել Bluetooth-ի կազմաձևումը Android TV սարքի վրա և կապվել ու թույլ տալ կապակցումները զուգակցված սարքերի հետ:"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Թույլ է տալիս հավելվածին տեսնել Bluetooth-ի կարգավորումը հեռախոսի վրա և կապվել ու կապեր ընդունել զուգակցված սարքերի հետ:"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"վերահսկել Մոտ Տարածությամբ Հաղորդակցումը"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Թույլ է տալիս հավելվածին հաղորդակցվել Մոտ տարածությամբ հաղորդակցման (NFC) պիտակների, քարտերի և ընթերցիչների հետ:"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"անջատել ձեր էկրանի կողպեքը"</string>
@@ -827,7 +828,7 @@
<string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Դադարեցնել"</string>
<string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Հետ փաթաթել"</string>
<string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Արագ առաջ անցնել"</string>
- <string name="emergency_calls_only" msgid="3057351206678279851">"Միայն արտակարգ իրավիճակների զանգեր"</string>
+ <string name="emergency_calls_only" msgid="3057351206678279851">"Միայն շտապ կանչեր"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Ցանցը կողպված է"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"SIM քարտը PUK-ով կողպված է:"</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"Տեսեք Օգտատիրոջ ուղեցույցը կամ դիմեք Բաժանորդների սպասարկման կենտրոն:"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Միացված է <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>-ին"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Հպեք՝ ֆայլերը տեսնելու համար"</string>
<string name="pin_target" msgid="8036028973110156895">"Ամրացնել"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Ապամրացնել"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Հավելվածի տվյալներ"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Ցուցադրական օգտատերը գործարկվում է…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Թարմացնե՞լ տվյալները (<xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, <xliff:g id="TYPE_2">%3$s</xliff:g>) "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ծառայությունում"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Պահել"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ոչ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ոչ հիմա"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Երբեք"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Թարմացնել"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Շարունակել"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"գաղտնաբառ"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Միացնել/անջատել էկրանի տրոհումը"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Կողպէկրան"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Սքրինշոթ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ելնող պատուհանում։"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի ենթագրերի գոտին։"</string>
</resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 1f81eb5..1ee17f4 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplikasi admin profil kerja tidak ada atau rusak. Akibatnya, profil kerja dan data terkait telah dihapus. Hubungi admin untuk meminta bantuan."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profil kerja tidak tersedia lagi di perangkat ini"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Terlalu banyak percobaan memasukkan sandi"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Admin melepaskan perangkat untuk penggunaan pribadi"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Perangkat ini ada yang mengelola"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Organisasi mengelola perangkat ini dan mungkin memantau traffic jaringan. Ketuk untuk melihat detailnya."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Perangkat akan dihapus"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Mengizinkan aplikasi mengirim siaran permanen, yang tetap ada setelah siaran berakhir. Penggunaan yang berlebihan dapat membuat perangkat Android TV menjadi lambat atau tidak stabil dengan memicu penggunaan memori yang terlalu banyak."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Memungkinkan aplikasi mengirim siaran permanen, yang tetap ada setelah siaran berakhir. Penggunaan yang berlebihan dapat membuat ponsel menjadi lambat atau tidak stabil dengan memicu penggunaan memori yang terlalu banyak."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"baca kontak Anda"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Memungkinkan aplikasi membaca data tentang kontak yang disimpan pada tablet Anda, termasuk frekuensi Anda dalam melakukan panggilan, mengirim email, atau berkomunikasi dengan cara lain dengan individu tertentu. Izin ini memungkinkan aplikasi menyimpan data kontak, dan aplikasi berbahaya dapat berbagi data kontak tanpa sepengetahuan Anda."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Mengizinkan aplikasi membaca data tentang kontak yang disimpan di perangkat Android TV, termasuk frekuensi yang Anda gunakan saat melakukan panggilan, mengirim email, atau berkomunikasi dalam cara lain dengan individu tertentu. Izin ini memungkinkan aplikasi menyimpan data kontak, dan aplikasi berbahaya dapat membagikan data kontak tanpa sepengetahuan Anda."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Memungkinkan aplikasi membaca data tentang kontak yang disimpan pada ponsel Anda, termasuk frekuensi Anda dalam melakukan panggilan, mengirim email, atau berkomunikasi dengan cara lain dengan individu tertentu. Izin ini memungkinkan aplikasi menyimpan data kontak, dan aplikasi berbahaya dapat berbagi data kontak tanpa sepengetahuan Anda."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Mengizinkan aplikasi membaca data tentang kontak yang disimpan di tablet Anda. Aplikasi juga akan memiliki akses ke akun di tablet Anda yang telah membuat kontak. Ini mungkin mencakup akun yang dibuat oleh aplikasi yang telah diinstal. Izin ini mengizinkan aplikasi menyimpan data kontak, dan aplikasi berbahaya dapat membagikan data kontak tanpa sepengetahuan Anda."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Mengizinkan aplikasi membaca data tentang kontak yang disimpan di perangkat Android TV Anda. Aplikasi juga akan memiliki akses ke akun di perangkat Android TV Anda yang telah membuat kontak. Ini mungkin mencakup akun yang dibuat oleh aplikasi yang telah diinstal. Izin ini mengizinkan aplikasi menyimpan data kontak, dan aplikasi berbahaya dapat membagikan data kontak tanpa sepengetahuan Anda."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Mengizinkan aplikasi membaca data tentang kontak yang disimpan di ponsel Anda. Aplikasi juga akan memiliki akses ke akun di ponsel Anda yang telah membuat kontak. Ini mungkin mencakup akun yang dibuat oleh aplikasi yang telah diinstal. Izin ini mengizinkan aplikasi menyimpan data kontak, dan aplikasi berbahaya dapat membagikan data kontak tanpa sepengetahuan Anda."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ubah kontak Anda"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Memungkinkan aplikasi mengubah data tentang kontak yang tersimpan dalam tablet Anda, termasuk frekuensi Anda dalam melakukan panggilan, mengirim email, atau berkomunikasi dalam cara lain dengan kontak tertentu. Izin ini memungkinkan aplikasi menghapus data kontak."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Mengizinkan aplikasi mengubah data tentang kontak yang disimpan di perangkat Android TV Anda, termasuk frekuensi yang Anda gunakan saat melakukan panggilan, mengirim email, atau berkomunikasi dalam cara lain dengan kontak tertentu. Izin ini memungkinkan aplikasi menghapus data kontak."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Memungkinkan aplikasi mengubah data tentang kontak yang tersimpan dalam ponsel Anda, termasuk frekuensi Anda dalam melakukan panggilan, mengirim email, atau berkomunikasi dalam cara lain dengan kontak tertentu. Izin ini memungkinkan aplikasi menghapus data kontak."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Mengizinkan aplikasi mengubah data tentang kontak yang disimpan di tablet Anda. Izin ini mengizinkan aplikasi menghapus data kontak."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Mengizinkan aplikasi mengubah data tentang kontak yang disimpan di perangkat Android TV Anda. Izin ini mengizinkan aplikasi menghapus data kontak."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Mengizinkan aplikasi mengubah data tentang kontak yang disimpan di ponsel Anda. Izin ini mengizinkan aplikasi menghapus data kontak."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"membaca log panggilan"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Aplikasi ini dapat membaca histori panggilan."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"menulis log panggilan"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"akses perintah penyedia lokasi ekstra"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Memungkinkan aplikasi mengakses perintah penyedia lokasi ekstra. Tindakan ini memungkinkan aplikasi mengganggu pengoperasian GPS atau sumber lokasi lain."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"akses lokasi pasti hanya saat di latar depan"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Aplikasi ini bisa mendapatkan lokasi pasti Anda ketika aplikasi berada di latar depan. Fitur layanan lokasi ini harus diaktifkan dan tersedia di ponsel agar dapat digunakan oleh aplikasi. Fitur ini dapat meningkatkan konsumsi baterai."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"akses perkiraan lokasi (berbasis jaringan) hanya di latar depan"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Aplikasi ini dapat mengetahui lokasi berdasarkan sumber jaringan seperti menara seluler dan jaringan Wi-Fi, namun hanya jika aplikasi berada di latar depan. Layanan lokasi tersebut harus diaktifkan dan tersedia di tablet agar aplikasi dapat menggunakannya."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Aplikasi ini dapat mengetahui lokasi Anda berdasarkan sumber jaringan seperti menara seluler dan jaringan Wi-Fi, tetapi hanya jika aplikasi berada di latar depan. Layanan lokasi ini harus diaktifkan dan tersedia di perangkat Android TV agar dapat digunakan oleh aplikasi."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Aplikasi ini dapat mengetahui lokasi berdasarkan sumber jaringan seperti menara seluler dan jaringan Wi-Fi, namun hanya jika aplikasi berada di latar depan. Layanan lokasi tersebut harus diaktifkan dan tersedia di ponsel agar aplikasi dapat menggunakannya."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Aplikasi ini bisa mendapatkan lokasi pasti Anda saat berada di latar depan. Fitur layanan lokasi harus diaktifkan dan tersedia di perangkat agar dapat digunakan oleh aplikasi. Fitur ini dapat meningkatkan konsumsi baterai."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"akses perkiraan lokasi hanya saat berada di latar depan"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Aplikasi ini bisa mendapatkan perkiraan lokasi Anda hanya saat berada di latar depan. Layanan lokasi harus diaktifkan dan tersedia di perangkat agar aplikasi dapat menggunakannya."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"akses lokasi di latar belakang"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Jika aplikasi diberi izin tambahan ke akses lokasi perkiraan atau akurat, aplikasi dapat mengakses lokasi saat bekerja di latar belakang."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Aplikasi ini dapat mengakses lokasi saat berjalan di latar belakang, selain akses lokasi latar depan."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ubah setelan audio Anda"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Memungkinkan aplikasi mengubah setelan audio global, misalnya volume dan pengeras suara mana yang digunakan untuk keluaran."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"rekam audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Memungkinkan aplikasi melihat konfigurasi Bluetooth di tablet, dan membuat serta menerima sambungan dengan perangkat yang disandingkan."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Mengizinkan aplikasi melihat konfigurasi Bluetooth di perangkat Android TV, serta melakukan dan menerima sambungan dengan perangkat yang tersambung."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Memungkinkan aplikasi melihat konfigurasi Bluetooth di ponsel, dan membuat serta menerima sambungan dengan perangkat yang disandingkan."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontrol NFC"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Mengizinkan apl berkomunikasi dengan tag, kartu, dan alat pembaca Komunikasi Nirkabel Jarak Dekat (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"nonaktifkan kunci layar Anda"</string>
@@ -1535,8 +1536,8 @@
<string name="launchBrowserDefault" msgid="6328349989932924119">"Luncurkan Browser?"</string>
<string name="SetupCallDefault" msgid="5581740063237175247">"Terima panggilan?"</string>
<string name="activity_resolver_use_always" msgid="5575222334666843269">"Selalu"</string>
- <string name="activity_resolver_set_always" msgid="4142825808921411476">"Setel untuk selalu membuka"</string>
- <string name="activity_resolver_use_once" msgid="948462794469672658">"Hanya sekali"</string>
+ <string name="activity_resolver_set_always" msgid="4142825808921411476">"Selalu gunakan"</string>
+ <string name="activity_resolver_use_once" msgid="948462794469672658">"Sekali ini saja"</string>
<string name="activity_resolver_app_settings" msgid="6758823206817748026">"Setelan"</string>
<string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"%1$s tidak mendukung profil kerja"</string>
<string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"Tablet"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Tersambung ke <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Ketuk untuk melihat file"</string>
<string name="pin_target" msgid="8036028973110156895">"Pasang pin"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Lepas pin"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Info aplikasi"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Memulai demo..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Perbarui item-item berikut di "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, dan <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Simpan"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Lain kali"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Lain kali"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Tidak pernah"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Lanjutkan"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"sandi"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Aktifkan/Nonaktifkan Layar Terpisah"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Layar Kunci"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikasi <xliff:g id="APP_NAME">%1$s</xliff:g> di Jendela pop-up."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Kolom teks <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index d23cf5f..3e67533 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Stjórnunarforrit vinnusniðsins vantar eða er skemmt. Vinnusniðinu og gögnum því tengdu hefur því verið eytt. Hafðu samband við kerfisstjórann til að fá frekari aðstoð."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Vinnusniðið þitt er ekki lengur í boði á þessu tæki"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Of margar tilraunir til að slá inn aðgangsorð"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Kerfisstjóri lét af hendi tæki til einkanota"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Tækinu er stjórnað"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Fyrirtækið þitt stjórnar þessu tæki og kann að fylgjast með netnotkun. Ýttu hér til að fá upplýsingar."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Tækið verður hreinsað"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Leyfir forritinu að senda viðvarandi tilkynningar, sem eru áfram í gangi eftir birtingu. Of mikil notkun þeirra getur hægt á virkni Android TV tækisins eða gert það óstöðugt með því að nota of mikið af minni þess."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Leyfir forriti að senda viðvarandi tilkynningar, sem eru áfram í gangi eftir birtingu. Of mikil notkun þeirra getur hægt á virkni símans eða gert hann óstöðugan með því að nota of mikið af minni hans."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lesa tengiliði"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Leyfir forriti að lesa gögn um tengiliði sem vistuð eru í spjaldtölvunni, þ. á m. tíðni samskipta þinna við tiltekna tengiliði með símtölum, tölvupósti eða öðrum hætti. Þessi heimild gerir forritum kleift að vista tengiliðagögnin þín og spilliforrit kunna að deila tengiliðaupplýsingum án þinnar vitundar."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Leyfir forriti að lesa gögn um tengiliði sem vistuð eru í Android TV, þ. á m. tíðni samskipta þinna við tiltekna tengiliði með símtölum, tölvupósti eða öðrum hætti. Þessi heimild gerir forritum kleift að vista tengiliðagögnin þín og spilliforrit kunna að deila tengiliðaupplýsingum án þinnar vitundar."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Leyfir forriti að lesa gögn um tengiliði sem vistuð eru í símanum, þ. á m. tíðni samskipta þinna við tiltekna tengiliði með símtölum, tölvupósti eða öðrum hætti. Þessi heimild gerir forritum kleift að vista tengiliðagögnin þín og spilliforrit kunna að deila tengiliðaupplýsingum án þinnar vitundar."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Leyfir forritinu að lesa gögn um tengiliði sem vistuð eru í spjaldtölvunni þinni. Forrit munu einnig hafa aðgang að reikningum í spjaldtölvunni sem hafa búið til tengiliði. Þar á meðal geta verið reikningar sem forrit sem þú hefur sett upp hafa stofnað. Þessi heimild gerir forritum kleift að vista tengiliðagögnin þín og spilliforrit kunna að deila tengiliðaupplýsingum án þinnar vitundar."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Leyfir forritinu að lesa gögn um tengiliði sem vistuð eru í Android TV tækinu. Forrit munu einnig hafa aðgang að reikningum í Android TV tækinu sem hafa búið til tengiliði. Þar á meðal geta verið reikningar sem forrit sem þú hefur sett upp hafa stofnað. Þessi heimild gerir forritum kleift að vista tengiliðagögnin þín og spilliforrit kunna að deila tengiliðaupplýsingum án þinnar vitundar."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Leyfir forritinu að lesa gögn um tengiliði sem vistuð eru í símanum þínum. Forrit munu einnig hafa aðgang að reikningum í símanum sem hafa búið til tengiliði. Þar á meðal geta verið reikningar sem forrit sem þú hefur sett upp hafa stofnað. Þessi heimild gerir forritum kleift að vista tengiliðagögnin þín og spilliforrit kunna að deila tengiliðaupplýsingum án þinnar vitundar."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"breyta tengiliðunum þínum"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Leyfir forriti að breyta gögnum um tengiliði sem vistuð eru í spjaldtölvunni, þ. á m. tíðni samskipta þinna við tiltekna tengiliði með símtölum, tölvupósti eða öðrum hætti. Þessi heimild gerir forritum kleift að eyða tengiliðagögnum."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Leyfir forriti að breyta gögnum um tengiliði sem vistuð eru í Android TV, þ. á m. tíðni samskipta þinna við tiltekna tengiliði með símtölum, tölvupósti eða öðrum hætti. Þessi heimild gerir forritum kleift að eyða tengiliðagögnum."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Leyfir forriti að breyta gögnum um tengiliði sem vistuð eru í símanum, þ. á m. tíðni samskipta þinna við tiltekna tengiliði með símtölum, tölvupósti eða öðrum hætti. Þessi heimild gerir forritum kleift að eyða tengiliðagögnum."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Leyfir forriti að breyta gögnum um tengiliði sem vistuð eru í spjaldtölvunni. Þessi heimild gerir forritum kleift að eyða tengiliðagögnum."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Leyfir forriti að breyta gögnum um tengiliði sem vistuð eru í Android TV tækinu. Þessi heimild gerir forritum kleift að eyða tengiliðagögnum."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Leyfir forriti að breyta gögnum um tengiliði sem vistuð eru í símanum þínum. Þessi heimild gerir forritum kleift að eyða tengiliðagögnum."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lesa símtalaskrá"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Þetta forrit getur lesið símtalaferilinn þinn."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"skrifa símtalaskrá"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"aðgangur að viðbótarskipunum staðsetningarveitu"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Leyfir forriti að fá aðgang að fleiri skipunum staðsetningarveitu. Þetta getur gert forritinu kleift að hafa áhrif á virkni GPS og annars staðsetningarbúnaðar."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"aðgangur að nákvæmri staðsetningu aðeins í forgrunni"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Þetta forrit getur aðeins séð staðsetningu þína þegar það er í forgrunni. Það verður að vera kveikt á þessari staðsetningarþjónustu og hún þarf að vera aðgengileg í símanum til að forritið geti notað hana. Þetta getur aukið rafhlöðunotkun."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"aðgangur að áætlaðri staðsetningu (út frá netkerfi), aðeins í forgrunni"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Þetta forrit getur séð staðsetningu þína út frá netkerfum á borð við farsímasenda og Wi-Fi net, en þó aðeins þegar það er í forgrunni. Það verður að vera kveikt á þessari staðsetningarþjónustu og hún þarf að vera aðgengileg spjaldtölvunni til að forritið geti notað hana."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Þetta forrit getur séð staðsetningu þína út frá netkerfum á borð við farsímasenda og Wi-Fi net, en þó aðeins þegar það er í forgrunni. Það verður að vera kveikt á þessari staðsetningarþjónustu og hún þarf að vera aðgengileg Android TV tækinu til að forritið geti notað hana."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Þetta forrit getur séð staðsetningu þína út frá netkerfum á borð við farsímasenda og Wi-Fi net, en þó aðeins þegar það er í forgrunni. Það verður að vera kveikt á þessari staðsetningarþjónustu og hún þarf að vera aðgengileg símanum til að forritið geti notað hana."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Þetta forrit getur aðeins séð staðsetningu þína þegar það er í forgrunni. Það verður að vera kveikt á staðsetningarþjónustu og hún þarf að vera tiltæk í tækinu til að forritið geti notað hana. Þetta getur aukið rafhlöðunotkun."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"aðgangur að áætlaðri staðsetningu aðeins í forgrunni"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Þetta forrit getur aðeins séð áætlaða staðsetningu þína þegar það er í forgrunni. Það verður að vera kveikt á staðsetningarþjónustu og hún þarf að vera tiltæk í tækinu til að forritið geti notað hana."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"aðgangur að staðsetningu í bakgrunni"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ef þetta er veitt til viðbótar við aðgang að áætlaðri eða nákvæmri staðsetningu getur forritið fengið aðgang að staðsetningu á meðan það keyrir í bakgrunni."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Þetta forrit hefur aðgang að staðsetningu þegar það er í gangi í bakgrunni, og auk þess þegar það er í forgrunni."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"breyta hljóðstillingum"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Leyfir forriti að breyta altækum hljóðstillingum, s.s. hljóðstyrk og hvaða hátalari er notaður sem úttak."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"taka upp hljóð"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Leyfir forriti að skoða grunnstillingu Bluetooth í spjaldtölvunni og koma á og samþykkja tengingar við pöruð tæki."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Leyfir forriti að skoða stillingu Bluetooth í Android TV og tengjast og samþykkja tengingar við pöruð tæki."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Leyfir forriti að skoða grunnstillingu Bluetooth í símanum og koma á og samþykkja tengingar við pöruð tæki."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"stjórna nándarsamskiptum (NFC)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Leyfir forriti að eiga samskipti við NFC-merki, -spjöld og -lesara (nándarsamskipti)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"slökkva á skjálásnum"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Tengt við <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Ýttu til að skoða skrárnar"</string>
<string name="pin_target" msgid="8036028973110156895">"Festa"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Losa"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Forritsupplýsingar"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Byrjar kynningu…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Uppfæra þessi atriði í "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> og <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Vista"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nei, takk"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ekki núna"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Aldrei"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Uppfæra"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Áfram"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"aðgangsorð"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Breyta skjáskiptingu"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lásskjár"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Skjámynd"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Forritið <xliff:g id="APP_NAME">%1$s</xliff:g> í sprettiglugga."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Skjátextastika <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 8ebcb0e..2ae9f5f 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -142,7 +142,7 @@
<string name="wfcSpnFormat_wifi" msgid="1376356951297043426">"Wi-Fi"</string>
<string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Chiamate Wi-Fi"</string>
<string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
- <string name="wifi_calling_off_summary" msgid="5626710010766902560">"Off"</string>
+ <string name="wifi_calling_off_summary" msgid="5626710010766902560">"OFF"</string>
<string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Chiamata tramite Wi-Fi"</string>
<string name="wfc_mode_cellular_preferred_summary" msgid="4958965609212575619">"Chiamata su rete mobile"</string>
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"L\'app di amministrazione dei profili di lavoro manca o è danneggiata. Di conseguenza, il tuo profilo di lavoro e i relativi dati sono stati eliminati. Contatta l\'amministratore per ricevere assistenza."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Il tuo profilo di lavoro non è più disponibile sul dispositivo"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Troppi tentativi di inserimento della password"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'amministratore ha abbandonato il dispositivo per uso personale"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Il dispositivo è gestito"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Questo dispositivo è gestito dalla tua organizzazione, che potrebbe monitorare il traffico di rete. Tocca per i dettagli."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Il dispositivo verrà resettato"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Consente all\'app di inviare annunci permanenti, che permangono anche al termine dell\'annuncio. Un uso eccessivo potrebbe rendere il dispositivo Android TV lento o instabile causando un uso eccessivo della memoria."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Consente all\'applicazione di inviare broadcast permanenti, che permangono anche al termine del broadcast. Un uso eccessivo potrebbe rendere il telefono lento o instabile causando un uso eccessivo della memoria."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lettura contatti personali"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Consente all\'applicazione di leggere i dati relativi ai tuoi contatti memorizzati sul tablet, inclusa la frequenza con cui hai effettuato chiamate, inviato email o comunicato in altri modi con individui specifici. Questa autorizzazione consente alle applicazioni di salvare i dati dei tuoi contatti e applicazioni dannose potrebbero condividere i dati dei contatti a tua insaputa."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Consente all\'app di leggere i dati relativi ai contatti memorizzati sul dispositivo Android TV, inclusa la frequenza con cui hai chiamato contatti specifici, hai inviato loro email o hai comunicato con loro in altri modi. Questa autorizzazione consente alle app di salvare i dati dei tuoi contatti e app dannose potrebbero condividere i dati dei contatti a tua insaputa."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Consente all\'applicazione di leggere i dati relativi ai tuoi contatti memorizzati sul telefono, inclusa la frequenza con cui hai effettuato chiamate, inviato email o comunicato in altri modi con individui specifici. Questa autorizzazione consente alle applicazioni di salvare i dati dei tuoi contatti e applicazioni dannose potrebbero condividere i dati dei contatti a tua insaputa."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Consente all\'app di leggere i dati relativi ai contatti memorizzati sul tablet. Le app avranno inoltre accesso agli account memorizzati sul tablet su cui sono stati creati contatti. Potrebbero essere inclusi gli account creati da app installate. Questa autorizzazione consente alle app di salvare i dati dei tuoi contatti e app dannose potrebbero condividere i dati dei contatti a tua insaputa."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Consente all\'app di leggere i dati relativi ai contatti memorizzati sul dispositivo Android TV. Le app avranno inoltre accesso agli account memorizzati sul dispositivo Android TV su cui sono stati creati contatti. Potrebbero essere inclusi gli account creati da app installate. Questa autorizzazione consente alle app di salvare i dati dei tuoi contatti e app dannose potrebbero condividere i dati dei contatti a tua insaputa."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Consente all\'app di leggere i dati relativi ai contatti memorizzati sul telefono. Le app avranno inoltre accesso agli account memorizzati sul telefono su cui sono stati creati contatti. Potrebbero essere inclusi gli account creati da app installate. Questa autorizzazione consente alle app di salvare i dati dei tuoi contatti e app dannose potrebbero condividere i dati dei contatti a tua insaputa."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modifica dei contatti personali"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Consente all\'applicazione di modificare i dati relativi ai contatti memorizzati sul tablet, inclusa la frequenza con cui hai effettuato chiamate, inviato email o comunicato in altri modi con contatti specifici. Questa autorizzazione consente alle applicazioni di eliminare i dati dei contatti."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Consente all\'app di modificare i dati relativi ai contatti memorizzati sul dispositivo Android TV, inclusa la frequenza con cui hai chiamato contatti specifici, hai inviato loro email o hai comunicato con loro in altri modi. Questa autorizzazione consente all\'app di eliminare i dati dei contatti."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Consente all\'applicazione di modificare i dati relativi ai contatti memorizzati sul telefono, inclusa la frequenza con cui hai effettuato chiamate, inviato email o comunicato in altri modi con contatti specifici. Questa autorizzazione consente alle applicazioni di eliminare i dati dei contatti."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Consente all\'app di modificare i dati relativi ai contatti memorizzati sul tablet. Questa autorizzazione consente alle app di eliminare i dati dei contatti."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Consente all\'app di modificare i dati relativi ai contatti memorizzati sul dispositivo Android TV. Questa autorizzazione consente alle app di eliminare i dati dei contatti."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Consente all\'app di modificare i dati relativi ai contatti memorizzati sul telefono. Questa autorizzazione consente alle app di eliminare i dati dei contatti."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lettura del registro chiamate"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Questa app può leggere la cronologia chiamate."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"scrittura del registro chiamate"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"accesso a comandi aggiuntivi provider di geolocalizz."</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Consente all\'app di accedere a ulteriori comandi del fornitore di posizione. Ciò potrebbe consentire all\'app di interferire con il funzionamento del GPS o di altre fonti di geolocalizzazione."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"accesso alla posizione esatta solo in primo piano"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Questa app può recuperare la tua posizione esatta solo quando è in primo piano. Questi servizi di geolocalizzazione devono essere attivi e disponibili sul telefono affinché l\'app possa usarli. Potrebbe aumentare il consumo della batteria."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"accesso alla posizione approssimativa (in base alla rete) solo in primo piano"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Questa app può recuperare la tua posizione tramite fonti di rete quali ripetitori di telefonia mobile e reti Wi-Fi, ma soltanto quando l\'app è in primo piano. Questi servizi di geolocalizzazione devono essere attivi e disponibili sul tablet affinché l\'app possa usarli."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Questa app può recuperare la tua posizione tramite fonti di rete quali ripetitori di telefonia mobile e reti Wi-Fi, ma soltanto quando l\'app è in primo piano. Questi servizi di geolocalizzazione devono essere attivi e disponibili sul dispositivo Android TV affinché l\'app possa usarli."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Questa app può recuperare la tua posizione tramite fonti di rete quali ripetitori di telefonia mobile e reti Wi-Fi, ma soltanto quando l\'app è in primo piano. Questi servizi di geolocalizzazione devono essere attivi e disponibili sul telefono affinché l\'app possa usarli."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Questa app può recuperare la tua posizione esatta solo quando è in primo piano. I servizi di geolocalizzazione devono essere attivi e disponibili sul dispositivo affinché l\'app possa usarli. Potrebbe aumentare il consumo della batteria."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"Accesso alla posizione approssimativa solo in primo piano"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Questa app può recuperare la tua posizione approssimativa solo quando è in primo piano. I servizi di geolocalizzazione devono essere attivi e disponibili sull\'auto affinché l\'app possa usarli."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"accesso alla posizione in background"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Se concedi l\'autorizzazione insieme all\'accesso alla posizione precisa o approssimativa, l\'app potrà accedere alla posizione mentre viene eseguita in background."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Questa app può accedere alla posizione in background, oltre ad accedervi in primo piano."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifica impostazioni audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Consente all\'applicazione di modificare le impostazioni audio globali, come il volume e quale altoparlante viene utilizzato per l\'uscita."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"registrazione audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Consente all\'applicazione di visualizzare la configurazione del Bluetooth sul tablet e di stabilire e accettare connessioni con dispositivi accoppiati."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Consente all\'app di visualizzare la configurazione del Bluetooth del dispositivo Android TV, nonché di stabilire e accettare connessioni con dispositivi accoppiati."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Consente all\'applicazione di visualizzare la configurazione del Bluetooth sul telefono e di stabilire e accettare connessioni con dispositivi accoppiati."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controllo Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Consente all\'applicazione di comunicare con tag, schede e lettori NFC (Near Field Communication)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"disattivazione blocco schermo"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Connesso a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tocca per visualizzare i file"</string>
<string name="pin_target" msgid="8036028973110156895">"Blocca"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Sgancia"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informazioni app"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Avvio della demo…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Vuoi aggiornare questi elementi su "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> e <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Salva"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"No, grazie"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Non ora"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Mai"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Aggiorna"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continua"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"password"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Attiva/disattiva schermo diviso"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Schermata di blocco"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"App <xliff:g id="APP_NAME">%1$s</xliff:g> in una finestra popup."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra del titolo di <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 85d0f3b..395c818 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"אפליקציית הניהול של פרופיל העבודה חסרה או פגומה. כתוצאה מכך, פרופיל העבודה שלך נמחק, כולל כל הנתונים הקשורים אליו. לקבלת עזרה, פנה למנהל המערכת."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"פרופיל העבודה שלך אינו זמין עוד במכשיר הזה"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"בוצעו ניסיונות רבים מדי להזנת סיסמה"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"מנהל המערכת ביטל את המכשיר לצורכי שימוש אישי"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"המכשיר מנוהל"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"הארגון שלך מנהל מכשיר זה ועשוי לנטר את התנועה ברשת. הקש לקבלת פרטים."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"תתבצע מחיקה של המכשיר"</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"מאפשרת לאפליקציה לשלוח שידורים \"דביקים\" (sticky), שנותרים לאחר שהשידור מסתיים. בעקבות שימוש מופרז באפשרות זו, שיעור ניצול הזיכרון יהיה גבוה מדי ומכשיר ה-Android TV עלול לפעול בצורה איטית או בלתי יציבה."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"מאפשר לאפליקציה לשלוח שידורים דביקים, אשר נותרים לאחר סיום השידור. אפליקציות זדוניות עלולות להאט את פעילות הטלפון או להפוך אותה לבלתי יציבה על ידי אילוץ המכשיר להשתמש ביותר מדי זיכרון."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"קריאת אנשי הקשר שלך"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"מאפשר לאפליקציה לקרוא נתונים לגבי אנשי הקשר שלך המאוחסנים בטאבלט, כולל את התדירות שבה התקשרת, שלחת אימייל או יצרת קשר בדרכים אחרות עם אנשים ספציפיים. אישור זה מתיר לאפליקציות לשמור את נתוני אנשי הקשר שלך. כמו כן, אפליקציות זדוניות עשויות לשתף נתוני אנשי קשר ללא ידיעתך."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"מאפשרת לאפליקציה לקרוא נתונים על אנשי הקשר השמורים במכשיר ה-Android TV, כולל התדירות שבה התקשרת, שלחת אימייל או יצרת קשר בדרכים אחרות עם אנשי קשר ספציפיים. הרשאה זו מאפשרת לאפליקציות לשמור נתונים של אנשי הקשר שלך, ואפליקציות זדוניות עלולות לשתף נתונים של אנשי קשר ללא ידיעתך."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"מאפשר לאפליקציה לקרוא נתונים לגבי אנשי הקשר שלך המאוחסנים בטלפון, כולל את התדירות שבה התקשרת, שלחת אימייל או יצרת קשר בדרכים אחרות עם אנשים ספציפיים. אישור זה מתיר לאפליקציות לשמור את נתוני אנשי הקשר שלך. כמו כן, אפליקציות זדוניות עשויות לשתף נתוני אנשי קשר ללא ידיעתך."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"מאפשרת לאפליקציה לקרוא נתונים על אנשי הקשר השמורים בטאבלט שלך. לאפליקציות תהיה גם גישה לחשבונות בטאבלט שיצרו אנשי קשר. פעולה זו עשויה לכלול חשבונות שנוצרו על ידי אפליקציות שהתקנת. הרשאה זו מאפשרת לאפליקציות לשמור נתונים של אנשי הקשר שלך, ואפליקציות זדוניות עלולות לשתף נתונים של אנשי קשר ללא ידיעתך."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"מאפשרת לאפליקציה לקרוא נתונים על אנשי הקשר השמורים במכשיר ה-Android TV שלך. לאפליקציות תהיה גם גישה לחשבונות במכשיר ה-Android TV שיצרו אנשי קשר. פעולה זו עשויה לכלול חשבונות שנוצרו על ידי אפליקציות שהתקנת. הרשאה זו מאפשרת לאפליקציות לשמור נתונים של אנשי הקשר שלך, ואפליקציות זדוניות עלולות לשתף נתונים של אנשי קשר ללא ידיעתך."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"מאפשרת לאפליקציה לקרוא נתונים על אנשי הקשר השמורים בטלפון שלך. לאפליקציות תהיה גם גישה לחשבונות בטלפון שיצרו אנשי קשר. פעולה זו עשויה לכלול חשבונות שנוצרו על ידי אפליקציות שהתקנת. הרשאה זו מאפשרת לאפליקציות לשמור נתונים של אנשי הקשר שלך, ואפליקציות זדוניות עלולות לשתף נתונים של אנשי קשר ללא ידיעתך."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"שינוי אנשי הקשר שלך"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"מאפשר לאפליקציה לשנות את הנתונים לגבי אנשי הקשר שלך המאוחסנים בטאבלט, כולל התדירות שבה התקשרת, שלחת אימייל או יצרת קשר בדרכים אחרות עם אנשי קשר ספציפיים. אישור זה מתיר לאפליקציות למחוק נתוני אנשי קשר."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"מאפשרת לאפליקציה לשנות נתונים לגבי אנשי הקשר שלך השמורים במכשיר Android TV, כולל התדירות שבה התקשרת, שלחת אימייל או יצרת קשר בדרכים אחרות עם אנשי קשר ספציפיים. הרשאה זו מאפשרת לאפליקציות למחוק נתונים של אנשי קשר."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"מאפשר לאפליקציה לשנות את הנתונים לגבי אנשי הקשר שלך המאוחסנים בטלפון, כולל התדירות שבה התקשרת, שלחת אימייל או יצרת קשר בדרכים אחרות עם אנשי קשר ספציפיים. אישור זה מתיר לאפליקציות למחוק נתוני אנשי קשר."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"מאפשרת לאפליקציה לשנות את הנתונים לגבי אנשי הקשר המאוחסנים בטאבלט שלך. הרשאה זו מאפשרת לאפליקציות למחוק נתונים של אנשי קשר."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"מאפשרת לאפליקציה לשנות את הנתונים לגבי אנשי הקשר המאוחסנים במכשיר ה-Android TV שלך. הרשאה זו מאפשרת לאפליקציות למחוק נתונים של אנשי קשר."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"מאפשרת לאפליקציה לשנות את הנתונים לגבי אנשי הקשר המאוחסנים בטלפון שלך. הרשאה זו מאפשרת לאפליקציות למחוק נתונים של אנשי קשר."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"קריאת יומן שיחות"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"אפליקציה זו יכולה לקרוא את היסטוריית השיחות שלך."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"כתיבת יומן שיחות"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"גישה לפקודות ספק מיקום נוספות"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"מאפשרת לאפליקציה לגשת לפקודות נוספות של ספק המיקום. הרשאה זו עשויה לאפשר לאפליקציה לשבש את פעולת ה-GPS או מקורות מיקום אחרים."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"קבלת גישה למיקום מדויק בחזית בלבד"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"אפליקציה זו יכולה לזהות את המיקום המדויק שלך רק כאשר היא פועלת בחזית. כדי שהאפליקציה תוכל להשתמש בשירותי המיקום, עליהם להיות מופעלים וזמינים בטלפון. ייתכן שפעולה זו תגביר את צריכת הסוללה."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"קבלת גישה למיקום המשוער (מבוסס-רשת) רק במצב פעיל"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"אפליקציה זו יכולה לזהות את המיקום שלך על סמך מקורות מיקום ברשת, כגון אנטנות סלולריות ורשתות Wi-Fi, אבל רק כשהאפליקציה במצב פעיל. שירותי מיקום אלה חייבים להיות מופעלים וזמינים בטאבלט כדי שהאפליקציה תוכל להשתמש בהם."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"אפליקציה זו יכולה לזהות את המיקום שלך על סמך מקורות מיקום ברשת, כגון אנטנות סלולריות ורשתות Wi-Fi, אבל רק כשהאפליקציה במצב פעיל. שירותי מיקום אלה חייבים להיות מופעלים וזמינים במכשיר ה-Android TV כדי שהאפליקציה תוכל להשתמש בהם."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"אפליקציה זו יכולה לזהות את המיקום שלך על סמך מקורות מיקום ברשת, כגון אנטנות סלולריות ורשתות Wi-Fi, אבל רק כשהאפליקציה במצב פעיל. שירותי מיקום אלה חייבים להיות מופעלים וזמינים בטלפון כדי שהאפליקציה תוכל להשתמש בהם."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"אפליקציה זו יכולה לזהות את המיקום המדויק שלך רק כאשר היא פועלת בחזית. כדי שהאפליקציה תוכל להשתמש בשירותי המיקום, עליהם להיות מופעלים וזמינים במכשיר. ייתכן שפעולה זו תגביר את צריכת הסוללה."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"קבלת גישה למיקום משוער תתבצע בחזית בלבד"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"אפליקציה זו יכולה לזהות את המיקום המשוער שלך רק כאשר היא פועלת בחזית. שירותי מיקום חייבים להיות מופעלים וזמינים במכשיר שלך כדי שהאפליקציה תוכל להשתמש בהם."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"גישה למיקום ברקע"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"אם מתקבל אישור, בנוסף לגישה למיקום משוער או מדויק, תהיה לאפליקציה גישה למיקום גם כשהיא פועלת ברקע."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"האפליקציה הזו יכולה לגשת למיקום כשהיא רצה ברקע, בנוסף לקבלת גישה למיקום בחזית."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"שנה את הגדרות האודיו שלך"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"מאפשר לאפליקציה לשנות הגדרות אודיו גלובליות כמו עוצמת קול ובחירת הרמקול המשמש לפלט."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"הקלט אודיו"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"מאפשר לאפליקציה להציג את תצורת ה-Bluetooth בטאבלט, וכן ליצור ולקבל חיבורים עם מכשירים מותאמים."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"מאפשרת לאפליקציה להציג את הגדרת ה-Bluetooth במכשיר ה-Android TV, וליצור ולקבל חיבורים עם מכשירים מותאמים."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"מאפשר לאפליקציה להציג את תצורת ה-Bluetooth בטלפון, וכן ליצור ולקבל חיבורים עם מכשירים מותאמים."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"שלוט ב-Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"מאפשר לאפליקציה נהל תקשורת עם תגים, כרטיסים וקוראים מסוג \'תקשורת מטווח קצר\'."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ביטול נעילת המסך שלך"</string>
@@ -543,7 +544,7 @@
<string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"פעולת טביעת האצבע בוטלה בידי המשתמש."</string>
<string name="fingerprint_error_lockout" msgid="7853461265604738671">"יותר מדי ניסיונות. נסה שוב מאוחר יותר."</string>
<string name="fingerprint_error_lockout_permanent" msgid="3895478283943513746">"יותר מדי ניסיונות. חיישן טביעות האצבע הושבת."</string>
- <string name="fingerprint_error_unable_to_process" msgid="1148553603490048742">"נסה שוב."</string>
+ <string name="fingerprint_error_unable_to_process" msgid="1148553603490048742">"כדאי לנסות שוב."</string>
<string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"לא נרשמו טביעות אצבע."</string>
<string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"במכשיר זה אין חיישן טביעות אצבע."</string>
<string name="fingerprint_name_template" msgid="8941662088160289778">"אצבע <xliff:g id="FINGERID">%d</xliff:g>"</string>
@@ -814,8 +815,8 @@
<string name="lockscreen_emergency_call" msgid="7500692654885445299">"חירום"</string>
<string name="lockscreen_return_to_call" msgid="3156883574692006382">"חזרה לשיחה"</string>
<string name="lockscreen_pattern_correct" msgid="8050630103651508582">"נכון!"</string>
- <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"נסה שוב"</string>
- <string name="lockscreen_password_wrong" msgid="8605355913868947490">"נסה שוב"</string>
+ <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"כדאי לנסות שוב"</string>
+ <string name="lockscreen_password_wrong" msgid="8605355913868947490">"כדאי לנסות שוב"</string>
<string name="lockscreen_storage_locked" msgid="634993789186443380">"בטל את הנעילה לכל התכונות והנתונים"</string>
<string name="faceunlock_multiple_failures" msgid="681991538434031708">"חרגת ממספר הניסיונות המרבי של זיהוי פנים"</string>
<string name="lockscreen_missing_sim_message_short" msgid="1248431165144893792">"אין כרטיס SIM"</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"מחובר אל <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"הקש כדי להציג קבצים"</string>
<string name="pin_target" msgid="8036028973110156895">"הצמד"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"בטל הצמדה"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"פרטי אפליקציה"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"מתחיל בהדגמה…"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"האם לעדכן פריטים אלה ב-"<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ו-<xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"שמירה"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"לא, תודה"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"לא עכשיו"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"אף פעם"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"עדכון"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"המשך"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"סיסמה"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"החלפת מצב של מסך מפוצל"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"מסך הנעילה"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"צילום מסך"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> בחלון קופץ."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"סרגל כיתוב של <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index e21035f..da7616c 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"仕事用プロファイルの管理アプリがないか、破損しています。そのため仕事用プロファイルと関連データが削除されました。管理者にサポートをご依頼ください。"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"お使いの仕事用プロファイルはこのデバイスで使用できなくなりました"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"パスワード入力回数が上限を超えました"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"管理者により、デバイスの個人使用が許可されました"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"管理対象のデバイス"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"このデバイスは組織によって管理され、ネットワーク トラフィックが監視される場合があります。詳しくはタップしてください。"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"デバイスのデータが消去されます"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"配信が終了してもメモリに残る sticky ブロードキャストの配信をアプリに許可します。この許可を使用しすぎると、メモリの使用量が増えて Android TV デバイスの動作が遅くなったり不安定になったりすることがあります。"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"配信が終了してもメモリに残るstickyブロードキャストの配信をアプリに許可します。この許可を使用し過ぎると、メモリの使用量が増えてモバイル デバイスの動作が遅くなったり不安定になったりする恐れがあります。"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"連絡先の読み取り"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"タブレットに保存されている連絡先に関するデータの読み取りをアプリに許可します。このデータには、電話、メール、または他の手段で特定の相手と連絡をとった頻度も含まれます。これにより、アプリに連絡先データの保存を許可することになり、悪意のあるアプリによって知らないうちに連絡先データが共有される恐れがあります。"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Android TV デバイスに保存されている連絡先に関するデータの読み取りをアプリに許可します。このデータには、電話、メール、または他の手段で特定の相手と連絡をとった頻度も含まれます。これにより、連絡先データの保存をアプリに許可することになり、悪意のあるアプリによって知らないうちに連絡先データが共有される恐れがあります。"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"モバイル デバイスに保存されている連絡先に関するデータの読み取りをアプリに許可します。このデータには、電話、メール、または他の手段で特定の相手と連絡をとった頻度も含まれます。これにより、アプリに連絡先データの保存を許可することになり、悪意のあるアプリによって知らないうちに連絡先データが共有される恐れがあります。"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"タブレットに保存されている連絡先に関するデータの読み取りをアプリに許可します。また、この読み取りを許可したアプリは、連絡先を作成したタブレット上のアカウントにもアクセスできます。これには、インストールしたアプリによって作成されたアカウントも含まれます。これにより、連絡先データの保存をアプリに許可することになり、悪意のあるアプリによって知らないうちに連絡先データが共有される恐れがあります。"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV デバイスに保存されている連絡先に関するデータの読み取りをアプリに許可します。また、この読み取りを許可したアプリは、連絡先を作成した Android TV デバイス上のアカウントにもアクセスできます。これには、インストールしたアプリによって作成されたアカウントも含まれます。これにより、連絡先データの保存をアプリに許可することになり、悪意のあるアプリによって知らないうちに連絡先データが共有される恐れがあります。"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"スマートフォンに保存されている連絡先に関するデータの読み取りをアプリに許可します。また、この読み取りを許可したアプリは、連絡先を作成したスマートフォン上のアカウントにもアクセスできます。これには、インストールしたアプリによって作成されたアカウントも含まれます。これにより、連絡先データの保存をアプリに許可することになり、悪意のあるアプリによって知らないうちに連絡先データが共有される恐れがあります。"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"連絡先の変更"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"タブレットに保存されている連絡先に関するデータの変更をアプリに許可します。このデータには、電話、メール、または他の手段で特定の相手と連絡をとった頻度も含まれます。これにより、アプリが連絡先データを削除できるようになります。"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Android TV デバイスに保存されている連絡先に関するデータの変更をアプリに許可します。このデータには、電話、メール、または他の手段で特定の相手と連絡をとった頻度も含まれます。これにより、連絡先データの削除をアプリに許可することになります。"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"モバイルデバイスに保存されている連絡先に関するデータの変更をアプリに許可します。このデータには、電話、メール、または他の手段で特定の相手と連絡をとった頻度も含まれます。これにより、アプリが連絡先データを削除できるようになります。"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"タブレットに保存されている連絡先に関するデータの変更をアプリに許可します。これにより、連絡先データの削除をアプリに許可することになります。"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV デバイスに保存されている連絡先に関するデータの変更をアプリに許可します。これにより、連絡先データの削除をアプリに許可することになります。"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"スマートフォンに保存されている連絡先に関するデータの変更をアプリに許可します。これにより、連絡先データの削除をアプリに許可することになります。"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"通話履歴の読み取り"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"このアプリは通話履歴を読み取ることができます。"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"通話履歴の書き込み"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"位置情報提供者の追加コマンドアクセス"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"位置情報提供元の追加のコマンドにアクセスすることをアプリに許可します。許可すると、アプリがGPSなどの位置情報源の動作を妨害する恐れがあります。"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"フォアグラウンドでのみ正確な位置情報にアクセス"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"このアプリは、フォアグラウンド状態でのみユーザーの正確な位置情報を取得できます。この位置情報サービスは ON の状態にして、スマートフォンでアプリがサービスを利用できるようにする必要があります。これにより、電池の消費量が増える可能性があります。"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"フォアグラウンドでのみ(ネットワークに基づく)おおよその位置情報にアクセス"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"このアプリは、フォアグラウンドでのみ、ネットワーク位置情報源(携帯基地局や Wi-Fi ネットワークなど)に基づいて、ユーザーの位置情報を取得できます。これらの位置情報サービスは ON の状態にして、タブレットでアプリがサービスを利用できるようにする必要があります。"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"このアプリは、フォアグラウンドでのみ、ネットワーク位置情報源(携帯基地局や Wi-Fi ネットワークなど)に基づいて、ユーザーの位置情報を取得できます。これらの位置情報サービスは ON の状態にして、Android TV デバイスでアプリがサービスを利用できるようにする必要があります。"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"このアプリは、フォアグラウンドでのみ、ネットワーク位置情報源(携帯基地局や Wi-Fi ネットワークなど)に基づいて、ユーザーの位置情報を取得できます。これらの位置情報サービスは ON の状態にして、スマートフォンでアプリがサービスを利用できるようにする必要があります。"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"このアプリは、フォアグラウンドでのみユーザーの正確な位置情報を取得できます。位置情報サービスを ON にして、デバイスでアプリがサービスを利用できるようにする必要があります。これにより、電池の消費量が増える可能性があります。"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"フォアグラウンドでのみおおよその位置情報にアクセス"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"このアプリは、フォアグラウンドでのみユーザーのおおよその位置情報を取得できます。位置情報サービスを ON にして、デバイスでアプリがサービスを利用できるようにする必要があります。"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"バックグラウンドでの位置情報へのアクセス"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"これが、おおよその位置情報または正確な位置情報へのアクセスの追加権限の場合、アプリはバックグラウンドでの実行中も位置情報にアクセスできます。"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"このアプリは、フォアグラウンドで位置情報にアクセスできるだけでなく、バックグラウンドでの実行中も位置情報にアクセスできます。"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"音声設定の変更"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"音声全般の設定(音量、出力に使用するスピーカーなど)の変更をアプリに許可します。"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"録音"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"タブレットのBluetooth設定を表示すること、ペアのデバイスに接続すること/ペアのデバイスからの接続を受け入れることをアプリに許可します。"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Android TV デバイスの Bluetooth 設定の表示と、ペア設定されたデバイスへの接続の確立、またはペア設定されたデバイスからの接続の受け入れをアプリに許可します。"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"モバイルデバイスのBluetooth設定を表示すること、ペアのデバイスに接続すること/ペアのデバイスからの接続を受け入れることをアプリに許可します。"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"NFCの管理"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"NFCタグ、カード、リーダーとの通信をアプリに許可します。"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"画面ロックの無効化"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> に接続しました"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"タップしてファイルを表示"</string>
<string name="pin_target" msgid="8036028973110156895">"固定"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"固定を解除"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"アプリ情報"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"デモを開始しています…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"<xliff:g id="TYPE_0">%1$s</xliff:g>、<xliff:g id="TYPE_1">%2$s</xliff:g>、<xliff:g id="TYPE_2">%3$s</xliff:g>を "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" で更新しますか?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"はい"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"いいえ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"後で"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"なし"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"更新"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"続行"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"パスワード"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"分割画面の切り替え"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"ロック画面"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"スクリーンショット"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> アプリがポップアップ ウィンドウで開きます。"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> のキャプション バーです。"</string>
</resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 941dbbd..78890df 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"სამსახურის პროფილის ადმინისტრატორის აპი მიუწვდომელია ან დაზიანებულია. ამის გამო, თქვენი სამსახურის პროფილი და დაკავშირებული მონაცემები წაიშალა. დახმარებისთვის დაუკავშირდით თქვენს ადმინისტრატორს."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"თქვენი სამსახურის პროფილი აღარ არის ხელმისაწვდომი ამ მოწყობილობაზე"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"დაფიქსირდა პაროლის შეყვანის ზედმეტად ბევრი მცდელობა"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"ადმინისტრატორმა გაათავისუფლა მოწყობილობა პირადი გამოყენებისთვის"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"მოწყობილობა მართულია"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"ამ მოწყობილობას თქვენი ორგანიზაცია მართავს და მას ქსელის ტრაფიკის მონიტორინგი შეუძლია. შეეხეთ დამატებითი დეტალებისთვის."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"თქვენი მოწყობილობა წაიშლება"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ნებას რთავს აპს, გაგზავნოს ფიქსირებული მაუწყებლობა, რომელიც მაუწყებლობის დასრულების შემდეგაც რჩება. ჭარბმა გამოყენებამ, შესაძლოა, თქვენი Android TV მოწყობილობა ნელი ან არასტაბილური გახადოს, რადგან მეტისმეტად დიდი მეხსიერების გამოყენებას აიძულებს მას."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"აპს შეეძლება არასაჩქარო შეტყობინებების გაგზავნა, რომელიც რჩებიან გაგზავნის დასრულების შემდეგაც. მავნე აპლიკაციებს შეუძლიათ თქვენი ტელეფონის მუშაობის შენელება ან შეფერხება ზედმეტად დიდი მოცულობის მეხსიერების გამოყენების შედეგად."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"თქვენი კონტაქტების წაკითხვა"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"აპს შეეძლება, წაიკითხოს თქვენ ტაბლეტზე შენახული კონტაქტების მონაცემები, მათ შორის ინფორმაცია კონკრეტულ ადამიანებთან თქვენი დარეკვის, ელფოსტის გაგზავნის ან კომუნიკაციის სიხშირის შესახებ. ეს ნებართვა უფლებას აძლევს აპებს, შეინახონ თქვენი კონტაქტების მონაცემები და მავნე აპებმა შეიძლება გააზიარონ საკონტაქტო მონაცემები თქვენგან დამოუკიდებლად. "</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"ნებას რთავს აპს, წაიკითხოს Android TV მოწყობილობაში შენახული კონტაქტების მონაცემები, მათ შორის, მონაცემები იმის შესახებ, თუ რა სიხშირით ურეკავდით, ელფოსტას უგზავნიდით, თუ სხვა გზით უკავშირდებოდით კონკრეტულ ადამიანებს. ეს ნებართვა, აპებს საშუალებას აძლევს, შეინახონ თქვენი კონტაქტების მონაცემები, ამის გამო, მავნე აპებს შეეძლებათ გააზიარონ კონტაქტების მონაცემები, ისე რომ თქვენ ამის შესახებ არ იცოდეთ."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"აპს შეეძლება, წაიკითხოს თქვენ ტელეფონზე შენახული კონტაქტების მონაცემები, მათ შორის ინფორმაცია კონკრეტულ ადამიანებთან თქვენი დარეკვის, ელფოსტის გაგზავნის ან კომუნიკაციის სიხშირის შესახებ. ეს ნებართვა უფლებას აძლევს აპებს, შეინახონ თქვენი კონტაქტების მონაცემები და მავნე აპებმა შეიძლება გააზიარონ საკონტაქტო მონაცემები თქვენგან დამოუკიდებლად. "</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"აპს საშუალებას აძლევს, წაიკითხოს თქვენს ტაბლეტზე შენახული კონტაქტების მონაცემები. აპს, ასევე, ექნება წვდომა თქვენს ტაბლეტზე არსებულ ანგარიშებზე, რომლებსაც კონტაქტები აქვთ შექმნილი. ეს შეიძლება მოიცავდეს თქვენ მიერ ინსტალირებული აპების მიერ შექმნილ ანგარიშებს. ეს ნებართვა უფლებას აძლევს აპებს, შეინახონ თქვენი კონტაქტების მონაცემები და მავნე აპებმა შეიძლება თქვენთვის შეუტყობინებლად გააზიარონ საკონტაქტო მონაცემები."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"აპს საშუალებას აძლევს, წაიკითხოს თქვენს Android TV მოწყობილობაზე შენახული კონტაქტების მონაცემები. აპს, ასევე, ექნება წვდომა თქვენს Android TV მოწყობილობებზე არსებულ ანგარიშებზე, რომლებსაც კონტაქტები აქვთ შექმნილი. ეს შეიძლება მოიცავდეს თქვენ მიერ ინსტალირებული აპების მიერ შექმნილ ანგარიშებს. ეს ნებართვა უფლებას აძლევს აპებს, შეინახონ თქვენი კონტაქტების მონაცემები და მავნე აპებმა შეიძლება თქვენთვის შეუტყობინებლად გააზიარონ საკონტაქტო მონაცემები."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"აპს საშუალებას აძლევს, წაიკითხოს თქვენს ტელეფონზე შენახული კონტაქტების მონაცემები. აპს, ასევე, ექნება წვდომა თქვენს ტელეფონზე არსებულ ანგარიშებზე, რომლებსაც კონტაქტები აქვთ შექმნილი. ეს შეიძლება მოიცავდეს თქვენ მიერ ინსტალირებული აპების მიერ შექმნილ ანგარიშებს. ეს ნებართვა უფლებას აძლევს აპებს, შეინახონ თქვენი კონტაქტების მონაცემები და მავნე აპებმა შეიძლება თქვენთვის შეუტყობინებლად გააზიარონ საკონტაქტო მონაცემები."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"თქვენი კონტაქტების შეცვლა"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"აპს შეეძლება, შეცვალოს თქვენ ტაბლეტზე შენახული კონტაქტების მონაცემები, მათ შორის ინფორმაცია კონკრეტულ ინდივიდუალებთან თქვენი დარეკვის, ელფოსტის გაგზავნის ან კომუნიკაციის სიხშირის შესახებ. ეს ნებართვა უფლებას აძლევს აპებს, წაშალოს საკონტაქტო მონაცემები. "</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"ნებას რთავს აპს, შეცვალოს Android TV მოწყობილობაში შენახული კონტაქტების მონაცემები, მათ შორის, მონაცემები იმის შესახებ, თუ რა სიხშირით ურეკავდით, ელფოსტას უგზავნიდით, თუ სხვა გზით უკავშირდებოდით კონკრეტულ კონტაქტებს. ეს ნებართვა აპებს კონტაქტების მონაცემების წაშლის საშუალებას აძლევს."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"აპს შეეძლება, შეცვალოს თქვენ ტელეფონზე შენახული კონტაქტების მონაცემები, მათ შორის ინფორმაცია კონკრეტულ ინდივიდუალებთან თქვენი დარეკვის, ელფოსტის გაგზავნის ან კომუნიკაციის სიხშირის შესახებ. ეს ნებართვა უფლებას აძლევს აპებს, წაშალოს საკონტაქტო მონაცემები. "</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"აპს საშუალებას აძლევს, შეცვალოს თქვენს ტაბლეტზე შენახული კონტაქტების მონაცემები. ეს ნებართვა საშუალებას აძლევს აპებს, წაშალონ კონტაქტის მონაცემები."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"აპს საშუალებას აძლევს, შეცვალოს თქვენს Android TV მოწყობილობაზე შენახული კონტაქტების მონაცემები. ეს ნებართვა საშუალებას აძლევს აპებს, წაშალონ კონტაქტის მონაცემები."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"აპს საშუალებას აძლევს, შეცვალოს თქვენს ტელეფონზე შენახული კონტაქტების მონაცემები. ეს ნებართვა საშუალებას აძლევს აპებს, წაშალონ კონტაქტის მონაცემები."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ზარების ჟურნალის წაკითხვა"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ამ აპს შეუძლია თქვენი საუბრის ისტორიის წაკითხვა."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"ზარების ჟურნალში ჩაწერა"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"მდებარეობის პროვაიდერის დამატებით ბრძანებებზე წვდომა"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"აპს შეეძლება წვდომა ჰქონდეს მდებარეობის სერვისის დამატებით ბრძანებებზე. შესაძლოა აპმა ეს გამოიყენოს GPS-ისა და მდებარეობის სხვა წყაროების მუშაობის პროცესში ჩარევისთვის."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ზუსტ მდებარეობაზე წვდომა მხოლოდ წინა პლანზე"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"ამ აპს შეუძლია თქვენი ზუსტი მდებარეობის შესახებ ინფორმაციის მიღება მხოლოდ მაშინ, როცა გაშვებულია წინა პლანზე. თქვენს ტელეფონზე ჩართული და ხელმისაწვდომი უნდა იყოს მდებარეობის სერვისები, აპმა მათი გამოყენება რომ შეძლოს. ამან შეიძლება გაზარდოს ბატარეის მოხმარება."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"მიახლოებით მდებარეობაზე (ქსელის კოორდინატების მიხედვით) წვდომა მხოლოდ წინა პლანზე"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ამ აპს შეუძლია თქვენი მდებარეობის შესახებ ინფორმაციის მიღება ისეთი წყაროებიდან, როგორიცაა მობილური კავშირგაბმულობის ანძები და Wi-Fi ქსელები (თუმცა მხოლოდ მაშინ, როცა აპი გაშვებულია წინა პლანზე). მდებარეობის აღნიშნული სერვისები თქვენს ტაბლეტზე ჩართული და ხელმისაწვდომი უნდა იყოს, აპმა მათი გამოყენება რომ შეძლოს."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"ამ აპს შეუძლია თქვენი მდებარეობის შესახებ ინფორმაციის მიღება ისეთი წყაროებიდან, როგორიცაა მობილური კავშირგაბმულობის ანძები და Wi-Fi ქსელები (თუმცა მხოლოდ მაშინ, როცა აპი გაშვებულია წინა პლანზე). მდებარეობის აღნიშნული სერვისები თქვენს Android TV მოწყობილობაზე ჩართული და ხელმისაწვდომი უნდა იყოს, აპმა მათი გამოყენება რომ შეძლოს."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ამ აპს შეუძლია თქვენი მდებარეობის შესახებ ინფორმაციის მიღება ისეთი წყაროებიდან, როგორიცაა მობილური კავშირგაბმულობის ანძები და Wi-Fi ქსელები (თუმცა მხოლოდ მაშინ, როცა აპი გაშვებულია წინა პლანზე). მდებარეობის აღნიშნული სერვისები თქვენს ტელეფონზე ჩართული და ხელმისაწვდომი უნდა იყოს, აპმა მათი გამოყენება რომ შეძლოს."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"ამ აპს შეუძლია თქვენი ზუსტი მდებარეობის შესახებ ინფორმაციის მიღება მხოლოდ მაშინ, როცა გაშვებულია წინა პლანზე. თქვენს მოწყობილობაზე ჩართული და ხელმისაწვდომი უნდა იყოს მდებარეობის სერვისები, აპმა მათი გამოყენება რომ შეძლოს. ამან შეიძლება გაზარდოს ბატარეის მოხმარება."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"მიახლოებით მდებარეობაზე წვდომა მხოლოდ წინა პლანზე"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"ამ აპს შეუძლია თქვენი მიახლოებითი მდებარეობის შესახებ ინფორმაციის მიღება მხოლოდ მაშინ, როცა გაშვებულია წინა პლანზე. თქვენს მოწყობილობაზე ჩართული და ხელმისაწვდომი უნდა იყოს მდებარეობის სერვისები, აპმა მათი გამოყენება რომ შეძლოს."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"მდებარეობაზე წვდომა ფონურ რეჟიმში"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ამ ნებართვის მიახლოებით ან ზუსტ მდებარეობაზე წვდომის ნებართვასთან ერთად მინიჭების შემთხვევაში, აპს შეეძლება მდებარეობაზე წვდომა ფონურ რეჟიმში."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"წინა პლანზე ყოფნისას მდებარეობაზე წვდომის გარდა, ამ აპს შეუძლია, ჰქონდეს წვდომა მდებარეობაზე ფონურ რეჟიმში მუშაობისას."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"თქვენი აუდიო პარამეტრების შეცვლა"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"აპს შეეძლება აუდიოს გლობალური პარამეტრების შეცვლა. მაგ.: ხმის სიმაღლე და რომელი დინამიკი გამოიყენება სიგნალის გამოსტანად."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"აუდიოს ჩაწერა"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"აპს შეეძლება, ნახოს Bluetooth-ის კონფიგურაცია ტაბლეტზე, შექმნას და მიიღოს კავშირები დაწყვილებულ მოწყობილობებთან."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ნებას რთავს აპს, თქვენს Android TV მოწყობილობაზე ნახოს Bluetooth-ის კონფიგურაცია, ასევე, დაამყაროს და მიიღოს კავშირები დაწყვილებულ მოწყობილობებთან."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"აპს შეეძლება, ნახოს Bluetooth-ის კონფიგურაცია ტელეფონზე და შექმნას და მიიღოს კავშირები დაწყვილებულ მოწყობილობებთან."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ახლო მოქმედების რადიოკავშირი (NFC) მართვა"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"აპს შეეძლება ახლო მოქმედების რადიოკავშირის (NFC) მეშვეობით ტეგების, ბარათებისა და წამკითხველების შემცველი მონაცემების მიმოცვლა."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"თქვენი ეკრანის ბლოკის გათიშვა"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"დაკავშირებულია <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>-თან"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"შეეხეთ ფაილების სანახავად"</string>
<string name="pin_target" msgid="8036028973110156895">"ჩამაგრება"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"ჩამაგრების მოხსნა"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"აპის შესახებ"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"მიმდინარეობს დემონსტრაციის დაწყება…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"გსურთ, "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"-ში განაახლოთ <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> და <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"შენახვა"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"არა, გმადლობთ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ახლა არა"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"არასოდეს"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"განახლება"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"გაგრძელება"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"პაროლი"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"გაყოფილი ეკრანის გადართვა"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"ჩაკეტილი ეკრანი"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ეკრანის ანაბეჭდი"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> აპი ამომხტარ ფანჯარაში."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის სუბტიტრების ზოლი."</string>
</resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index b142e58..119b2ae 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Жұмыс профилінің әкімші қолданбасы жоқ немесе бүлінген. Нәтижесінде жұмыс профиліңіз және қатысты деректер жойылды. Көмек алу үшін әкімшіге хабарласыңыз."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Жұмыс профиліңіз осы құрылғыда енді қолжетімді емес"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Құпия сөз көп рет қате енгізілді"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Әкімші құрылғыны жеке пайдалануға ұсынды."</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Құрылғы басқарылады"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Ұйымыңыз осы құрылғыны басқарады және желі трафигін бақылауы мүмкін. Мәліметтер алу үшін түртіңіз."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Құрылғыңыздағы деректер өшіріледі"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Қолданба трансляция біткеннен кейін сақталатын бекітілген трансляцияларды жібере алатын болады. Тым жиі пайдалансаңыз, жад толып, Android TV құрылғысы баяу немесе тұрақсыз жұмыс істеуі мүмкін."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Қолданбаға хабар тарату аяқталғанда сақталатын жабысқақ хабар тарату мүмкіндігін береді. Тым көп қолдану телефон жұмысын баяулатады немесе жадты көп қолдану арқылы жұмысын тұрақсыздандырады."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"контактілерді оқу"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Қолданбаға планшетте сақталған байланыстар, белгілі тұлғаларға шалынған қоңырау, хаттар немесе басқа байланыс түрінің жиіліктерін қоса, туралы ақпаратты оқу мүмкіндігін береді. Бұл рұқсат қолданбаға байланыстар туралы деректерді сақтау мүмкіндігін береді және залалды қолданбалар байланыстар туралы деректерді сіздің келісіміңізсіз бөлісуі ықтимал."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Қолданба Android TV құрылғыңызда сақталған контактілер туралы деректі оқи алатын болады. Бұл деректерге белгілі бір адамдарға қаншалықты жиі қоңырау шалатыныңыз, электрондық хабар жазатыныңыз немесе басқа жолмен хабарласатыныңыз туралы ақпарат кіреді. Бұл рұқсат арқылы қолданбалар контакт туралы деректерді сақтай алады. Ал зиянды қолданбалар контакт туралы деректерді сіздің рұқсатыңызсыз бөлісуі мүмкін."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Қолданбаға телефонда сақталған байланыстар, белгілі тұлғаларға шалынған қоңырау, хаттар немесе басқа байланыс түрінің жиіліктерін қоса, туралы ақпаратты оқу мүмкіндігін береді. Бұл рұқсат қолданбаға байланыстар туралы деректерді сақтау мүмкіндігін береді және залалды қолданбалар байланыстар туралы деректерді сіздің келісіміңізсіз бөлісуі ықтимал."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Қолданбаға планшетте сақталған контактілеріңіз туралы деректерді оқуға рұқсат етеді. Қолданбалар контактілер жасалған планшеттегі есептік жазбаларды пайдалана алады. Бұған сіз орнатқан қолданбалар арқылы жасалған есептік жазбалар кіруі мүмкін. Бұл рұқсат қолданбаларға контакт деректерін сақтау мүмкіндігін береді және зиянды қолданбалар контакт деректерін сіздің келісіміңізсіз бөлісуі ықтимал."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Қолданбаға Android TV құрылғысында сақталған контактілеріңіз туралы деректерді оқуға рұқсат етеді. Қолданба контактілер жасалған Android TV құрылғысындағы есептік жазбаларды пайдалана алады. Бұған сіз орнатқан қолданбалар арқылы жасалған есептік жазбалар кіруі мүмкін. Бұл рұқсат қолданбаларға контакт деректерін сақтау мүмкіндігін береді және зиянды қолданбалар контакт деректерін сіздің келісіміңізсіз бөлісуі ықтимал."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Қолданбаға телефонда сақталған контактілеріңіз туралы деректерді оқуға рұқсат етеді. Қолданбалар контактілер жасалған телефондағы есептік жазбаларды пайдалана алады. Бұған сіз орнатқан қолданбалар арқылы жасалған есептік жазбалар кіруі мүмкін. Бұл рұқсат қолданбаларға контакт деректерін сақтау мүмкіндігін береді және зиянды қолданбалар контакт деректерін сіздің келісіміңізсіз бөлісуі ықтимал."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"контактілерді өзгерту"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Қолданбаға планшетте сақталған байланыстар, белгілі тұлғаларға шалынған қоңырау, хаттар немесе басқа байланыс түрінің жиіліктерін қоса, туралы ақпаратты өзгерту мүмкіндігін береді. Бұл рұқсат қолданбаға байланыстар туралы деректерді өшіру мүмкіндігін береді."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Қолданба Android TV құрылғыңызда сақталған контактілер туралы деректі өзгерте алатын болады. Бұл деректерге белгілі бір контактіге қаншалықты жиі қоңырау шалатыныңыз, электрондық хабар жазатыныңыз немесе басқа жолмен хабарласатыныңыз туралы ақпарат кіреді. Бұл рұқсаттың көмегімен қолданбалар контакт туралы деректерді жоя алады."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Қолданбаға телефонда сақталған байланыстар, белгілі тұлғаларға шалынған қоңырау, хаттар немесе басқа байланыс түрінің жиіліктерін қоса, туралы ақпаратты өзгерту мүмкіндігін береді. Бұл рұқсат қолданбаға байланыстар туралы деректерді өшіру мүмкіндігін береді."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Қолданбаға планшетте сақталған контактілеріңіз туралы деректерді өзгертуге рұқсат етеді. Бұл рұқсат қолданбаларға контактілер туралы деректерді жоюға рұқсат береді."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Қолданбаға Android TV құрылғысында сақталған контактілеріңіз туралы деректерді өзгертуге рұқсат етеді. Бұл рұқсат қолданбаларға контактілер туралы деректерді жоюға рұқсат береді."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Қолданбаға телефонда сақталған контактілеріңіз туралы деректерді өзгертуге рұқсат етеді. Бұл рұқсат қолданбаларға контактілер туралы деректерді жоюға рұқсат береді."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"қоңыраулар тіркеуін оқу"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Бұл қолданба қоңыраулар тарихын оқи алады."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"қоңырау тіркеуді жазу"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"қосымша аймақ жабдықтаушы пәрмендеріне қол жетімділік"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Қолданбаға орын жеткізушісінің қосымша пәрмендеріне қатынасуға рұқсат береді. Бұл қолданбаға GPS немесе басқа орын көздерінің жұмысына кедергі келтіруге рұқсат беруі мүмкін."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"нақты орналасқан жер туралы ақпаратқа тек ашық экранда кіру"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Бұл қолданба нақты орналасқан жеріңіз туралы ақпаратты экранда ашық тұрғанда ғана анықтай алады. Қолданба бұл орынды анықтау қызметтерін пайдалана алуы үшін, олар қосулы әрі телефонда қолжетімді болуы керек. Батарея көбірек тұтынылуы мүмкін."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"болжалды геодерекке (желі негізінде) тек экрандық режимде кіру"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Бұл қолданба орналасқан жеріңізді ұялы байланыс мұнаралары мен Wi-Fi желілері сияқты желі көздерінің негізінде анықтайды. Бірақ бұл үшін қолданба экрандық режимде жұмыс істеп тұруы керек. Қолданба бұл орынды анықтау қызметтерін пайдалана алуы үшін, олар қосулы әрі планшетте қолжетімді болуы керек."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Бұл қолданба орналасқан жеріңізді ұялы байланыс мұнаралары мен Wi-Fi желілері сияқты желі көздерінің негізінде анықтайды. Бірақ бұл үшін қолданба ашық тұруы керек. Қолданба орынды анықтау қызметтерін пайдалана алуы үшін, олар қосулы әрі Android TV құрылғысында қолжетімді болуы керек."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Бұл қолданба орналасқан жеріңізді ұялы байланыс мұнаралары мен Wi-Fi желілері сияқты желі көздерінің негізінде анықтайды. Бірақ бұл үшін қолданба экранда ашық тұруы керек. Қолданба бұл орынды анықтау қызметтерін пайдалана алуы үшін, олар қосулы әрі телефонда қолжетімді болуы керек."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Бұл қолданба нақты орналасқан жеріңіз туралы ақпаратты экранда ашық тұрғанда ғана анықтай алады. Қолданба орынды анықтау қызметтерін пайдалана алуы үшін, олар қосулы әрі құрылғыда қолжетімді болуы керек. Батарея көбірек тұтынылуы мүмкін."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"болжалды орналасқан жер туралы ақпаратқа тек ашық экранда кіру"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Бұл қолданба орналасқан жеріңіз туралы болжалды ақпаратты экранда ашық тұрғанда ғана анықтай алады. Қолданба орынды анықтау қызметтерін пайдалана алуы үшін, олар қосулы әрі құрылғыда қолжетімді болуы керек."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"геодеректерді фондық режимде пайдалану"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Егер ол шамамен есептегендегі немесе нақты орынды пайдалануға рұқсат алса, қолданба фондық режимде жұмыс істеп тұрып-ақ геодеректерді пайдалана алады."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Қолданба фондық және белсенді режимде де орналасқан жеріңіз мәліметін ала алады."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио параметрлерін өзгерту"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Қолданбаға дыбыс қаттылығы және аудио шығыс үндеткішін таңдау сияқты жаһандық аудио параметрлерін өзгерту мүмкіндігін береді."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"аудио жазу"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Қолданбаға планшеттегі Bluetooth конфигурациясын көру және жұпталған құрылғымен байланыс орнату немесе қабылдау мүмкіндігін береді."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Қолданба Android TV құрылғыңыздағы Bluetooth конфигурациясын көре алады және жұпталған құрылғылармен байланыс орнатып, оларды қабылдай алатын болады."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Қолданбаға телефондағы Bluetooth конфигурациясын көру және жұпталған құрылғымен байланыс орнату немесе қабылдау мүмкіндігін береді"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"NFC функциясын басқару"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Қолданбаға NFC белгілерімен, карталармен және оқу құралдарымен байланысуға рұқсат береді."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"экран бекітпесін істен шығару"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> қосылу орындалды"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Файлдарды көру үшін түртіңіз"</string>
<string name="pin_target" msgid="8036028973110156895">"PIN коды"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Босату"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Қолданба ақпараты"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Демо нұсқасы іске қосылуда..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" қызметіндегі <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> және <xliff:g id="TYPE_2">%3$s</xliff:g> деректері жаңартылсын ба?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Сақтау"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Жоқ, рақмет"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Қазір емес"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Ешқашан"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Жаңарту"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Жалғастыру"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"құпия сөз"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Экранды бөлу мүмкіндігін қосу/өшіру"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Құлып экраны"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Скриншот"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Қалқымалы терезедегі <xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасының жазу жолағы."</string>
</resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 43289eb..331c9f3 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"កម្មវិធីអ្នកគ្រប់គ្រងកម្រងព័ត៌មានការងារនេះអាចបាត់ ឬមានបញ្ហា។ ដូច្នេះហើយទើបកម្រងព័ត៌មានការងាររបស់អ្នក និងទិន្នន័យដែលពាក់ព័ន្ធត្រូវបានលុប។ សូមទាក់ទងទៅអ្នកគ្រប់គ្រងរបស់អ្នក ដើម្បីទទួលបានជំនួយ។"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"កម្រងព័ត៌មានការងាររបស់អ្នកលែងមាននៅលើឧបករណ៍នេះទៀតហើយ"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ការព្យាយាមបញ្ចូលពាក្យសម្ងាត់ច្រើនដងពេកហើយ"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"អ្នកគ្រប់គ្រងបានបោះបង់ឧបករណ៍ចោលដោយសារការប្រើប្រាស់ផ្ទាល់ខ្លួន"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"ឧបករណ៍ស្ថិតក្រោមការគ្រប់គ្រង"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"ស្ថាប័នរបស់អ្នកគ្រប់គ្រងឧបករណ៍នេះ ហើយអាចនឹងតាមដានចរាចរណ៍បណ្តាញ។ ចុចដើម្បីទទួលបានព័ត៌មានលម្អិត។"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"ឧបករណ៍របស់អ្នកនឹងត្រូវបានលុប"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"អនុញ្ញាតឱ្យកម្មវិធីផ្ញើការផ្សាយស្អិត ដែលមានបន្ទាប់ពីការផ្សាយចប់។ ការប្រើប្រាស់ច្រើនពេកអាចធ្វើឱ្យឧបករណ៍ Android TV របស់អ្នកប្រើអង្គចងចាំច្រើនជ្រុល ដែលធ្វើឱ្យវាដើរយឺត ឬគ្មានស្ថិរភាព។"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ឲ្យកម្មវិធីផ្ញើការប្រកាសដែលទាក់ទាញ ដែលមានបន្ទាប់ពីការប្រកាសចប់។ ការប្រើលើសអាចធ្វើឲ្យទូរស័ព្ទយឺត ឬមិនស្ថិតស្ថេរដោយធ្វើឲ្យវាប្រើអង្គចងចាំធំពេក។"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"អានទំនាក់ទំនងរបស់អ្នក"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"ឲ្យកម្មវិធីអានទិន្នន័យអំពីទំនាក់ទំនងរបស់អ្នកដែលមានក្នុងកុំព្យូទ័របន្ទះរបស់អ្នក រួមមានប្រេកង់ដែលអ្នកបានហៅ អ៊ីមែល ឬទាក់ទងតាមវិធីផ្សេងៗជាមួយមនុស្សណាម្នាក់។ សិទ្ធិនេះអនុញ្ញាតឲ្យកម្មវិធីរក្សាទុកទិន្នន័យទំនាក់ទំនងរបស់អ្នក ហើយកម្មវិធីព្យាបាទអាចចែករំលែកទិន្នន័យទំនាក់ទំនងដោយមិនឲ្យអ្នកដឹង។"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"អនុញ្ញាតឱ្យកម្មវិធីអានទិន្នន័យអំពីទំនាក់ទំនង ដែលអ្នកបានរក្សាទុកនៅក្នុងឧបករណ៍ Android TV របស់អ្នក រួមទាំងភាពញឹកញាប់ដែលអ្នកបានហៅទូរសព្ទ ផ្ញើអ៊ីមែល ឬទាក់ទងតាមវិធីផ្សេងទៀតជាមួយបុគ្គលជាក់លាក់។ ការអនុញ្ញាតនេះអនុញ្ញាតឱ្យកម្មវិធីរក្សាទុកទិន្នន័យទំនាក់ទំនងរបស់អ្នក ហើយកម្មវិធីគ្រោះថ្នាក់អាចនឹងចែករំលែកទិន្នន័យទំនាក់ទំនងដោយមិនឱ្យអ្នកដឹង។"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"ឲ្យកម្មវិធីអានទិន្នន័យអំពីទំនាក់ទំនងរបស់អ្នកដែលបានរក្សាទុកក្នុងទូរស័ព្ទ រួមមានប្រេកង់ដែលអ្នកបានហៅ អ៊ីមែល ឬទាក់ទងតាមវិធីផ្សេងៗជាមួយអ្នកណាម្នាក់។ សិទ្ធិនេះឲ្យកម្មវិធីរក្សាទុកទិន្នន័យទំនាក់ទំនងរបស់អ្នក ហើយកម្មវិធីព្យាបាទអាចចែករំលែកទិន្នន័យទំនាក់ទំនងដោយមិនឲ្យអ្នកដឹង។"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"អនុញ្ញាតឱ្យកម្មវិធីអានទិន្នន័យអំពីទំនាក់ទំនង ដែលអ្នកបានរក្សាទុកនៅក្នុងថេប្លេតរបស់អ្នក។ កម្មវិធីក៏នឹងមានសិទ្ធិចូលប្រើគណនីនៅលើថេប្លេត ដែលអ្នកបានបង្កើតទំនាក់ទំនងផងដែរ។ គណនីទាំងនោះអាចរួមបញ្ចូលទាំងគណនី ដែលបង្កើតដោយកម្មវិធីដែលអ្នកបានដំឡើង។ ការអនុញ្ញាតនេះអនុញ្ញាតឱ្យកម្មវិធីរក្សាទុកទិន្នន័យទំនាក់ទំនងរបស់អ្នក ហើយកម្មវិធីគ្រោះថ្នាក់អាចនឹងចែករំលែកទិន្នន័យទំនាក់ទំនងដោយមិនឱ្យអ្នកដឹង។"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"អនុញ្ញាតឱ្យកម្មវិធីអានទិន្នន័យអំពីទំនាក់ទំនង ដែលអ្នកបានរក្សាទុកនៅក្នុងឧបករណ៍ Android TV របស់អ្នក។ កម្មវិធីក៏នឹងមានសិទ្ធិចូលប្រើគណនីនៅលើឧបករណ៍ Android TV ដែលអ្នកបានបង្កើតទំនាក់ទំនងផងដែរ។ គណនីទាំងនោះអាចរួមបញ្ចូលទាំងគណនី ដែលបង្កើតដោយកម្មវិធីដែលអ្នកបានដំឡើង។ ការអនុញ្ញាតនេះអនុញ្ញាតឱ្យកម្មវិធីរក្សាទុកទិន្នន័យទំនាក់ទំនងរបស់អ្នក ហើយកម្មវិធីគ្រោះថ្នាក់អាចនឹងចែករំលែកទិន្នន័យទំនាក់ទំនងដោយមិនឱ្យអ្នកដឹង។"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"អនុញ្ញាតឱ្យកម្មវិធីអានទិន្នន័យអំពីទំនាក់ទំនង ដែលអ្នកបានរក្សាទុកនៅក្នុងទូរសព្ទរបស់អ្នក។ កម្មវិធីក៏នឹងមានសិទ្ធិចូលប្រើគណនីនៅលើទូរសព្ទ ដែលអ្នកបានបង្កើតទំនាក់ទំនងផងដែរ។ គណនីទាំងនោះអាចរួមបញ្ចូលទាំងគណនី ដែលបង្កើតដោយកម្មវិធីដែលអ្នកបានដំឡើង។ ការអនុញ្ញាតនេះអនុញ្ញាតឱ្យកម្មវិធីរក្សាទុកទិន្នន័យទំនាក់ទំនងរបស់អ្នក ហើយកម្មវិធីគ្រោះថ្នាក់អាចនឹងចែករំលែកទិន្នន័យទំនាក់ទំនងដោយមិនឱ្យអ្នកដឹង។"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"កែទំនាក់ទំនងរបស់អ្នក"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"ឲ្យកម្មវិធីកែទិន្នន័យអំពីទំនាក់ទំនងរបស់អ្នកដែលបានរក្សាទុកក្នុងកុំព្យូទ័របន្ទះ រួមមានប្រេកង់ដែលអ្នកបានហៅ អ៊ីមែល ឬទាក់ទងតាមវិធីផ្សេងៗជាមួយទំនាក់ទំនងជាក់លាក់។ សិទ្ធិនេះអនុញ្ញាតឲ្យកម្មវិធីលុបទិន្នន័យទំនាក់ទំនងរបស់អ្នក។"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"អនុញ្ញាតឱ្យកម្មវិធីកែទិន្នន័យអំពីទំនាក់ទំនងដែលអ្នកបានរក្សាទុកនៅក្នុងឧបករណ៍ Android TV របស់អ្នក រួមទាំងភាពញឹកញាប់ដែលអ្នកបានហៅទូរសព្ទ ផ្ញើអ៊ីមែល ឬទាក់ទងតាមវិធីផ្សេងទៀតជាមួយទំនាក់ទំនាក់ជាក់លាក់ផងដែរ។ ការអនុញ្ញាតនេះអនុញ្ញាតឱ្យកម្មវិធីលុបទិន្នន័យទំនាក់ទំនង។"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"ឲ្យកម្មវិធីកែទិន្នន័យអំពីទំនាក់ទំនងរបស់អ្នកដែលបានរក្សាទុកក្នុងទូរស័ព្ទរបស់អ្នក រួមមានប្រេកង់ដែលអ្នកបានហៅ អ៊ីមែល ឬបានទាក់ទងតាមវិធីផ្សេងៗជាមួយទំនាក់ទំនាក់ជាក់លាក់។ សិទ្ធិនេះឲ្យកម្មវិធីលុបទិន្នន័យទំនាក់ទំនង។"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"អនុញ្ញាតឱ្យកម្មវិធីកែទិន្នន័យអំពីទំនាក់ទំនង ដែលអ្នកបានរក្សាទុកនៅក្នុងថេប្លេតរបស់អ្នក។ ការអនុញ្ញាតនេះអនុញ្ញាតឱ្យកម្មវិធីលុបទិន្នន័យទំនាក់ទំនង។"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"អនុញ្ញាតឱ្យកម្មវិធីកែទិន្នន័យអំពីទំនាក់ទំនង ដែលអ្នកបានរក្សាទុកនៅក្នុងឧបករណ៍ Android TV របស់អ្នក។ ការអនុញ្ញាតនេះអនុញ្ញាតឱ្យកម្មវិធីលុបទិន្នន័យទំនាក់ទំនង។"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"អនុញ្ញាតឱ្យកម្មវិធីកែទិន្នន័យអំពីទំនាក់ទំនង ដែលអ្នកបានរក្សាទុកនៅក្នុងទូរសព្ទរបស់អ្នក។ ការអនុញ្ញាតនេះអនុញ្ញាតឱ្យកម្មវិធីលុបទិន្នន័យទំនាក់ទំនង។"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"អានកំណត់ហេតុហៅ"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"កម្មវិធីនេះអាចអានប្រវត្តិហៅទូរសព្ទរបស់អ្នកបាន។"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"សរសេរបញ្ជីហៅ"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ចូលដំណើរការពាក្យបញ្ជាក្រុមហ៊ុនផ្ដល់ទីតាំង"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ឲ្យកម្មវិធីចូលដំណើរការពាក្យបញ្ជាកម្មវិធីផ្ដល់ទីតាំងបន្ថែម។ វាអាចអនុញ្ញាតឲ្យកម្មវិធីទាក់ទងជាមួយប្រតិបត្តិការជីភីអេស ឬប្រភពទីតាំងផ្សេង។"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ចូលប្រើទីតាំងជាក់លាក់តែនៅផ្ទៃខាងមុខប៉ុណ្ណោះ"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"កម្មវិធីនេះអាចទទួលបានទីតាំងពិតប្រាកដរបស់អ្នកតែនៅពេលវាស្ថិតនៅផ្ទៃខាងមុខប៉ុណ្ណោះ។ សេវាកម្មទីតាំងទាំងនេះត្រូវតែបើក និងមាននៅលើទូរសព្ទរបស់អ្នក ដើម្បីឱ្យកម្មវិធីនេះអាចប្រើពួកវាបាន។ វាអាចប្រើថាមពលច្រើនជាងមុន។"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"ចូលប្រើទីតាំងប្រហាក់ប្រហែល (ផ្អែកលើបណ្តាញ) នៅផ្ទៃខាងមុខតែប៉ុណ្ណោះ"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"កម្មវិធីនេះអាចទទួលបានទីតាំងរបស់អ្នក ដោយផ្អែកលើប្រភពបណ្តាញដូចជា អង់តែនបណ្តាញទូរសព្ទ និងបណ្តាញ Wi-Fi ជាដើម ប៉ុន្តែនៅពេលកម្មវិធីស្ថិតនៅផ្ទៃខាងមុខតែប៉ុណ្ណោះ។ សេវាកម្មទីតាំងទាំងនេះត្រូវតែបើក និងមាននៅលើថេប្លេតរបស់អ្នក ដើម្បីឱ្យកម្មវិធីនេះអាចប្រើវាបាន។"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"កម្មវិធីនេះអាចទទួលបានទីតាំងរបស់អ្នក ដោយផ្អែកលើប្រភពបណ្តាញដូចជា អង់តែនបណ្តាញទូរសព្ទ និងបណ្តាញ Wi-Fi ជាដើម ប៉ុន្តែនៅពេលកម្មវិធីស្ថិតនៅផ្ទៃខាងមុខតែប៉ុណ្ណោះ។ សេវាកម្មទីតាំងទាំងនេះត្រូវតែបើក និងមាននៅលើឧបករណ៍ Android TV របស់អ្នក ដើម្បីអាចឱ្យកម្មវិធីប្រើវាបាន។"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"កម្មវិធីនេះអាចទទួលបានទីតាំងរបស់អ្នក ដោយផ្អែកលើប្រភពបណ្តាញដូចជា អង់តែនបណ្តាញទូរសព្ទ និងបណ្តាញ Wi-Fi ជាដើម ប៉ុន្តែនៅពេលកម្មវិធីស្ថិតនៅផ្ទៃខាងមុខតែប៉ុណ្ណោះ។ សេវាកម្មទីតាំងទាំងនេះត្រូវតែបើក និងមាននៅលើទូរសព្ទរបស់អ្នក ដើម្បីឱ្យកម្មវិធីនេះអាចប្រើវាបាន។"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"កម្មវិធីនេះអាចទទួលបានទីតាំងជាក់លាក់របស់អ្នក តែនៅពេលវាស្ថិតនៅផ្ទៃខាងមុខប៉ុណ្ណោះ។ សេវាកម្មទីតាំងត្រូវតែបើក និងមាននៅលើឧបករណ៍របស់អ្នក ដើម្បីអាចឱ្យកម្មវិធីប្រើវាបាន។ សកម្មភាពនេះអាចបង្កើនការប្រើប្រាស់ថ្ម។"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"ចូលប្រើទីតាំងប្រហាក់ប្រហែលតែនៅផ្ទៃខាងមុខប៉ុណ្ណោះ"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"កម្មវិធីនេះអាចទទួលបានទីតាំងប្រហាក់ប្រហែលរបស់អ្នក តែនៅពេលវាស្ថិតនៅផ្ទៃខាងមុខប៉ុណ្ណោះ។ សេវាកម្មទីតាំងត្រូវតែបើក និងមាននៅលើឧបករណ៍របស់អ្នក ដើម្បីអាចឱ្យកម្មវិធីប្រើវាបាន។"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"ចូលប្រើទីតាំងនៅផ្ទៃខាងក្រោយ"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ប្រសិនបើផ្ដល់ការអនុញ្ញាតនេះបន្ថែមពីលើការចូលប្រើទីតាំងជាក់លាក់ ឬប្រហាក់ប្រហែល កម្មវិធីនឹងអាចចូលប្រើទីតាំងនោះ ខណៈពេលដំណើរការនៅផ្ទៃខាងក្រោយ។"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"កម្មវិធីនេះអាចចូលប្រើទីតាំង ពេលកំពុងដំណើរការនៅផ្ទៃខាងក្រោយ បន្ថែមពីលើការចូលប្រើទីតាំងនៅផ្ទៃខាងមុខ។"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ប្ដូរការកំណត់អូឌីយូរបស់អ្នក"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ឲ្យកម្មវិធីកែការកំណត់សំឡេងសកល ដូចជាកម្រិតសំឡេង និងអូប៉ាល័រដែលបានប្រើសម្រាប់លទ្ធផល។"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ថតសំឡេង"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ឲ្យកម្មវិធីមើលការកំណត់រចនាសម្ព័ន្ធប៊្លូធូសលើកុំព្យូទ័របន្ទះ ព្រមទាំងធ្វើការតភ្ជាប់ និងទទួលជាមួយឧបករណ៍បានផ្គូផ្គង។"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"អនុញ្ញាតឱ្យកម្មវិធីមើលការកំណត់រចនាសម្ព័ន្ធប៊្លូធូសនៅលើឧបករណ៍ Android TV របស់អ្នក ព្រមទាំងធ្វើការតភ្ជាប់ និងទទួលយកការតភ្ជាប់ជាមួយឧបករណ៍ដែលបានផ្គូផ្គង។"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ឲ្យកម្មវិធីមើលការកំណត់រចនាសម្ព័ន្ធប៊្លូធូសក្នុងទូរស័ព្ទ ដើម្បីទទួល និងតភ្ជាប់ជាមួយឧបករណ៍បានផ្គូផ្គង។"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ពិនិត្យការទាក់ទងនៅក្បែរ (NFC)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ឲ្យកម្មវិធីទាក់ទងជាមួយស្លាក (NFC) កាត និងកម្មវិធីអាន។"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"បិទការចាក់សោអេក្រង់របស់អ្នក"</string>
@@ -1864,7 +1865,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"បានភ្ជាប់ទៅ <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ប៉ះដើម្បីមើលឯកសារ"</string>
<string name="pin_target" msgid="8036028973110156895">"ខ្ទាស់"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"មិនខ្ទាស់"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ព័ត៌មានកម្មវិធី"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"កំពុងចាប់ផ្តើមការបង្ហាញសាកល្បង…"</string>
@@ -1907,6 +1912,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"ធ្វើបច្ចុប្បន្នភាពធាតុទាំងនេះនៅក្នុង "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> និង <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"រក្សាទុក"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"ទេ អរគុណ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"កុំទាន់"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"កុំឱ្យសោះ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"ធ្វើបច្ចុប្បន្នភាព"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"បន្ត"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"ពាក្យសម្ងាត់"</string>
@@ -2002,5 +2009,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"បិទ/បើកមុខងារបំបែកអេក្រង់"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"អេក្រង់ចាក់សោ"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"រូបថតអេក្រង់"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"កម្មវិធី <xliff:g id="APP_NAME">%1$s</xliff:g> នៅក្នុងវិនដូលោតឡើង។"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"របារពណ៌នាអំពី <xliff:g id="APP_NAME">%1$s</xliff:g>។"</string>
</resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 6ebe7b4..cb7fd71 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ನಿರ್ವಾಹಕ ಅಪ್ಲಿಕೇಶನ್ ಕಳೆದು ಹೋಗಿದೆ ಅಥವಾ ಹಾಳಾಗಿದೆ. ಇದರ ಪರಿಣಾಮವಾಗಿ ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ಮತ್ತು ಅದಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಸಹಾಯಕ್ಕಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ಈ ಸಾಧನದಲ್ಲಿ ಈಗ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ಹಲವಾರು ಪಾಸ್ವರ್ಡ್ ಪ್ರಯತ್ನಗಳು"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"ವೈಯಕ್ತಿಕ ಬಳಕೆಗಾಗಿ ನಿರ್ವಾಹಕರು ತೊರೆದ ಸಾಧನ"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"ಸಾಧನವನ್ನು ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"ನಿಮ್ಮ ಸಂಸ್ಥೆಯು ಈ ಸಾಧನವನ್ನು ನಿರ್ವಹಿಸುತ್ತದೆ ಮತ್ತು ಅದು ನೆಟ್ವರ್ಕ್ ಟ್ರಾಫಿಕ್ ಮೇಲೆ ಗಮನವಿರಿಸಬಹುದು. ವಿವರಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ಪ್ರಸಾರವು ಮುಕ್ತಾಯಗೊಂಡ ನಂತರ ಉಳಿದಿರುವ ಜಿಗುಟಾದ ಪ್ರಸಾರಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದರ ಹೆಚ್ಚಿನ ಬಳಕೆಯು Android TV ಸಾಧನವನ್ನು ನಿಧಾನಗೊಳಿಸುತ್ತದೆ ಅಥವಾ ಅತಿಯಾಗಿ ಮೆಮೊರಿಯನ್ನು ಬಳಸುವಂತೆ ಮಾಡುವ ಮೂಲಕ ಅಸ್ಥಿರಗೊಳಿಸುತ್ತದೆ."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ಪ್ರಸಾರ ಕೊನೆಗೊಂಡ ನಂತರ ಹಾಗೆಯೇ ಉಳಿಯುವ ಸ್ಟಿಕಿ ಪ್ರಸಾರಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಮಿತಿಮೀರಿದ ಬಳಕೆಯು ಫೋನ್ ಅನ್ನು ನಿಧಾನಗೊಳಿಸಬಹುದು ಅಥವಾ ಅತಿಯಾದ ಮೆಮೊರಿ ಬಳಕೆಯು ಅಸ್ಥಿರತೆಯನ್ನು ಉಂಟುಮಾಡಬಹುದು."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ನಿಮ್ಮ ಸಂಪರ್ಕಗಳನ್ನು ಓದಿರಿ"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ನಲ್ಲಿ ನಿರ್ದಿಷ್ಟ ವ್ಯಕ್ತಿಗಳೊಂದಿಗೆ ನೀವು ಇತರ ಮಾರ್ಗಗಳಲ್ಲಿ ಮಾಡಿರುವ ಕರೆ, ಇಮೇಲ್ ಅಥವಾ ಸಂವಹನ ನಡೆಸಿರುವ ಆವರ್ತನವೂ ಸೇರಿದಂತೆ, ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತ ಡೇಟಾವನ್ನು ರೀಡ್ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ನಿಮ್ಮ ಸಂಪರ್ಕದ ಡೇಟಾವನ್ನು ಉಳಿಸಿಕೊಳ್ಳಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ ಮತ್ತು ದುರುದ್ದೇಶಪೂರಿತ ಅಪ್ಲಿಕೇಶನ್ಗಳು ನಿಮ್ಮ ಗಮನಕ್ಕೆ ತರದೆಯೇ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"ನಿರ್ದಿಷ್ಟ ವ್ಯಕ್ತಿಗಳೊಂದಿಗೆ ಇತರ ವಿಧಾನಗಳಲ್ಲಿ ನೀವು ಕರೆ ಮಾಡಿದ, ಇಮೇಲ್ ಮಾಡಿದ ಅಥವಾ ಸಂವಹನ ಮಾಡಿದ ಆವರ್ತನ ಪ್ರಮಾಣ ಸೇರಿದಂತೆ ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಓದಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಈ ಅನುಮತಿಯು ನಿಮ್ಮ ಸಂಪರ್ಕದ ಡೇಟಾವನ್ನು ಉಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ, ಮತ್ತು ದುರುದ್ದೇಶಪೂರಿತ ಅಪ್ಲಿಕೇಶನ್ಗಳು ನಿಮ್ಮ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ನಿಮಗೆ ತಿಳಿಯದಂತೆ ಹಂಚಿಕೊಳ್ಳಬಹುದು."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ನಿರ್ದಿಷ್ಟ ವ್ಯಕ್ತಿಗಳ ಜೊತೆಗೆ ನೀವು ವಿವಿಧ ಮಾರ್ಗಗಳಲ್ಲಿ ಮಾಡಿರುವ ಕರೆ, ಇಮೇಲ್ ಮತ್ತು ಸಂವಹನವನ್ನು ನಡೆಸಿರುವ ಆವರ್ತನವೂ ಸೇರಿದಂತೆ, ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತ ಡೇಟಾವನ್ನು ರೀಡ್ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ನಿಮ್ಮ ಸಂಪರ್ಕದ ಡೇಟಾವನ್ನು ಉಳಿಸಿಕೊಳ್ಳಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ ಮತ್ತು ದುರುದ್ದೇಶಪೂರಿತ ಅಪ್ಲಿಕೇಶನ್ಗಳು ನಿಮ್ಮ ಗಮನಕ್ಕೆ ತರದೆಯೇ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ರೀಡ್ ಮಾಡಲು ಆ್ಯಪ್ಗೆ ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಸಂಪರ್ಕಗಳನ್ನು ರಚಿಸಿದ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ನಲ್ಲಿನ ಖಾತೆಗಳಿಗೂ ಸಹ ಆ್ಯಪ್ಗಳು ಪ್ರವೇಶ ಹೊಂದಿರುತ್ತವೆ. ಇದರಲ್ಲಿ ನೀವು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿದ ಆ್ಯಪ್ಗಳು ರಚಿಸಿದ ಖಾತೆಗಳನ್ನು ಒಳಗೊಂಡಿರಬಹುದು. ಈ ಅನುಮತಿಯು ನಿಮ್ಮ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಉಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಅನುಮತಿಸುತ್ತದೆ, ಆದರೆ ದುರುದ್ದೇಶಪೂರಿತ ಆ್ಯಪ್ಗಳಿಗೆ ನಿಮ್ಮ ಗಮನಕ್ಕೆ ಬಾರದೇ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ರೀಡ್ ಮಾಡಲು ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ರಚಿಸಲಾದ ಸಂಪರ್ಕಗಳಿಗೆ ಖಾತೆಗಳಿಗೆ ಆ್ಯಪ್ಗಳು ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತವೆ. ಇದರಲ್ಲಿ ನೀವು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿದ ಆ್ಯಪ್ಗಳು ರಚಿಸಿದ ಖಾತೆಗಳನ್ನು ಒಳಗೊಂಡಿರಬಹುದು. ಈ ಅನುಮತಿಯು ನಿಮ್ಮ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಉಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಅನುಮತಿಸುತ್ತದೆ, ಆದರೆ ದುರುದ್ದೇಶಪೂರಿತ ಆ್ಯಪ್ಗಳಿಗೆ ನಿಮ್ಮ ಗಮನಕ್ಕೆ ಬಾರದೇ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ರೀಡ್ ಮಾಡಲು ಆ್ಯಪ್ಗೆ ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ. ಸಂಪರ್ಕಗಳನ್ನು ರಚಿಸಿದ ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿನ ಖಾತೆಗಳಿಗೂ ಸಹ ಆ್ಯಪ್ಗಳು ಪ್ರವೇಶ ಹೊಂದಿರುತ್ತವೆ. ಇದರಲ್ಲಿ ನೀವು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿದ ಆ್ಯಪ್ಗಳು ರಚಿಸಿದ ಖಾತೆಗಳನ್ನು ಒಳಗೊಂಡಿರಬಹುದು. ಈ ಅನುಮತಿಯು ನಿಮ್ಮ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಉಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಅನುಮತಿಸುತ್ತದೆ, ಆದರೆ ದುರುದ್ದೇಶಪೂರಿತ ಆ್ಯಪ್ಗಳಿಗೆ ನಿಮ್ಮ ಗಮನಕ್ಕೆ ಬಾರದೇ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ನಿಮ್ಮ ಸಂಪರ್ಕಗಳನ್ನು ಮಾರ್ಪಡಿಸಿ"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"ನಿರ್ದಿಷ್ಟ ಸಂಪರ್ಕಗಳೊಂದಿಗೆ ಇತರ ಮಾರ್ಗಗಳಲ್ಲಿ ನೀವು ಕರೆ, ಇಮೇಲ್, ಅಥವಾ ಸಂವಹನ ನಡೆಸಿರುವ ಆವರ್ತನವೂ ಒಳಗೊಂಡಂತೆ, ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಸಂಪರ್ಕದ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"ನಿರ್ದಿಷ್ಟ ವ್ಯಕ್ತಿಗಳೊಂದಿಗೆ ಇತರ ವಿಧಾನಗಳಲ್ಲಿ ನೀವು ಕರೆ ಮಾಡಿದ, ಇಮೇಲ್ ಮಾಡಿದ ಅಥವಾ ಸಂವಹನ ಮಾಡಿದ ಆವರ್ತನ ಪ್ರಮಾಣ ಸೇರಿದಂತೆ ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"ನಿರ್ದಿಷ್ಟ ಸಂಪರ್ಕಗಳೊಂದಿಗೆ ಇತರ ಮಾರ್ಗಗಳಲ್ಲಿ ನೀವು ಕರೆ, ಇಮೇಲ್, ಅಥವಾ ಸಂವಹನ ನಡೆಸಿರುವ ಆವರ್ತನವೂ ಒಳಗೊಂಡಂತೆ, ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಸಂಪರ್ಕದ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಆ್ಯಪ್ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ಆ್ಯಪ್ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಆ್ಯಪ್ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ಆ್ಯಪ್ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಆ್ಯಪ್ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ಆ್ಯಪ್ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ಕರೆಯ ಲಾಗ್ ಓದಿ"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಕರೆಯ ಇತಿಹಾಸ ಓದಬಹುದು."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"ಕರೆ ಲಾಗ್ ಬರೆಯಿರಿ"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ಹೆಚ್ಚುವರಿ ಸ್ಥಳ ಪೂರೈಕೆದಾರರ ಆದೇಶಗಳನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ಹೆಚ್ಚಿನ ಸ್ಥಳ ಪೂರೈಕೆದಾರ ಆದೇಶಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಇದು GPS ಅಥವಾ ಇತರ ಸ್ಥಳ ಮೂಲಗಳ ಕಾರ್ಯಾಚರಣೆಯಲ್ಲಿ ಮಧ್ಯ ಪ್ರವೇಶಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸಬಹುದು."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ಮುನ್ನೆಲೆಯಲ್ಲಿ ಮಾತ್ರ ನಿಖರವಾದ ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಮುಂಭಾಗದಲ್ಲಿರುವಾಗ ಮಾತ್ರ ನಿಮ್ಮ ನಿಖರ ಸ್ಥಳವನ್ನು ಪಡೆಯಬಹುದು. ಈ ಸ್ಥಳ ಸೇವೆಗಳನ್ನು ಆನ್ ಮಾಡಿರಬೇಕು ಮತ್ತು ಅವುಗಳನ್ನು ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಸಾಧ್ಯವಾಗುವಂತೆ ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ಅವುಗಳು ಲಭ್ಯವಿರಬೇಕು."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"ಮುನ್ನೆಲೆಯಲ್ಲಿ ಮಾತ್ರ ಅಂದಾಜು ಸ್ಥಳವನ್ನು (ನೆಟ್ವರ್ಕ್-ಆಧಾರಿತ) ಪ್ರವೇಶಿಸಿ"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ಈ ಆ್ಯಪ್ ನಿಮ್ಮ ಸ್ಥಳವನ್ನು ಸೆಲ್ ಟವರ್ಗಳು ಮತ್ತು ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ಗಳಂತಹ ನೆಟ್ವರ್ಕ್ ಮೂಲಗಳ ಆಧಾರದ ಮೇಲೆ ಪಡೆಯಬಹುದು, ಆದರೆ ಆ್ಯಪ್ ಮುನ್ನೆಲೆಯಲ್ಲಿದ್ದಾಗ ಮಾತ್ರ. ಈ ಸ್ಥಳ ಸೇವೆಗಳನ್ನು ಆನ್ ಮಾಡಿರಬೇಕು ಮತ್ತು ಅವುಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್ಗೆ ಸಾಧ್ಯವಾಗುವಂತೆ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ನಲ್ಲಿ ಅವುಗಳು ಲಭ್ಯವಿರಬೇಕು."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಸ್ಥಳವನ್ನು ಸೆಲ್ ಟವರ್ಗಳು ಮತ್ತು ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ಗಳಂತಹ ನೆಟ್ವರ್ಕ್ ಮೂಲಗಳ ಆಧಾರದ ಮೇಲೆ ಪಡೆಯಬಹುದು, ಆದರೆ ಅದು ಅಪ್ಲಿಕೇಶನ್ ಮುನ್ನೆಲೆಯಲ್ಲಿದ್ದಾಗ ಮಾತ್ರ. ಅವುಗಳನ್ನು ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಸಾಧ್ಯವಾಗಲು ಈ ಸ್ಥಳ ಸೇವೆಗಳನ್ನು ಆನ್ ಮಾಡಿರಬೇಕು ಮತ್ತು ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಅವು ಲಭ್ಯವಿರಬೇಕು."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ಈ ಆ್ಯಪ್ ನಿಮ್ಮ ಸ್ಥಳವನ್ನು ಸೆಲ್ ಟವರ್ಗಳು ಮತ್ತು ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ಗಳಂತಹ ನೆಟ್ವರ್ಕ್ ಮೂಲಗಳ ಆಧಾರದ ಮೇಲೆ ಪಡೆಯಬಹುದು, ಆದರೆ ಆ್ಯಪ್ ಮುನ್ನೆಲೆಯಲ್ಲಿದ್ದಾಗ ಮಾತ್ರ. ಈ ಸ್ಥಳ ಸೇವೆಗಳನ್ನು ಆನ್ ಮಾಡಿರಬೇಕು ಮತ್ತು ಅವುಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್ಗೆ ಸಾಧ್ಯವಾಗುವಂತೆ ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ಅವುಗಳು ಲಭ್ಯವಿರಬೇಕು."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"ಈ ಆ್ಯಪ್ ಮುಂಭಾಗದಲ್ಲಿರುವಾಗ ಮಾತ್ರ ನಿಮ್ಮ ನಿಖರ ಸ್ಥಳವನ್ನು ಪಡೆಯಬಲ್ಲದು. ಸ್ಥಳ ಸೇವೆಗಳನ್ನು ಆನ್ ಮಾಡಿರಬೇಕು ಮತ್ತು ಅವುಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್ಗೆ ಬಳಸಲು ಸಾಧ್ಯವಾಗುವಂತೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಅವುಗಳು ಲಭ್ಯವಿರಬೇಕು. ಇದು ಬ್ಯಾಟರಿ ಬಳಕೆಯನ್ನು ಹೆಚ್ಚಿಸಬಹುದು."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"ಮುನ್ನೆಲೆಯಲ್ಲಿ ಮಾತ್ರ ಅಂದಾಜು ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"ಈ ಆ್ಯಪ್ ಮುನ್ನೆಲೆಯಲ್ಲಿರುವಾಗ ಮಾತ್ರ ನಿಮ್ಮ ಅಂದಾಜು ಸ್ಥಳವನ್ನು ಪಡೆಯಬಲ್ಲದು. ಸ್ಥಳ ಸೇವೆಗಳನ್ನು ಆನ್ ಮಾಡಿರಬೇಕು ಮತ್ತು ಅವುಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್ಗೆ ಸಾಧ್ಯವಾಗುವಂತೆ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಅವುಗಳು ಲಭ್ಯವಿರಬೇಕು."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ಇದನ್ನು ಅಂದಾಜು ಅಥವಾ ನಿಖರವಾದ ಸ್ಥಳ ಪ್ರವೇಶಕ್ಕೆ ಹೆಚ್ಚುವರಿಯಾಗಿ ಅನುಮತಿಸಿದರೆ, ಆ್ಯಪ್ ಹಿನ್ನೆಲೆಯಲ್ಲಿ ರನ್ ಆಗುತ್ತಿರುವಾಗ ಅದು ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಬಹುದು."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"ಈ ಆ್ಯಪ್ ಹಿನ್ನೆಲೆಯಲ್ಲಿ ರನ್ ಆಗುವಾಗ ಸ್ಥಳದ ಜೊತೆಗೆ ಮುನ್ನೆಲೆಯಲ್ಲಿನ ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಬಹುದು."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ನಿಮ್ಮ ಆಡಿಯೊ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಬದಲಾಯಿಸಿ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ವಾಲ್ಯೂಮ್ ರೀತಿಯ ಮತ್ತು ಔಟ್ಪುಟ್ಗಾಗಿ ಯಾವ ಸ್ಪೀಕರ್ ಬಳಸಬೇಕು ಎಂಬ ರೀತಿಯ ಜಾಗತಿಕ ಆಡಿಯೊ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ಆಡಿಯೊ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ಟ್ಯಾಬ್ಲೆಟ್ನಲ್ಲಿ ಬ್ಲೂಟೂತ್ನ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ವೀಕ್ಷಿಸಲು ಮತ್ತು ಜೋಡಿ ಮಾಡಿರುವ ಸಾಧನಗಳೊಂದಿಗೆ ಸಂಪರ್ಕಗಳನ್ನು ಕಲ್ಪಿಸಲು ಹಾಗೂ ಸ್ವೀಕರಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಬ್ಲೂಟೂತ್ನ ಕಾನ್ಫಿಗರೇಶನ್ ವೀಕ್ಷಿಸಲು ಮತ್ತು ಜೋಡಿಸಿರುವ ಸಾಧನಗಳೊಂದಿಗೆ ಸಂಪರ್ಕಗಳನ್ನು ಮಾಡಲು ಮತ್ತು ಸ್ವೀಕರಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ಫೋನ್ನಲ್ಲಿ ಬ್ಲೂಟೂತ್ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ವೀಕ್ಷಿಸಲು ಮತ್ತು ಜೋಡಿ ಮಾಡಿರುವ ಸಾಧನಗಳೊಂದಿಗೆ ಸಂಪರ್ಕಗಳನ್ನು ಕಲ್ಪಿಸಲು ಹಾಗೂ ಸ್ವೀಕರಿಸಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ಸಮೀಪ ಕ್ಷೇತ್ರ ಸಂವಹನವನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ಸಮೀಪದ ಕ್ಷೇತ್ರ ಸಂವಹನ (NFC) ಟ್ಯಾಗ್ಗಳು, ಕಾರ್ಡ್ಗಳು, ಮತ್ತು ಓದುಗರನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
@@ -1257,7 +1258,7 @@
<item msgid="9177085807664964627">"VPN"</item>
</string-array>
<string name="network_switch_type_name_unknown" msgid="3665696841646851068">"ಅಪರಿಚಿತ ನೆಟ್ವರ್ಕ್ ಪ್ರಕಾರ"</string>
- <string name="accept" msgid="5447154347815825107">"ಸ್ವೀಕರಿಸು"</string>
+ <string name="accept" msgid="5447154347815825107">"ಸ್ವೀಕರಿಸಿ"</string>
<string name="decline" msgid="6490507610282145874">"ನಿರಾಕರಿಸಿ"</string>
<string name="select_character" msgid="3352797107930786979">"ಅಕ್ಷರವನ್ನು ಸೇರಿಸಿ"</string>
<string name="sms_control_title" msgid="4748684259903148341">"SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ಫೈಲ್ಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="pin_target" msgid="8036028973110156895">"ಪಿನ್ ಮಾಡು"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"ಅನ್ಪಿನ್"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ಅಪ್ಲಿಕೇಶನ್ ಮಾಹಿತಿ"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ಡೆಮೋ ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"ಈ ಮುಂದಿನ ಐಟಂಗಳನ್ನು "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ನಲ್ಲಿ ಅಪ್ಡೇಟ್ ಮಾಡುವುದೇ: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ಮತ್ತು <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"ಉಳಿಸಿ"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"ಬೇಡ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ಸದ್ಯಕ್ಕೆ ಬೇಡ"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ಎಂದೂ ಇಲ್ಲ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"ಅಪ್ಡೇಟ್"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ಮುಂದುವರಿಯಿರಿ"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"ಪಾಸ್ವರ್ಡ್"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"ಸ್ಪ್ಲಿಟ್-ಸ್ಕ್ರೀನ್ ಟಾಗಲ್ ಮಾಡಿ"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"ಲಾಕ್ ಸ್ಕ್ರೀನ್"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ಸ್ಕ್ರೀನ್ಶಾಟ್"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"ಪಾಪ್-ಅಪ್ ಸ್ಪೇಸ್ ವಿಂಡೋದಲ್ಲಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್ನ ಶೀರ್ಷಿಕೆಯ ಪಟ್ಟಿ."</string>
</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index c6caedf..95cd444 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"직장 프로필 관리 앱이 없거나 손상되어 직장 프로필 및 관련 데이터가 삭제되었습니다. 도움이 필요한 경우 관리자에게 문의하세요."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"직장 프로필을 이 기기에서 더 이상 사용할 수 없습니다."</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"비밀번호 입력을 너무 많이 시도함"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"관리자가 기기를 개인용으로 전환했습니다."</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"관리되는 기기"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"조직에서 이 기기를 관리하며 네트워크 트래픽을 모니터링할 수도 있습니다. 자세한 내용을 보려면 탭하세요."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"기기가 삭제됩니다."</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"앱에서 브로드캐스트가 끝난 후에도 남아 있는 스티키 브로드캐스트를 허용합니다. 지나치게 사용하면 Android TV 기기에서 메모리를 너무 많이 사용하여 기기가 불안정해지거나 속도가 저하될 수 있습니다."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"앱이 브로드캐스트가 끝난 후에 남은 브로드캐스트를 보낼 수 있도록 허용합니다. 앱을 지나치게 사용하면 휴대전화에서 메모리를 너무 많이 사용하도록 하여 속도를 저하시키거나 불안정하게 만들 수 있습니다."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"연락처 읽기"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"특정인과 전화, 이메일 또는 기타 수단으로 연락한 빈도를 포함하여 사용자 태블릿에 저장된 연락처에 대한 데이터를 앱이 읽도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 저장할 수 있으며, 악성 앱이 사용자 모르게 연락처 데이터를 공유할 수도 있습니다."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"특정인과 전화, 이메일 또는 기타 방법으로 연락한 빈도를 비롯하여 Android TV 기기에 저장된 연락처에 관한 데이터를 앱이 읽도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 저장할 수 있으며, 악성 앱이 사용자가 모르게 연락처 데이터를 공유할 수도 있습니다."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"특정인과 전화, 이메일 또는 기타 수단으로 연락한 빈도를 포함하여 사용자 휴대전화에 저장된 연락처에 대한 데이터를 앱이 읽도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 저장할 수 있으며, 악성 앱이 사용자 모르게 연락처 데이터를 공유할 수도 있습니다."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"태블릿에 저장된 연락처에 관한 데이터를 앱이 읽도록 허용합니다. 또한 앱은 태블릿에서 연락처가 생성된 계정에도 액세스할 수 있습니다. 여기에는 설치한 앱에서 생성한 계정이 포함될 수 있습니다. 이 권한을 사용하면 앱이 연락처 데이터를 저장할 수 있으며, 악성 앱이 사용자가 모르게 연락처 데이터를 공유할 수도 있습니다."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV 기기에 저장된 연락처에 관한 데이터를 앱이 읽도록 허용합니다. 또한 앱은 Android TV 기기에서 연락처가 생성된 계정에도 액세스할 수 있습니다. 여기에는 설치한 앱에서 생성한 계정이 포함될 수 있습니다. 이 권한을 사용하면 앱이 연락처 데이터를 저장할 수 있으며, 악성 앱이 사용자가 모르게 연락처 데이터를 공유할 수도 있습니다."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"휴대전화에 저장된 연락처에 관한 데이터를 앱이 읽도록 허용합니다. 또한 앱은 휴대전화에서 연락처가 생성된 계정에도 액세스할 수 있습니다. 여기에는 설치한 앱에서 생성한 계정이 포함될 수 있습니다. 이 권한을 사용하면 앱이 연락처 데이터를 저장할 수 있으며, 악성 앱이 사용자가 모르게 연락처 데이터를 공유할 수도 있습니다."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"연락처 수정"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"특정인과 전화, 이메일 또는 기타 수단으로 연락한 빈도를 포함하여 사용자 태블릿에 저장된 연락처에 대한 데이터를 앱이 수정할 수 있도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 삭제할 수 있습니다."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"특정인과 전화, 이메일 또는 기타 방법으로 연락한 빈도를 비롯하여 Android TV 기기에 저장된 연락처에 관한 데이터를 앱이 수정하도록 허용합니다. 이 권한을 사용하면 앱에서 연락처 데이터를 삭제할 수 있습니다."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"특정인과 전화, 이메일 또는 기타 수단으로 연락한 빈도를 포함하여 사용자 휴대전화에 저장된 연락처에 대한 데이터를 앱이 수정할 수 있도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 삭제할 수 있습니다."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"태블릿에 저장된 연락처에 관한 데이터를 앱이 수정하도록 허용합니다. 이 권한을 사용하면 앱에서 연락처 데이터를 삭제할 수 있습니다."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV 기기에 저장된 연락처에 관한 데이터를 앱이 수정하도록 허용합니다. 이 권한을 사용하면 앱에서 연락처 데이터를 삭제할 수 있습니다."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"휴대전화에 저장된 연락처에 관한 데이터를 앱이 수정하도록 허용합니다. 이 권한을 사용하면 앱에서 연락처 데이터를 삭제할 수 있습니다."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"통화 기록 읽기"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"이 앱은 통화 기록을 읽을 수 있습니다."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"통화 기록 쓰기"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"추가 위치 제공업체 명령에 접근"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"앱이 추가 위치 정보 제공 기능의 명령에 접근하도록 허용합니다. 이 경우 앱이 GPS 또는 기타 위치 소스의 작동을 방해할 수 있습니다."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"포그라운드에서만 정확한 위치에 액세스"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"이 앱은 포그라운드에 있을 때만 나의 정확한 위치를 알 수 있습니다. 앱에서 위치 서비스를 사용하려면 휴대전화에서 위치 서비스가 사용 설정되어 있으며 사용할 수 있어야 합니다. 배터리 사용량이 늘어날 수 있습니다."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"포그라운드에서만 대략적인 위치(네트워크 기반)에 액세스"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"앱이 포그라운드에 있을 때만 휴대전화 기지국과 Wi-Fi 네트워크 등의 네트워크 소스를 바탕으로 사용자의 위치를 파악할 수 있습니다. 앱에서 위치 서비스를 사용하려면 태블릿에서 위치 서비스가 켜져 있으며 사용 가능해야 합니다."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"앱이 포그라운드에 있을 때만 휴대전화 기지국과 Wi-Fi 네트워크 등의 네트워크 소스를 바탕으로 사용자의 위치를 파악할 수 있습니다. 앱에서 위치 서비스를 사용하려면 Android TV 기기에서 위치 서비스를 지원하며 사용 설정되어 있어야 합니다."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"앱이 포그라운드에 있을 때만 휴대전화 기지국과 Wi-Fi 네트워크 등의 네트워크 소스를 바탕으로 사용자의 위치를 파악할 수 있습니다. 앱에서 위치 서비스를 사용하려면 휴대전화에서 위치 서비스가 켜져 있으며 사용 가능해야 합니다."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"이 앱은 포그라운드에 있을 때만 나의 정확한 위치를 확인할 수 있습니다. 앱에서 위치 서비스를 사용하려면 기기에서 위치 서비스가 지원되며 사용 설정되어 있어야 합니다. 배터리 사용량이 늘어날 수 있습니다."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"포그라운드에서만 대략적인 위치에 액세스"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"이 앱은 포그라운드에 있을 때만 나의 대략적인 위치를 확인할 수 있습니다. 앱에서 위치 서비스를 사용하려면 기기에서 위치 서비스가 지원되며 사용 설정되어 있어야 합니다."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"백그라운드에서 위치 정보 액세스"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"이 권한이 대략적인 위치 정보 또는 정확한 위치 정보 액세스 권한에 추가적으로 부여되면 앱이 백그라운드에서 실행되는 동안 위치에 액세스할 수 있습니다."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"이 앱이 포그라운드뿐만 아니라 백그라운드에서 실행되는 동안에도 위치에 액세스할 수 있습니다."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"오디오 설정 변경"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"앱이 음량이나 출력을 위해 사용하는 스피커 등 전체 오디오 설정을 변경할 수 있도록 허용합니다."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"오디오 녹음"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"앱이 태블릿의 블루투스 설정을 확인하고 페어링된 기기에 연결하며 연결을 수락할 수 있도록 허용합니다."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"앱이 Android TV 기기에서 블루투스 설정을 보고 페어링된 기기에 연결하며 페어링된 기기와의 연결을 수락하도록 허용합니다."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"앱에서 휴대전화의 블루투스 설정을 확인하고 등록된 디바이스에 연결하며 연결을 수락할 수 있습니다."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"NFC(Near Field Communication) 제어"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"앱이 NFC(근거리 무선 통신) 태그, 카드 및 리더와 통신할 수 있도록 허용합니다."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"화면 잠금 사용 중지"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g>에 연결됨"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"파일을 확인하려면 탭하세요."</string>
<string name="pin_target" msgid="8036028973110156895">"고정"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"고정 해제"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"앱 정보"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"데모 시작 중..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"에서 <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> 및 <xliff:g id="TYPE_2">%3$s</xliff:g> 업데이트"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"저장"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"사용 안함"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"나중에"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"사용 안함"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"업데이트"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"계속"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"비밀번호"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"화면 분할 모드 전환"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"잠금 화면"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"스크린샷"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"팝업 창의 <xliff:g id="APP_NAME">%1$s</xliff:g> 앱"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>의 자막 표시줄입니다."</string>
</resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 341c996..c247e9f 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Жумуш профилинин башкаруучу колдонмосу жок же бузулгандыктан, жумуш профилиңиз жана ага байланыштуу дайындар жок кылынды. Жардам алуу үчүн администраторуңузга кайрылыңыз."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Жумуш профилиңиз бул түзмөктөн жок кылынды"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Өтө көп жолу сырсөздү киргизүү аракети жасалды"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Админ түзмөктөн жеке колдонуу үчүн баш тартты"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Түзмөктү ишкана башкарат"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын көрүү үчүн таптап коюңуз."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Түзмөгүңүз тазаланат"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Колдонмого таркатма аяктагандан кийин калган кадалган таркатмаларды жөнөтүүгө уруксат берет. Ашыкча колдонуу Android TV түзмөгүңүздүн өтө көп эстутумду пайдалануу менен жай же туруксуз иштешине алып келиши мүмкүн."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Колдонмого берүү токтогондон кийин улантыла берүүчү жабышкак берүүлөрдү жөнөтүү уруксатын берет. Муну ашыкча колдонуу, эстутумду өтө көп пайдаланууга алып келип, телефондун жай же туруксуз иштөөсүнүнө себепкер болушу мүмкүн."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"байланыштарыңызды окуу"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Колдонмо планшетиңиздеги байланыштар, ошондой эле белгилүү бир адамдар менен канчалык көп чалышып, кат жазышып жана башка жолдор менен байланышып жаткандыгыңыз тууралуу маалыматты көрүп, сактай алат. Мындай уруксатты алган колдонмолор байланыштар тууралуу маалыматты сактай алышат, ал эми зыянкеч программалар байланыштар тууралуу маалыматты алдын ала эскертүүсүз бөлүшүүсү мүмкүн."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Колдонмого Android TV түзмөгүңүздө сакталган байланыштар тууралуу дайындарды, анын ичинде белгилүү бир байланыштарга канча убакытта бир чалып, электрондук кат жөнөтүп же башка ыкмалар менен баарлашканыңызды өзгөртүүгө колдонмого уруксат берет. Бул уруксат колдонмолорго байланыштарыңыздын дайындарын сактоого уруксат берет жана зыянкеч колдонмолор ал дайындарды сизге кабарлабастан башкалар менен бөлүшүүсү мүмкүн."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Колдонмо түзмөктөгү байланыштар, ошондой эле белгилүү бир адамдар менен канчалык көп чалышып, кат жазышып жана башка жолдор менен байланышып жаткандыгыңыз тууралуу маалыматты көрүп, сактай алат. Зыянкеч программалар байланыштар тууралуу маалыматты алдын ала эскертүүсүз бөлүшүүсү мүмкүн."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Колдонмо планшетиңизде сакталган байланыштардын дайын-даректерин көрө алат. Мындан тышкары, колдонмолорго планшетиңиздеги байланыштар камтылган аккаунттар да жеткиликтүү болот. Алардын катарына сиз орноткон колдонмолордо түзүлгөн аккаунттар кириши мүмкүн. Байланыштардын чоо-жайын сактоо мүмкүнчүлүгүнө ээ болгон зыянкеч программалар ал маалыматты сиздин уруксатыңызсыз башкаларга бериши мүмкүн."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Колдонмо Android TV түзмөгүңүздө сакталган байланыштардын дайын-даректерин көрө алат. Мындан тышкары, колдонмолорго Android TV түзмөгүңүздөгү байланыштар камтылган аккаунттар да жеткиликтүү болот. Алардын катарына сиз орноткон колдонмолордо түзүлгөн аккаунттар кириши мүмкүн. Байланыштардын чоо-жайын сактоо мүмкүнчүлүгүнө ээ болгон зыянкеч программалар ал маалыматты сиздин уруксатыңызсыз башкаларга бериши мүмкүн."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Колдонмо телефонуңузда сакталган байланыштардын дайын-даректерин көрө алат. Мындан тышкары, колдонмолорго телефонуңуздагы байланыштар камтылган аккаунттар да жеткиликтүү болот. Алардын катарына сиз орноткон колдонмолордо түзүлгөн аккаунттар кириши мүмкүн. Байланыштардын чоо-жайын сактоо мүмкүнчүлүгүнө ээ болгон зыянкеч программалар ал маалыматты сиздин уруксатыңызсыз башкаларга бериши мүмкүн."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"байланыштарыңызды өзгөртүү"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Колдонмо планшетиңиздеги байланыштар, ошондой эле белгилүү бир адамдар менен канчалык көп чалышып, кат жазышып жана башка жолдор менен байланышып жаткандыгыңыз тууралуу маалыматты өзгөртө алат. Мындай уруксатты алган колдонмолор байланыштар тууралуу маалыматты жок кыла алышат."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Android TV түзмөгүңүздө сакталган байланыштар тууралуу дайындарды, анын ичинде белгилүү бир байланыштарга канча убакытта бир чалып, электрондук кат жөнөтүп же башка ыкмалар менен баарлашканыңызды өзгөртүүгө колдонмого уруксат берет. Бул уруксат колдонмолорго байланыштардын дайындарын өчүрүүгө уруксат берет."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Колдонмо телефонуңуздагы байланыштар, ошондой эле белгилүү бир адамдар менен канчалык көп чалышып, кат жазышып жана башка жолдор менен байланышып жаткандыгыңыз тууралуу маалыматты өзгөртө алат. Мындай уруксатты алган колдонмолор байланыштар тууралуу маалыматты жок кыла алышат."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Колдонмо планшетиңизде сакталган байланыштарыңыздын дайын-даректерин өзгөртө алат. Мындан тышкары, бул уруксат колдонмолорго байланыштардын дайын-даректерин өчүрүүгө уруксат берет."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Колдонмо Android TV түзмөгүңүздө сакталган байланыштарыңыздын дайын-даректерин өзгөртө алат. Мындан тышкары, бул уруксат колдонмолорго байланыштардын дайын-даректерин өчүрүүгө уруксат берет."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Колдонмо телефонуңузда сакталган байланыштарыңыздын дайын-даректерин өзгөртө алат. Мындан тышкары, бул уруксат колдонмолорго байланыштардын дайын-даректерин өчүрүүгө уруксат берет."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"чалуулар тизмегин окуу"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Бул колдонмо чалууларыңыздын таржымалын окуй алат."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"чалуулар тизмегин жаздыруу"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"жайгашкан жерди аныктагычтын кошумча буйруктарын пайдалануу"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Колдонмого жайгашкан жерди табуучу кошумча жабдуучулардын буйруктарын колдонуу мүмкүнчүлүгүн берет. Ушуну менен колдонмо GPS\'тин ишине жана башка жайгашкан жерлерди аныктоо кызматтарына кийлигише алат."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"так аныкталган жайгашкан жерге активдүү режимде гана кирүүгө уруксат берүү"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Бул колдонмонун активдүү режимде гана жайгашкан жериңиздин дайындарын алууга мүмкүнчүлүгү бар. Колдонмо бул кызматтарды пайдаланышы үчүн аларды күйгүзүп, телефонуңузга туташтырып коюшуңуз керек. Ушуну менен батареянын кубаты көбүрөөк сарпталышы мүмкүн."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"болжолдуу аныкталган жайгашкан жерге (тармактын негизинде) автивдүү режимде гана кирүүгө уруксат берүү"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Бул колдонмо байланыш мунаралары жана Wi-Fi сыяктуу тармактык булактар аркылуу жайгашкан жериңизди аныктай алат, бирок ал үчүн колдонмо ачылып турушу керек. Колдонмо бул кызматтарды пайдаланышы үчүн, аларды күйгүзүп, планшетиңизге туташтырып коюшуңуз керек."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Бул колдонмо байланыш мунаралары жана Wi-Fi сыяктуу тармактык булактар аркылуу жайгашкан жериңизди аныктай алат, бирок ал үчүн колдонмо ачылып турушу керек. Колдонмо бул кызматтарды пайдаланышы үчүн жайгашкан жерди аныктоо кызматтарын күйгүзүп, Android TV түзмөгүңүзгө туташтырып коюшуңуз керек."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Бул колдонмо байланыш мунаралары жана Wi-Fi сыяктуу тармактык булактар аркылуу жайгашкан жериңизди аныктай алат, бирок ал үчүн колдонмо ачылып турушу керек. Колдонмо бул кызматтарды пайдаланышы үчүн, аларды күйгүзүп, телефонуңузга туташтырып коюшуңуз керек."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Колдонмо кайда жүргөнүңүздү активдүү режимде гана аныктай алат. Ал үчүн түзмөгүңүздө жайгашкан жерди аныктоо кызматын иштетип, колдонмого кайда жүргөнүңүздү аныктоого уруксат беришиңиз керек. Батареяңыз тез отуруп калышы мүмкүн."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"кайда жүргөнүмдү активдүү режимде божомолдоого уруксат берүү"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Колдонмо кайда жүргөнүңүздү активдүү режимде гана аныктай алат. Ал үчүн түзмөгүңүздө жайгашкан жерди аныктоо кызматын иштетип, колдонмого кайда жүргөнүңүздү аныктоого уруксат беришиңиз керек."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"жайгашкан жерди фондо аныктоо"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Эгер колдонмого жайгашкан жерди болжолдуу же так аныктоого уруксат берилсе, ал фондо иштеп жатып эле жайгашкан жерди аныктап турат."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Колдонмо кайда жүргөнүңүздү активдүү режимде гана эмес, фондук режимде да аныктай алат."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио жөндөөлөрүңүздү өзгөртүңүз"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Колдонмого үн деңгээли жана кайсы динамик аркылуу үн чыгарылышы керек сыяктуу түзмөктүн аудио тууралоолорун өзгөртүүгө уруксат берет."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"аудио жаздыруу"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Колдонмого планшеттин Bluetooth конфигурацияларын көрүү, жупталган түзмөктөр менен байланыш түзүү жана кабыл алуу уруксатын берет."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Android TV түзмөгүңүздөгү Bluetooth конфигурациясын көрүп, жупташтырылган түзмөктөргө туташууга жана туташуу сурамын кабыл алууга колдонмого уруксат берет."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Колдонмого телефондун Bluetooth конфигурацияларын көрүү, жупташкан түзмөктөр менен туташуу түзүү жана кабыл алуу уруксатын берет."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communication көзөмөлү"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Колдонмого Жакынкы аралыкта байланышуу (NFC) белгилери, карталары жана окугучтары менен байланышуу мүмкүнчүлүгүн берет."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"экранды бөгөттөөнү өчүрүү"</string>
@@ -605,8 +606,8 @@
<string name="permdesc_register_call_provider" msgid="4201429251459068613">"Колдонмого жаңы телеком туташууларын каттоо мүмкүнчүлүгүн берет."</string>
<string name="permlab_connection_manager" msgid="3179365584691166915">"телеком туташууларын башкаруу"</string>
<string name="permdesc_connection_manager" msgid="1426093604238937733">"Колдонмого телеком туташууларын башкаруу мүмкүнчүлүгүн берет."</string>
- <string name="permlab_bind_incall_service" msgid="5990625112603493016">"чалуу экраны менен байланыштыруу"</string>
- <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Колдонмого чалуу экраны качан жана кандай көрүнө тургандыгын башкаруу мүмкүнчүлүгүн берет."</string>
+ <string name="permlab_bind_incall_service" msgid="5990625112603493016">"сүйлөшүп жатканда экранды башкара аласыз"</string>
+ <string name="permdesc_bind_incall_service" msgid="4124917526967765162">"Сүйлөшүп жатканда экранды башкара аласыз."</string>
<string name="permlab_bind_connection_service" msgid="5409268245525024736">"телефония кызматтары"</string>
<string name="permdesc_bind_connection_service" msgid="6261796725253264518">"Колдонмого чалууларды жасоо/кабыл алуу үчүн телефония кызматтары менен байланышууга уруксат берет."</string>
<string name="permlab_control_incall_experience" msgid="6436863486094352987">"чалуу ичиндеги колдонуучу тажрыйбасын камсыз кылуу"</string>
@@ -827,7 +828,7 @@
<string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Токтотуу"</string>
<string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Артка түрүү"</string>
<string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Алдыга түрүү"</string>
- <string name="emergency_calls_only" msgid="3057351206678279851">"Өзгөчө кырдаалда гана чалууга болот"</string>
+ <string name="emergency_calls_only" msgid="3057351206678279851">"Кырсыктаганда гана чалуу"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Тармак кулпуланган"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"SIM-карта PUK-бөгөттө."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="5307979043730860995">"Колдонуучунун нускамасын караңыз же Кардарларды тейлөө борборуна кайрылыңыз."</string>
@@ -1757,8 +1758,8 @@
<string name="package_updated_device_owner" msgid="7560272363805506941">"Администраторуңуз жаңыртып койгон"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Администраторуңуз жок кылып салган"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ЖАРАЙТ"</string>
- <string name="battery_saver_description_with_learn_more" msgid="1817385558636532621">"Батареянын кубатынын мөөнөтүн узартуу үчүн Батареяны үнөмдөгүч режими төмөнкүлөрдү аткарат:\n·Түнкү режимди күйгүзөт\n·Фондогу аракеттерди, айрым визуалдык эффекттерди жана \"Окей Google\" сыяктуу башка функцияларды өчүрөт же чектейт\n\n"<annotation id="url">"Кеңири маалымат"</annotation></string>
- <string name="battery_saver_description" msgid="7618492104632328184">"Батареянын кубатынын мөөнөтүн узартуу үчүн Батареяны үнөмдөгүч режими төмөнкүлөрдү аткарат:\n·Түнкү режимди күйгүзөт\n·Фондогу аракеттерди, айрым визуалдык эффекттерди жана \"Окей Google\" сыяктуу башка функцияларды өчүрөт же чектейт"</string>
+ <string name="battery_saver_description_with_learn_more" msgid="1817385558636532621">"Батареяны үнөмдөө үчүн Батареяны үнөмдөгүч:\n·Караңгы теманы күйгүзөт\n·Фондогу аракеттерди, айрым визуалдык эффекттерди жана \"Окей Google\" сыяктуу башка функцияларды өчүрөт же чектейт\n\n"<annotation id="url">"Кеңири маалымат"</annotation></string>
+ <string name="battery_saver_description" msgid="7618492104632328184">"Батареяны үнөмдөө үчүн Батареяны үнөмдөгүч режими:\n·Караңгы теманы күйгүзөт\n·Фондогу аракеттерди, айрым визуалдык эффекттерди жана \"Окей Google\" сыяктуу башка функцияларды өчүрөт же чектейт"</string>
<string name="data_saver_description" msgid="4995164271550590517">"Трафикти үнөмдөө режиминде айрым колдонмолор дайындарды фондо өткөрө алышпайт. Учурда сиз пайдаланып жаткан колдонмо дайындарды жөнөтүп/ала алат, бирок адаттагыдан азыраак өткөргөндүктөн, анын айрым функциялары талаптагыдай иштебей коюшу мүмкүн. Мисалы, сүрөттөр басылмайынча жүктөлбөйт."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Трафикти үнөмдөө режимин иштетесизби?"</string>
<string name="data_saver_enable_button" msgid="4399405762586419726">"Күйгүзүү"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> менен туташты"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Файлдарды көрүү үчүн таптап коюңуз"</string>
<string name="pin_target" msgid="8036028973110156895">"Кадоо"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Кадоодон алып коюу"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Колдонмо тууралуу"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Демо режим башталууда…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" кызматындагы төмөнкүлөр жаңыртылсынбы: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> жана <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Сактоо"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Жок, рахмат"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Азыр эмес"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Эч качан"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Жаңыртуу"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Улантуу"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"сырсөз"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Экранды бөлүүнү күйгүзүү же өчүрүү"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Кулпуланган экран"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Скриншот"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу Калкыма терезеде көрүндү."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунун маалымат тилкеси."</string>
</resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index e0da206..45e7c44d 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"ບໍ່ມີແອັບຜູ້ເບິ່ງແຍງລະບົບໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ ຫຼື ເສຍຫາຍ. ຜົນກໍຄື, ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ ແລະ ຂໍ້ມູນທີ່ກ່ຽວຂ້ອງຂອງທ່ານຖືກລຶບອອກແລ້ວ. ໃຫ້ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບສຳລັບການຊ່ວຍເຫຼືອ."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກຂອງທ່ານບໍ່ສາມາດໃຊ້ໄດ້ໃນອຸປະກອນນີ້ອີກຕໍ່ໄປ"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ລອງໃສ່ລະຫັດຜ່ານຫຼາຍເທື່ອເກີນໄປ"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"ອຸປະກອນທີ່ຍົກເລີກແລ້ວສຳລັບການໃຊ້ສ່ວນບຸກຄົນ"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"ອຸປະກອນມີການຈັດການ"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"ອົງກອນຂອງທ່ານຈັດການອຸປະກອນນີ້ ແລະ ອາດກວດສອບທຣາບຟິກເຄືອຂ່າຍນຳ. ແຕະເພື່ອເບິ່ງລາຍລະອຽດ."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"ອຸປະກອນຂອງທ່ານຈະຖືກລຶບ"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ອະນຸຍາດໃຫ້ແອັບສົ່ງການອອກອາກາດແບບຍຶດຕິດ, ເຊິ່ງຍັງຄົງຢູ່ຫຼັງຈາກການອອກອາກາດສິ້ນສຸດ. ການໃຊ້ເກີນອາດຈະເຮັດໃຫ້ອຸປະກອນ Android TV ຂອງທ່ານຊ້າລົງ ຫຼື ບໍ່ໝັ້ນຄົງເນື່ອງຈາກມັນໃຊ້ໜ່ວຍຄວາມຈຳຫຼາຍເກີນໄປ."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນສົ່ງການກະຈາຍສັນຍານແບບຍຶດຕິດ, ທີ່ຍັງຄົງເຫຼືອຫຼັງຈາກການກະຈາຍສັນຍານສິ້ນສຸດລົງ. ການນຳໃຊ້ແບບມະຫາສານອາດເຮັດໃຫ້ໂທລະສັບຊ້າ ຫຼືບໍ່ສະຖຽນ ໂດຍການໃຊ້ໜ່ວຍຄວາມຈຳຫຼາຍເກີນໄປ."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ອ່ານລາຍຊື່ຜູ່ຕິດຕໍ່ຂອງທ່ານ"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"ອະນຸຍາດໃຫ້ແອັບຯອ່ານຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ່ຕິດຕໍ່ໃນແທັບເລັດຂອງທ່ານ, ຮວມເຖິງຂໍ້ມູນການຈຳນວນການຕິດຕໍ່ຕ່າງໆເຊັ່ນ: ການໂທ, ອີເມວ, ຫຼືຕິດຕໍ່ຫາໃນທາງອື່ນໆກັບບຸກຄົນໃດນຶ່ງໄດ້. ການອະນຸຍາດນີ້ເຮັດໃຫ້ແອັບຯ ສາມາດບັນທຶກຂໍ້ມູນຜູ່ຕິດຕໍ່ຂອງທ່ານ ແລະແອັບຯທີ່ເປັນອັນຕະລາຍ ອາດສົ່ງຕໍ່ຂໍ້ມູນເຫຼົ່ານັ້ນໂດຍທີ່ທ່ານບໍ່ຮູ້ໂຕ."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"ອະນຸຍາດໃຫ້ແອັບອ່ານຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ້ຕິດຕໍ່ຂອງທ່ານທີ່ບັນທຶກໄວ້ຢູ່ໃນອຸປະກອນ Android TV ຂອງທ່ານ, ຮວມທັງຄວາມຖີ່ທີ່ທ່ານໄດ້ໂທ, ອີເມວ ຫຼື ສື່ສານກັບບຸກຄົນສະເພາະດ້ວຍວິທີອື່ນ. ສິດອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບຕ່າງໆສາມາດບັນທຶກຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ຂອງທ່ານ ແລະ ແອັບທີ່ເປັນອັນຕະລາຍອາດສາມາດແບ່ງປັນຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ໂດຍທີ່ທ່ານບໍ່ຮູ້ໄດ້."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"ອະນຸຍາດໃຫ້ແອັບຯ ອ່ານຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ່ຕິດຕໍ່ທີ່ເກັບໄວ້ໃນໂທລະສັບຂອງທ່ານ ຮວມເຖິງຄວາມຖີ່ການໂທ, ການສົ່ງສົ່ງອີເມວ ຫຼືການສື່ສານໃນຮູບແບບອື່ນກັບບຸກຄົນໃດນຶ່ງ. ການອະນຸຍາດເຮັດໃຫ້ແອັບຯ ສາມາດບັນທຶກຂໍ້ມູນລາຍຊື່ຜູ່ຕິດຕໍ່ຂອງທ່ານ ແລະແອັບຯທີ່ເປັນອັນຕະລາຍ ອາດເຜີຍແຜ່ຂໍ້ມູນຂອງທ່ານໂດຍທີ່ທ່ານບໍ່ໄດ້ຮັບຮູ້."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"ອະນຸຍາດໃຫ້ແອັບອ່ານຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ້ຕິດຕໍ່ທີ່ບັນທຶກໄວ້ຢູ່ແທັບເລັດຂອງທ່ານ. ນອກຈາກນັ້ນ, ແອັບຕ່າງໆຍັງມີສິດເຂົ້າເຖິງບັນຊີຢູ່ແທັບເລັດຂອງທ່ານທີ່ສ້າງລາຍຊື່ຜູ້ຕິດຕໍ່ໄດ້ນຳ. ນີ້ອາດຮວມເຖິງບັນຊີທີ່ສ້າງຂຶ້ນມາໂດຍແອັບທີ່ທ່ານຕິດຕັ້ງໄວ້ນຳ. ສິດອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບຕ່າງໆສາມາດບັນທຶກຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ຂອງທ່ານ ແລະ ແອັບທີ່ເປັນອັນຕະລາຍອາດແບ່ງປັນຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ໄດ້ໂດຍທີ່ທ່ານບໍ່ຮູ້ໄດ້."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"ອະນຸຍາດໃຫ້ແອັບອ່ານຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ້ຕິດຕໍ່ທີ່ຈັດເກັບໄວ້ຢູ່ອຸປະກອນ Android TV ຂອງທ່ານ. ນອກຈາກນັ້ນ, ແອັບຕ່າງໆຍັງມີສິດເຂົ້າເຖິງບັນຊີຢູ່ອຸປະກອນ Android TV ຂອງທ່ານທີ່ສ້າງລາຍຊື່ຜູ້ຕິດຕໍ່ໄດ້ນຳ. ນີ້ອາດຮວມເຖິງບັນຊີທີ່ສ້າງຂຶ້ນມາໂດຍແອັບທີ່ທ່ານຕິດຕັ້ງໄວ້ນຳ. ສິດອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບຕ່າງໆສາມາດບັນທຶກຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ຂອງທ່ານ ແລະ ແອັບທີ່ເປັນອັນຕະລາຍອາດແບ່ງປັນຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ໄດ້ໂດຍທີ່ທ່ານບໍ່ຮູ້ໄດ້."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ອະນຸຍາດໃຫ້ແອັບອ່ານຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ້ຕິດຕໍ່ທີ່ບັນທຶກໄວ້ຢູ່ໂທລະສັບຂອງທ່ານ. ນອກຈາກນັ້ນ, ແອັບຕ່າງໆຍັງມີສິດເຂົ້າເຖິງບັນຊີຢູ່ໂທລະສັບຂອງທ່ານທີ່ສ້າງລາຍຊື່ຜູ້ຕິດຕໍ່ໄດ້ນຳ. ນີ້ອາດຮວມເຖິງບັນຊີທີ່ສ້າງຂຶ້ນມາໂດຍແອັບທີ່ທ່ານຕິດຕັ້ງໄວ້ນຳ. ສິດອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບຕ່າງໆສາມາດບັນທຶກຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ຂອງທ່ານ ແລະ ແອັບທີ່ເປັນອັນຕະລາຍອາດແບ່ງປັນຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ໄດ້ໂດຍທີ່ທ່ານບໍ່ຮູ້ໄດ້."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ແກ້ໄຂລາຍຊື່ຜູ່ຕິດຕໍ່ຂອງທ່ານ"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"ອະນຸຍາດໃຫ້ແອັບຯ ແກ້ໄຂຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ່ຕິດຕໍ່ຂອງທ່ານທີ່ເກັບໄວ້ໃນແທັບເລັດ ຮວມທັງຄວາມຖີ່ໃນການໂທ, ການສົ່ງອີເມວ ຫຼືການສື່ສານໃນຮູບແບບອື່ນຂອງທ່ານກັບລາຍຊື່ຜູ່ຕິດຕໍ່ໃດນຶ່ງ. ການກຳນົດສິດນີ້ເຮັດໃຫ້ແອັບຯສາມາດລຶບຂໍ້ມູນລາຍຊື່ຜູ່ຕິດຕໍ່ໄດ້."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"ອະນຸຍາດໃຫ້ແອັບແກ້ໄຂຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຕິດຕໍ່ຂອງທ່ານທີ່ບັນທຶກຢູ່ໃນອຸປະກອນ Android TV ທ່ານ, ຮວມທັງຄວາມຖີ່ ທີທ່ານໂທ, ອີເມວ ຫຼື ການສື່ ສານກັບລາຍຊື່ຕິດຕໍ່ສະເພາະໃນຮູບແບບອື່ນນຳ. ສິດອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບສາມາດລຶບຂໍ້ມູນລາຍຊື່ຕິດຕໍ່ໄດ້."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"ອະນຸຍາດໃຫ້ແອັບຯແກ້ໄຂຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ່ຕິດຕໍ່ ທີ່ບັນທຶກໃນໂທລະສັບຂອງທ່ານ ຮວມທັງຄວາມຖີ່ຂອງການໂທ, ການອີເມວ ຫຼືການຕິດຕໍ່ໃນຮູບແບບອື່ນກັບລາຍຊື່ຜູ່ຕິດຕໍ່ໃດນຶ່ງນຳ. ການອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບຯ ສາມາດລຶບຂໍ້ມູນລາຍຊື່ຜູ່ຕິດຕໍ່ໄດ້."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"ອະນຸຍາດໃຫ້ແອັບແກ້ໄຂຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ້ຕິດຕໍ່ທີ່ບັນທຶກໄວ້ຢູ່ແທັບເລັດຂອງທ່ານ. ສິດອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບສາມາດລຶບຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ອອກໄດ້."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"ອະນຸຍາດໃຫ້ແອັບແກ້ໄຂຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຕິດຕໍ່ຂອງທ່ານທີ່ບັນທຶກຢູ່ໃນອຸປະກອນ Android TV ທ່ານ. ສິດອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບສາມາດລຶບຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ອອກໄດ້."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ອະນຸຍາດໃຫ້ແອັບແກ້ໄຂຂໍ້ມູນກ່ຽວກັບລາຍຊື່ຜູ້ຕິດຕໍ່ທີ່ບັນທຶກໄວ້ຢູ່ໂທລະສັບຂອງທ່ານ. ສິດອະນຸຍາດນີ້ຈະເຮັດໃຫ້ແອັບສາມາດລຶບຂໍ້ມູນລາຍຊື່ຜູ້ຕິດຕໍ່ອອກໄດ້."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ອ່ານບັນທຶກການໂທ"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"ຂຽນຂໍ້ມູນການໂທ"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ເຂົ້າເຖິງຄຳສັ່ງຜູ່ໃຫ້ບໍລິການພິກັດສະຖານທີ່"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ອະນຸຍາດໃຫ້ແອັບຯເຂົ້າເຖິງຄຳສັ່ງເພີ່ມເຕີມຂອງຜູ່ໃຫ້ບໍລິການສະຖານທີ່. ນີ້ອາດຈະເປັນການເຮັດໃຫ້ແອັບຯ ລົບກວນການເຮັດວຽກຂອງ GPS ຫຼືແຫລ່ງຂໍ້ມູນສະຖານທີ່ອື່ນໆໄດ້."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ເຂົ້າເຖິງສະຖານທີ່ແນ່ນອນໃນພື້ນໜ້າເທົ່ານັ້ນ"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"ແອັບນີ້ສາມາດຮັບເອົາສະຖານທີ່ແນ່ນອນຂອງທ່ານໄດ້ທຸກເວລາທີ່ມັນຢູ່ໃນພື້ນໜ້າ. ການບໍລິການສະຖານທີ່ເຫຼົ່ານີ້ຕ້ອງເປີດຢູ່ ແລະ ສາມາດໃຊ້ໄດ້ໃນໂທລະສັບຂອງທ່ານເພື່ອໃຫ້ແອັບສາມາດໃຊ້ພວກມັນໄດ້. ນີ້ອາດຈະເຮັດໃຫ້ການໃຊ້ແບັດເຕີຣີເພີ່ມຂຶ້ນ."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"ເຂົ້າເຖິງສະຖານທີ່ໂດຍປະມານ (ອ້າງອີງຈາກເຄືອຂ່າຍ) ສະເພາະໃນພື້ນໜ້າ"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ແອັບນີ້ສາມາດເບິ່ງສະຖານທີ່ຂອງທ່ານໂດຍອ້າງອີງຈາກແຫລ່ງທີ່ມເຄືອຂ່າຍ ເຊັ່ນ: ເສົາສັນຍານໂທລະສັບ ແລະ ເຄືອຂ່າຍ Wi-Fi ໄດ້, ແຕ່ສະເພາະເມື່ອແອັບເຮັດວຽກໂດຍປາກົດຢູ່ໜ້າເທົ່ານັ້ນ. ບໍລິການສະຖານທີ່ເຫຼົ່ານີ້ຈະຕ້ອງຖືກເປີດໃຊ້ ແລະ ສາມາດໃຊ້ໄດ້ຢູ່ແທັບເລັດຂອງທ່ານ, ແອັບຈຶ່ງຈະສາມາດໃຊ້ພວກມັນໄດ້."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"ແອັບນີ້ສາມາດດຶງຂໍ້ມູນສະຖານທີ່ຂອງທ່ານໂດຍອ້າງອີງແຫລ່ງຂໍ້ມູນເຄືອຂ່າຍ ເຊັ່ນ: ເສົາສັນຍານໂທລະສັບມືຖື ແລະ ເຄືອຂ່າຍ Wi-Fi, ແຕ່ສະເພາະເມື່ອແອັບຢູ່ໃນພື້ນໜ້າເທົ່ານັ້ນ. ບໍລິການສະຖານທີ່ເຫຼົ່ານີ້ຈະຕ້ອງຖືກເປີດໃຊ້ກ່ອນ ແລະ ສາມາດໃຊ້ໄດ້ຢູ່ອຸປະກອນ Android TV ຂອງທ່ານເພື່ອໃຫ້ແອັບສາມາດໃຊ້ພວກມັນໄດ້."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ແອັບນີ້ສາມາດເບິ່ງສະຖານທີ່ຂອງທ່ານໂດຍອ້າງອີງຈາກແຫລ່ງທີ່ມເຄືອຂ່າຍ ເຊັ່ນ: ເສົາສັນຍານໂທລະສັບ ແລະ ເຄືອຂ່າຍ Wi-Fi ໄດ້, ແຕ່ສະເພາະເມື່ອແອັບເຮັດວຽກໂດຍປາກົດຢູ່ໜ້າເທົ່ານັ້ນ. ບໍລິການສະຖານທີ່ເຫຼົ່ານີ້ຈະຕ້ອງຖືກເປີດໃຊ້ ແລະ ສາມາດໃຊ້ໄດ້ຢູ່ໂທລະສັບຂອງທ່ານ, ແອັບຈຶ່ງຈະສາມາດໃຊ້ພວກມັນໄດ້."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"ແອັບນີ້ສາມາດຮັບເອົາສະຖານທີ່ແນ່ນອນຂອງທ່ານໄດ້ທຸກເວລາທີ່ມັນຢູ່ໃນພື້ນໜ້າ. ການບໍລິການສະຖານທີ່ຕ້ອງເປີດຢູ່ ແລະ ສາມາດໃຊ້ໄດ້ໃນອຸປະກອນຂອງທ່ານເພື່ອໃຫ້ແອັບສາມາດໃຊ້ພວກມັນໄດ້. ນີ້ອາດຈະເຮັດໃຫ້ການໃຊ້ແບັດເຕີຣີເພີ່ມຂຶ້ນ."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"ເຂົ້າເຖິງສະຖານທີ່ໂດຍປະມານເມື່ອຢູ່ໃນພື້ນໜ້າເທົ່ານັ້ນ"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"ແອັບນີ້ສາມາດລະບຸສະຖານທີ່ໂດຍປະມານການຂອງທ່ານໄດ້ສະເພາະເມື່ອມັນຢູ່ໃນພື້ນຫຼັງເທົ່ານັ້ນ. ຈະຕ້ອງເປີດໃຊ້ບໍລິການສະຖານທີ່ ແລະ ໃຊ້ໄດ້ຢູ່ລົດຂອງທ່ານເພື່ອໃຫ້ແອັບສາມາດໃຊ້ພວກມັນໄດ້."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"ເຂົ້າເຖິງສະຖານທີ່ໃນພື້ນຫຼັງ"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ຖ້າອະນຸຍາດສິ່ງນີ້ນອກຈາກການເຂົ້າເຖິງສະຖານທີ່ໂດຍປະມານ ຫຼື ທີ່ແນ່ນອນ ແອັບສາມາດເຂົ້າເຖິງສະຖານທີ່ໄດ້ໃນຂະນະທີ່ເປີດໃຊ້ໃນພື້ນຫຼັງ."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"ແອັບນີ້ສາມາດເຂົ້າເຖິງສະຖານທີ່ໃນເວລາເຮັດວຽກໃນພື້ນຫຼັງ, ນອກເໜືອໄປຈາກການເຂົ້າເຖິງສະຖານທີ່ໃນພື້ນໜ້າ."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ປ່ຽນການຕັ້ງຄ່າສຽງຂອງທ່ານ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ອະນຸຍາດໃຫ້ແອັບຯແກ້ໄຂການຕັ້ງຄ່າສຽງສ່ວນກາງ ເຊັ່ນ: ລະດັບສຽງ ແລະລຳໂພງໃດທີ່ຖືກໃຊ້ສົ່ງສຽງອອກ."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ບັນທຶກສຽງ"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ອະນຸຍາດໃຫ້ແອັບຯເບິ່ງການຕັ້ງຄ່າຂອງ Bluetooth ໃນແທັບເລັດ ຕະຫຼອດຈົນເຊື່ອມຕໍ່ ແລະຍອມຮັບການເຊື່ອມຕໍ່ກັບອຸປະກອນທີ່ຈັບຄູ່ກັນແລ້ວ."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ອະນຸຍາດໃຫ້ແອັບເບິ່ງການຕັ້ງຄ່າ Bluetooth ຢູ່ອຸປະກອນ Android TV ຂອງທ່ານ ແລະ ຕອບຮັບການເຊື່ອມຕໍ່ກັບອຸປະກອນທີ່ຈັບຄູ່ໄວ້."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ອະນຸຍາດໃຫ້ແອັບຯເບິ່ງການຕັ້ງຄ່າຂອງ Bluetooth ໃນໂທລະສັບ, ຮວມທັງໃຫ້ສ້າງ ແລະຮັບການເຊື່ອມຕໍ່ຈາກອຸປະກອນທີ່ຈັບຄູ່ກັນ."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ຄວບຄຸມ Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ອະນຸຍາດໃຫ້ແອັບຯຕິດຕໍ່ສື່ສານກັບປ້າຍກຳກັບ, ບັດ ແລະໂຕອ່ານຂອງການສື່ສານໄລຍະສັ້ນ (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ປິດການລັອກໜ້າຈໍ"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"ເຊື່ອມຕໍ່ກັບ <xliff:g id="PRODUCT_NAME">%1$s</xliff:g> ແລ້ວ"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ແຕະເພື່ອເບິ່ງໄຟລ໌"</string>
<string name="pin_target" msgid="8036028973110156895">"ປັກໝຸດ"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"ຖອນປັກໝຸດ"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ຂໍ້ມູນແອັບ"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ກຳລັງເລີ່ມເດໂມ…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"ອັບເດດລາຍການເຫຼົ່ານີ້ໃນ "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ແລະ <xliff:g id="TYPE_2">%3$s</xliff:g> ບໍ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"ບັນທຶກ"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"ບໍ່, ຂອບໃຈ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ບໍ່ຟ້າວເທື່ອ"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ຢ່າບັນທຶກ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"ອັບເດດ"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ສືບຕໍ່"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"ລະຫັດຜ່ານ"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"ເປີດ/ປິດການແບ່ງໜ້າຈໍ"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"ໜ້າຈໍລັອກ"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ຮູບໜ້າຈໍ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"ແອັບ <xliff:g id="APP_NAME">%1$s</xliff:g> ໃນໜ້າຈໍປັອບອັບ."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"ແຖບຄຳບັນຍາຍຂອງ <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 1212aa4..75df18c 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Trūksta darbo profilio administratoriaus programos arba ji sugadinta. Todėl darbo profilis ir susiję duomenys buvo ištrinti. Jei reikia pagalbos, susisiekite su administratoriumi."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Darbo profilis nebepasiekiamas šiame įrenginyje"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Per daug slaptažodžio bandymų"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratorius atmetė prašymą įrenginį naudoti asmeniniais tikslais"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Įrenginys yra tvarkomas"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Šį įrenginį tvarko organizacija ir gali stebėti tinklo srautą. Palieskite, kad gautumėte daugiau informacijos."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Įrenginys bus ištrintas"</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Programai leidžiama siųsti užsifiksuojančias transliacijas, kurios išlieka pasibaigus transliacijai. Per dažnas jų naudojimas gali sulėtinti „Android TV“ įrenginio veikimą ar padaryti jį nestabilų verčiant naudoti per daug atminties."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Leidžiama programai siųsti užsifiksuojančias transliacijas, kurios išlieka pasibaigus transliacijai. Per dažnas jų naudojimas gali sulėtinti telefono veikimą ar padaryti jį nestabilų verčiant naudoti per daug atminties."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"skaityti kontaktus"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Leidžiama programai skaityti duomenis apie planšetiniame kompiuteryje saugomus kontaktus, įskaitant dažnį, kuriuo konkretiems asmenims skambinote, siuntėte el. laiškus ar bendravote kitais būdais. Šis leidimas suteikia teisę programoms saugoti kontaktinius duomenis, o kenkėjiškos programos gali bendrinti kontaktinius duomenis be jūsų žinios."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Programai leidžiama skaityti duomenis apie „Android TV“ įrenginyje saugomus kontaktus, įskaitant dažnį, kuriuo konkretiems asmenims skambinote, siuntėte el. laiškus ar bendravote kitais būdais. Šis leidimas suteikia teisę programoms saugoti kontaktinius duomenis, o kenkėjiškos programos gali bendrinti kontaktinius duomenis be jūsų žinios."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Leidžiama programai skaityti duomenis apie telefone saugomus kontaktus, įskaitant dažnį, kuriuo konkretiems asmenims skambinote, siuntėte el. laiškus ar bendravote kitais būdais. Šis leidimas suteikia teisę programoms saugoti kontaktinius duomenis, o kenkėjiškos programos gali bendrinti kontaktinius duomenis be jūsų žinios."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Programai leidžiama skaityti duomenis apie planšetiniame kompiuteryje saugomus kontaktus. Programos taip pat galės pasiekti planšetiniame kompiuteryje esančias paskyras, kuriose buvo sukurta kontaktų. Gali būti įtrauktos paskyros, kurias sukūrė jūsų įdiegtos programos. Šis leidimas suteikia teisę programoms saugoti kontaktinius duomenis, o kenkėjiškos programos gali bendrinti kontaktinius duomenis be jūsų žinios."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Programai leidžiama skaityti duomenis apie „Android TV“ įrenginyje saugomus kontaktus. Programos taip pat galės pasiekti „Android TV“ įrenginyje esančias paskyras, kuriose buvo sukurta kontaktų. Gali būti įtrauktos paskyros, kurias sukūrė jūsų įdiegtos programos. Šis leidimas suteikia teisę programoms saugoti kontaktinius duomenis, o kenkėjiškos programos gali bendrinti kontaktinius duomenis be jūsų žinios."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Programai leidžiama skaityti duomenis apie telefone saugomus kontaktus. Programos taip pat galės pasiekti telefone esančias paskyras, kuriose buvo sukurta kontaktų. Gali būti įtrauktos paskyros, kurias sukūrė jūsų įdiegtos programos. Šis leidimas suteikia teisę programoms saugoti kontaktinius duomenis, o kenkėjiškos programos gali bendrinti kontaktinius duomenis be jūsų žinios."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"keisti kontaktus"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Leidžiama programai keisti duomenis apie planšetiniame kompiuteryje saugomus kontaktus, įskaitant dažnį, kuriuo konkretiems asmenims skambinote, siuntėte el. laiškus ar bendravote kitais būdais. Šis leidimas suteikia teisę programoms ištrinti kontaktinius duomenis."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Leidžiama programai keisti duomenis apie „Android TV“ įrenginyje saugomus kontaktus, įskaitant dažnį, kuriuo konkretiems asmenims skambinote, siuntėte el. laiškus ar bendravote kitais būdais. Šis leidimas suteikia teisę programoms ištrinti kontaktinius duomenis."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Leidžiama programai keisti duomenis apie telefone saugomus kontaktus, įskaitant dažnį, kuriuo konkretiems asmenims skambinote, siuntėte el. laiškus ar bendravote kitais būdais. Šis leidimas suteikia teisę programoms ištrinti kontaktinius duomenis."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Programai leidžiama keisti duomenis apie planšetiniame kompiuteryje saugomus kontaktus. Su šiuo leidimu programos gali ištrinti kontaktinius duomenis."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Programai leidžiama keisti duomenis apie „Android TV“ įrenginyje saugomus kontaktus. Su šiuo leidimu programos gali ištrinti kontaktinius duomenis."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Programai leidžiama keisti duomenis apie telefone saugomus kontaktus. Su šiuo leidimu programos gali ištrinti kontaktinius duomenis."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"skaityti skambučių žurnalą"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Ši programa gali nuskaityti skambučių istoriją."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"rašyti skambučių žurnalą"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"pasiekti papildomas vietos teikimo įrankio komandas"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Programai leidžiama pasiekti papildomas vietovės nustatymo paslaugų teikėjų komandas. Dėl to programa gali trukdyti veikti GPS ar kitiems vietovės nustatymo šaltiniams."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"pasiekti tikslią vietovę, tik kai programa veikia priekiniame plane"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Ši programa gali gauti tikslius jūsų vietovės duomenis bet kuriuo metu, kai veikia priekiniame plane. Šios vietovės paslaugos turi būti įjungtos ir pasiekiamos telefone, kad programa galėtų jas naudoti. Tai gali padidinti akumuliatoriaus energijos suvartojimą."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"pasiekti apytikslę vietovę (pagal tinklo duomenis), tik kai programa veikia priekiniame plane"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ši programa gali gauti jūsų vietos informaciją naudodamasi tinklo šaltinių, pvz., mobiliojo ryšio bokštų ir „Wi-Fi“ tinklų, duomenimis, bet tik kai ji yra naudojama priekiniame plane. Šios vietovės paslaugos turi būti įjungtos ir pasiekiamos planšetiniame kompiuteryje, kad programa galėtų jas naudoti."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ši programa gali gauti jūsų vietos informaciją naudodamasi tinklo šaltinių, pvz., mobiliojo ryšio bokštų ir „Wi-Fi“ tinklų, duomenimis, bet tik kai ji yra naudojama priekiniame plane. Šios vietovės paslaugos turi būti įjungtos ir pasiekiamos „Android TV“ įrenginyje, kad programa galėtų jas naudoti."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ši programa gali gauti jūsų vietos informaciją naudodamasi tinklo šaltinių, pvz., mobiliojo ryšio bokštų ir „Wi-Fi“ tinklų, duomenimis, bet tik kai ji yra naudojama priekiniame plane. Šios vietovės paslaugos turi būti įjungtos ir pasiekiamos telefone, kad programa galėtų jas naudoti."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ši programa gali gauti tikslius jūsų vietovės duomenis, tik kai veikia priekiniame plane. Vietovės paslaugos turi būti įjungtos ir pasiekiamos įrenginyje, kad programa galėtų jas naudoti. Dėl to akumuliatoriui gali reikėti daugiau energijos."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"pasiekti apytikslę vietovę, tik kai programa veikia priekiniame plane"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ši programa gali gauti apytikslius jūsų vietovės duomenis, tik kai veikia priekiniame plane. Vietovės paslaugos turi būti įjungtos ir pasiekiamos įrenginyje, kad programa galėtų jas naudoti."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"prieiga prie vietovės fone"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Jei papildomai suteikiama prieiga prie apytikslės arba tikslios vietovės, programa gali pasiekti vietovės duomenis veikdama fone."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ši programa gali pasiekti vietovę, kai veikia fone ar priekiniame plane."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"keisti garso nustatymus"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Leidžiama programai keisti visuotinius garso nustatymus, pvz., garsumą ir tai, kuris garsiakalbis naudojamas išvesčiai."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"įrašyti garsą"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Leidžiama programai peržiūrėti „Bluetooth“ konfigūraciją planšetiniame kompiuteryje ir užmegzti bei priimti ryšius iš susietų įrenginių."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Programai leidžiama peržiūrėti „Bluetooth“ konfigūraciją „Android TV“ įrenginyje ir užmegzti bei priimti ryšius iš susietų įrenginių."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Leidžiama programai peržiūrėti „Bluetooth“ konfigūraciją telefone ir užmegzti bei priimti ryšius iš susietų įrenginių."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"valdyti artimo lauko perdavimą (angl. „Near Field Communication“)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Leidžiama programai perduoti artimojo lauko ryšių technologijos (ALR) žymas, korteles ir skaitymo programas."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"išjungti ekrano užraktą"</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Prisijungta prie „<xliff:g id="PRODUCT_NAME">%1$s</xliff:g>“"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Palieskite, kad peržiūrėtumėte failus"</string>
<string name="pin_target" msgid="8036028973110156895">"Prisegti"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Atsegti"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Programos informacija"</string>
<string name="negative_duration" msgid="1938335096972945232">"–<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Paleidžiama demonstracinė versija…"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Atnaujinti šiuos elementus paslaugoje "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ir <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Išsaugoti"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ne, ačiū"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ne dabar"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Niekada"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Atnaujinti"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Tęsti"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"slaptažodį"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Perjungti išskaidyto ekrano režimą"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Užrakinimo ekranas"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Ekrano kopija"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ iššokančiajame lange."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Programos „<xliff:g id="APP_NAME">%1$s</xliff:g>“ antraštės juosta."</string>
</resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 547b6c9..89fd885 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -190,8 +190,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Trūkst darba profila administratora lietotnes, vai šī lietotne ir bojāta. Šī iemesla dēļ jūsu darba profils un saistītie dati tika dzēsti. Lai saņemtu palīdzību, sazinieties ar administratoru."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Jūsu darba profils šai ierīcē vairs nav pieejams."</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Veikts pārāk daudz paroles ievadīšanas mēģinājumu."</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrators atteicās no tādas ierīces pārvaldības, ko var izmantot personiskām vajadzībām"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Ierīce tiek pārvaldīta"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Jūsu organizācija pārvalda šo ierīci un var uzraudzīt tīkla datplūsmu. Pieskarieties, lai saņemtu detalizētu informāciju."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Jūsu ierīces dati tiks dzēsti"</string>
@@ -384,13 +383,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Ļauj lietotnei sūtīt piesaistošas apraides, kas tiek saglabātas pēc apraides pabeigšanas. Pārmērīga izmantošana var palēnināt Android TV ierīces darbību vai padarīt tās darbību nestabilu, liekot izmantot pārāk daudz atmiņas."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Ļauj lietotnei sūtīt piesaistošas apraides, kas tiek saglabātas pēc apraides pabeigšanas. Pārmērīga izmantošana var palēnināt tālruņa darbību vai padarīt tā darbību nestabilu, liekot izmantot pārāk daudz atmiņas."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lasīt kontaktpersonu informāciju"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Ļauj lietotnei lasīt datus par jūsu planšetdatorā saglabātajām kontaktpersonām, tostarp to, cik bieži esat zvanījis, sazinājies pa e-pastu vai citādi sazinājies ar konkrētām personām. Ar šo atļauju lietotnes var saglabāt jūsu kontaktpersonu datus, un ļaunprātīgas lietotnes var kopīgot kontaktpersonu datus bez jūsu atļaujas."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Ļauj lietotnei lasīt datus par Android TV ierīcē glabātajām kontaktpersonām, tostarp par zvanu un e-pasta ziņojumu apjomu vai saziņu citos veidos, kas veikta ar konkrētām kontaktpersonām. Ar šo atļauju lietotnes var saglabāt jūsu kontaktpersonu datus, un ļaunprātīgas lietotnes var kopīgot kontaktpersonu datus, jums nezinot."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Ļauj lietotnei lasīt datus par jūsu tālrunī saglabātajām kontaktpersonām, tostarp to, cik bieži esat zvanījis, sazinājies pa e-pastu vai citādi sazinājies ar konkrētām personām. Ar šo atļauju lietotnes var saglabāt jūsu kontaktpersonu datus, un ļaunprātīgas lietotnes var kopīgot kontaktpersonu datus bez jūsu atļaujas."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Ļauj lietotnei lasīt datus par planšetdatorā glabātajām kontaktpersonām. Lietotnēm būs arī piekļuve kontiem jūsu planšetdatorā, kuros ir izveidotas kontaktpersonas. Tas var ietvert kontus, ko izveidojušas jūsu instalētās lietotnes. Ar šo atļauju lietotnes var saglabāt jūsu kontaktpersonu datus, savukārt ļaunprātīgas lietotnes var kopīgot kontaktpersonu datus, jums nezinot."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Ļauj lietotnei lasīt datus par Android TV ierīcē glabātajām kontaktpersonām. Lietotnēm būs arī piekļuve kontiem jūsu Android TV ierīcē, kuros ir izveidotas kontaktpersonas. Tas var ietvert kontus, ko izveidojušas jūsu instalētās lietotnes. Ar šo atļauju lietotnes var saglabāt jūsu kontaktpersonu datus, savukārt ļaunprātīgas lietotnes var kopīgot kontaktpersonu datus, jums nezinot."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Ļauj lietotnei lasīt datus par tālrunī glabātajām kontaktpersonām. Lietotnēm būs arī piekļuve kontiem jūsu tālrunī, kuros ir izveidotas kontaktpersonas. Tas var ietvert kontus, ko izveidojušas jūsu instalētās lietotnes. Ar šo atļauju lietotnes var saglabāt jūsu kontaktpersonu datus, savukārt ļaunprātīgas lietotnes var kopīgot kontaktpersonu datus, jums nezinot."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"mainīt kontaktpersonu informāciju"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Ļauj lietotnei mainīt datus par planšetdatorā saglabātajām kontaktpersonām, tostarp par zvanu un e-pasta ziņojumu apjomu vai saziņu citos veidos, kas veikta ar konkrētām kontaktpersonām. Ar šo atļauju lietotne var dzēst kontaktpersonu datus."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Ļauj lietotnei mainīt datus par Android TV ierīcē saglabātajām kontaktpersonām, tostarp par zvanu un e-pasta ziņojumu apjomu vai saziņu citos veidos, kas veikta ar konkrētām kontaktpersonām. Ar šo atļauju lietotnes var dzēst kontaktpersonu datus."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Ļauj lietotnei mainīt datus par tālrunī saglabātajām kontaktpersonām, tostarp par zvanu un e-pasta ziņojumu apjomu vai saziņu citos veidos, kas veikta ar konkrētām kontaktpersonām. Ar šo atļauju lietotne var dzēst kontaktpersonu datus."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Ļauj lietotnei mainīt datus par planšetdatorā glabātajām kontaktpersonām. Ar šo atļauju lietotnes var dzēst kontaktpersonu datus."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Ļauj lietotnei mainīt datus par Android TV ierīcē glabātajām kontaktpersonām. Ar šo atļauju lietotnes var dzēst kontaktpersonu datus."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Ļauj lietotnei mainīt datus par tālrunī glabātajām kontaktpersonām. Ar šo atļauju lietotnes var dzēst kontaktpersonu datus."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"nolasīt zvanu žurnālu"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Šī lietotne var lasīt jūsu zvanu vēsturi."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"rakstīt zvanu žurnālā"</string>
@@ -410,13 +409,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"piekļūt atrašanās vietas nodrošinātāja papildu komandām"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Ļauj lietotnei piekļūt papildu atrašanās vietas noteikšanas nodrošinātāju komandām. Tas var ļaut lietotnei traucēt GPS vai citu atrašanās vietas noteikšanas avotu darbību."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"piekļuve precīzai atrašanās vietai, tikai darbojoties priekšplānā"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Šī lietotne var iegūt precīzu jūsu atrašanās vietu, tikai darbojoties priekšplānā. Šiem atrašanās vietu pakalpojumiem ir jābūt ieslēgtiem un pieejamiem jūsu tālrunī, lai lietotne varētu tos izmantot. Tādējādi var palielināties akumulatora jaudas patēriņš."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"piekļuve aptuvenai atrašanās vietai (pēc tīkla datiem), tikai darbojoties priekšplānā"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Kad šī lietotne darbojas priekšplānā, tā var noteikt jūsu atrašanās vietu, izmantojot tīkla resursus, piemēram, mobilo sakaru torņus un Wi-Fi tīklus. Šiem atrašanās vietu pakalpojumiem ir jābūt ieslēgtiem un pieejamiem jūsu planšetdatorā, lai lietotne varētu tos izmantot."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Kad šī lietotne darbojas priekšplānā, tā var noteikt jūsu atrašanās vietu, izmantojot tīkla resursus, piemēram, mobilo sakaru torņus un Wi-Fi tīklus. Šiem atrašanās vietu pakalpojumiem ir jābūt ieslēgtiem un pieejamiem jūsu Android TV ierīcē, lai lietotne varētu tos izmantot."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Kad šī lietotne darbojas priekšplānā, tā var noteikt jūsu atrašanās vietu, izmantojot tīkla resursus, piemēram, mobilo sakaru torņus un Wi-Fi tīklus. Šiem atrašanās vietu pakalpojumiem ir jābūt ieslēgtiem un pieejamiem jūsu tālrunī, lai lietotne varētu tos izmantot."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Šī lietotne var iegūt precīzu jūsu atrašanās vietu, tikai darbojoties priekšplānā. Jūsu ierīcē ir jābūt ieslēgtiem un pieejamiem atrašanās vietu pakalpojumiem, lai lietotne varētu tos izmantot. Tādējādi var palielināties akumulatora jaudas patēriņš."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"piekļuve aptuvenai atrašanās vietai, tikai darbojoties priekšplānā"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Šī lietotne var iegūt aptuvenu jūsu atrašanās vietu, tikai darbojoties priekšplānā. Jūsu ierīcē ir jābūt ieslēgtiem un pieejamiem atrašanās vietu pakalpojumiem, lai lietotne varētu tos izmantot."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"piekļūt atrašanās vietai fonā"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ja šī atļauja tiek piešķirta līdz ar piekļuvi aptuvenai vai precīzai atrašanās vietai, lietotne var piekļūt atrašanās vietai, darbojoties fonā."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Šī lietotne var piekļūt atrašanās vietas datiem, darbojoties ne tikai priekšplānā, bet arī fonā."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"mainīt audio iestatījumus"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ļauj lietotnei mainīt globālos audio iestatījumus, piemēram, skaļumu un izejai izmantoto skaļruni."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ierakstīt audio"</string>
@@ -497,6 +494,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Ļauj lietotnei skatīt Bluetooth konfigurāciju planšetdatorā, kā arī veidot un pieņemt savienojumus ar pārī savienotām ierīcēm."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Ļauj lietotnei skatīt Bluetooth konfigurāciju Android TV ierīcē, kā arī veidot un pieņemt savienojumus ar pārī savienotām ierīcēm."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Ļauj lietotnei skatīt Bluetooth konfigurāciju tālrunī, kā arī veidot un pieņemt savienojumus ar pārī savienotām ierīcēm."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontrolē tuvlauka saziņu"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Ļauj lietotnei sazināties ar tuva darbības lauka sakaru (Near Field Communication — NFC) atzīmēm, kartēm un lasītājiem."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"atspējot ekrāna bloķēšanu"</string>
@@ -1894,7 +1895,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Izveidots savienojums ar: <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Pieskarieties, lai skatītu failus."</string>
<string name="pin_target" msgid="8036028973110156895">"Piespraust"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Atspraust"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Lietotnes informācija"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Notiek demonstrācijas palaišana..."</string>
@@ -1938,6 +1943,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Vai atjaunināt šos vienumus pakalpojumā "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> un <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Saglabāt"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nē, paldies"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Vēlāk"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nekad"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Atjaunināt"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Turpināt"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"paroli"</string>
@@ -2034,5 +2041,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Pārslēgt ekrāna sadalīšanu"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Bloķēt ekrānu"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Ekrānuzņēmums"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> uznirstošajā logā."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> subtitru josla."</string>
</resources>
diff --git a/core/res/res/values-mcc510-mnc08/config.xml b/core/res/res/values-mcc510-mnc08/config.xml
index 7b27554..58fbb9e 100644
--- a/core/res/res/values-mcc510-mnc08/config.xml
+++ b/core/res/res/values-mcc510-mnc08/config.xml
@@ -23,4 +23,6 @@
and "333" is used for other purpose -->
<string-array translatable="false" name="config_callBarringMMI">
</string-array>
+ <string-array translatable="false" name="config_callBarringMMI_for_ims">
+ </string-array>
</resources>
diff --git a/core/res/res/values-mcc510-mnc89/config.xml b/core/res/res/values-mcc510-mnc89/config.xml
index 82efecf..c262247 100644
--- a/core/res/res/values-mcc510-mnc89/config.xml
+++ b/core/res/res/values-mcc510-mnc89/config.xml
@@ -23,4 +23,6 @@
and "333" is used for other purpose -->
<string-array translatable="false" name="config_callBarringMMI">
</string-array>
+ <string-array translatable="false" name="config_callBarringMMI_for_ims">
+ </string-array>
</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 93f2da1..0c73516 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Апликацијата на администраторот за работниот профил или исчезна или е оштетена. Како резултат на тоа, вашиот работен профил и поврзаните податоци ќе се избришат. За помош, контактирајте со администраторот."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Вашиот работен профил веќе не е достапен на уредов"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Премногу обиди за внесување лозинка"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Уред откажан од администраторот за лична употреба"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Некој управува со уредот"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Вашата организација управува со уредов и можно е да го следи сообраќајот на мрежата. Допрете за детали."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Уредот ќе се избрише"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Дозволува апликацијата да испраќа лепливи емитувања, што остануваат по завршување на емитувањето. Прекумерното користење може да го направи вашиот уред Android TV бавен или нестабилен, така што ќе предизвика користење премногу меморија."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Овозможува апликацијата да испраќа лепливи емитувања, кои остануваат по завршувањето на емитувањето. Прекумерна употреба може да предизвика телефонот да биде бавен или нестабилен со тоа што предизвикува да користи премногу меморија."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"прочитај контакти"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Овозможува апликацијата да чита податоци за контактите зачувани на вашиот таблет, вклучувајќи ја и фреквенцијата со која сте повикувале, сте праќале е-пошта или сте комуницирале на други начини со конкретни поединци. Оваа дозвола овозможува апликациите да ги зачуваат вашите податоци за контакт и злонамерните апликации може да споделат податоци за контакт без ваше знаење."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Дозволува апликацијата да чита податоци за вашите контакти кои се складирани во вашиот уред Android TV, вклучувајќи ја зачестеноста со која сте повикувале, сте испраќале е-пораки или сте комуницирале на друг начин со конкретни поединци. Оваа дозвола овозможува апликациите да ги зачувуваат вашите податоци на контактите, а злонамерните апликации можат да ги споделуваат податоците на контактите без ваше знаење."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Овозможува апликацијата да чита податоци за контактите зачувани во вашиот телефон, вклучувајќи ја и фреквенцијата со која сте повикувале, сте праќале е-пошта или сте комуницирале на други начини со конкретни поединци. Оваа дозвола овозможува апликациите да ги зачуваат вашите податоци за контакт и злонамерните апликации може да споделат податоци за контакт без ваше знаење."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Овозможува апликацијата да чита податоци за контактите складирани во вашиот таблет. Апликациите ќе имаат пристап и до сметките на вашиот таблет што создале контакти. Тука може да спаѓаат сметките создадени од апликациите што сте ги инсталирале. Оваа дозвола им овозможува на апликациите да ги зачувуваат податоците за вашите контакти, а злонамерните апликации може да споделуваат податоци за контактите без ваше знаење."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Овозможува апликацијата да чита податоци за контактите складирани во вашиот уред со Android TV. Апликациите ќе имаат пристап и до сметките на вашиот уред со Android TV што создале контакти. Тука може да спаѓаат сметките создадени од апликациите што сте ги инсталирале. Оваа дозвола им овозможува на апликациите да ги зачувуваат податоците за вашите контакти, а злонамерните апликации може да споделуваат податоци за контактите без ваше знаење."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Овозможува апликацијата да чита податоци за контактите складирани во вашиот телефон. Апликациите ќе имаат пристап и до сметките на вашиот телефон што создале контакти. Тука може да спаѓаат сметките создадени од апликациите што сте ги инсталирале. Оваа дозвола им овозможува на апликациите да ги зачувуваат податоците за вашите контакти, а злонамерните апликации може да споделуваат податоци за контактите без ваше знаење."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"измени ги своите контакти"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Овозможува апликацијата да менува податоци за контактите зачувани во вашиот таблет, вклучувајќи ја и колку често сте повикувале, сте праќале е-пошта или сте комуницирале на други начини со конкретни контакти. Оваа дозвола овозможува апликациите да бришат податоци за контакти."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Дозволува апликацијата да чита податоци за вашите контакти кои се складирани во вашиот уред Android TV, вклучувајќи ја зачестеноста со која сте повикувале, испраќале е-пораки или комуницирале на друг начин со конкретни контакти. Оваа дозвола овозможува апликациите да бришат податоци на контактите."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Овозможува апликацијата да менува податоци за контактите зачувани во вашиот телефон, вклучувајќи и колку често сте повикувале, сте праќале е-пошта или сте комуницирале на други начини со конкретни контакти. Оваа дозвола овозможува апликациите да бришат податоци за контакти."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Овозможува апликацијата да менува податоци за контактите складирани во вашиот таблет. Оваа дозвола им овозможува на апликациите да бришат податоци за контактите."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Овозможува апликацијата да менува податоци за контактите складирани во вашиот уред со Android TV. Оваа дозвола им овозможува на апликациите да бришат податоци за контактите."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Овозможува апликацијата да менува податоци за контактите складирани во вашиот телефон. Оваа дозвола им овозможува на апликациите да бришат податоци за контактите."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"прочитај дневник на повици"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Апликацијава може да ја чита историјата на повиците."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"напиши дневник на повици"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"пристапи кон наредби на давателот на дополнителна локација"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Овозможува апликацијата да пристапи кон дополнителни наредби на давател на локација. Ова може да овозможи апликацијата да го попечи функционирањето на GPS или други извори на локација."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"пристап до прецизната локација само во преден план"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Апликацијава може да ја добие вашата точна локација само кога е во преден план. Услугиве според локација мора да се вклучени и достапни на телефонот за да може да ги користи апликацијата. Ова може да го зголеми трошењето на батеријата."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"пристап до приближна локација (според мрежа) само во преден план"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Апликацијава може да ја добие вашата локација од мрежните извори како што се репетиторите за мобилни мрежи и Wi-Fi мрежите, но само кога апликацијата е во преде план. Овие услуги за локација мора да се вклучени и достапни на таблетот за да може да ги користи апликацијата."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Апликацијава може да ја добие вашата локација од мрежните извори како што се репетиторите за мобилни мрежи и Wi-Fi мрежите, но само кога апликацијата е во преден план. Овие услуги според локација мора да се вклучени и достапни на уредот Android TV за да може да ги користи апликацијата."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Апликацијава може да ја добие вашата локација од мрежните извори како што се репетиторите за мобилни мрежи и Wi-Fi мрежите, но само кога апликацијата е во преде план. Овие услуги за локација мора да се вклучени и достапни на телефонот за да може да ги користи апликацијата."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Апликацијава може да ја добие вашата точна локација само кога е во преден план. Услугите според локација мора да се вклучени и достапни на уредот за да може да ги користи апликацијата. Ова може да го зголеми трошењето на батеријата."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"пристап до приближната локација само во преден план"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Апликацијава може да ја добие вашата приближна локација само кога е во преден план. Услугите според локација мора да се вклучени и достапни на уредот за да може да ги користи апликацијата."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"пристап до локацијата во заднина"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ако покрај дозволата за пристап до приближната или прецизната локација е доделена и оваа дозвола, тогаш апликацијата ќе може да пристапува до локацијата додека се извршува во заднина."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Оваа апликација, покрај тоа што може да пристапува до локацијата кога е во преден план, има пристап и кога се извршува во заднина."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"менува аудио поставки"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Овозможува апликацијата да ги менува глобалните аудио поставки, како што се јачината на звукот и кој звучник се користи за излез."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"снимај аудио"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Овозможува апликацијата да ја види конфигурацијата на Bluetooth на таблетот и да прави и да прифаќа врски со спарени уреди."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Дозволува апликацијата да ја прикажува конфигурацијата на Bluetooth на вашиот уред Android TV и да прави и прифаќа врски со спарените уреди."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Овозможува апликацијата да ја види конфигурацијата на Bluetooth на телефонот и да прави и да прифаќа врски со спарени уреди."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"контролирај комуникација на блиско поле"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Дозволува апликацијата да комуницира со ознаки, картички и читачи за Комуникација при непосредна близина (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"оневозможи заклучување на екран"</string>
@@ -1864,7 +1865,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Поврзан на <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Допрете за да ги погледнете датотеките"</string>
<string name="pin_target" msgid="8036028973110156895">"Прикачете"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Откачете"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Информации за апликација"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Се вклучува демонстрацијата…"</string>
@@ -1907,6 +1912,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Да се ажурираат овие ставки во "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> и <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Зачувај"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Не, фала"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Не сега"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Никогаш"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Ажурирај"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Продолжи"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"лозинка"</string>
@@ -2002,5 +2009,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Вклучи/исклучи поделен екран"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Заклучен екран"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Слика од екранот"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Апликацијата <xliff:g id="APP_NAME">%1$s</xliff:g> во скокачки прозорец."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Насловна лента на <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index c220cdd..a7d48cf 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"ഔദ്യോഗിക പ്രൊഫൈൽ അഡ്മിൻ ആപ്പ് വിട്ടുപോയിരിക്കുന്നു അല്ലെങ്കിൽ കേടായിരിക്കുന്നു. ഫലമായി, നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈലും ബന്ധപ്പെട്ട വിവരങ്ങളും ഇല്ലാതാക്കിയിരിക്കുന്നു. സഹായത്തിന് അഡ്മിനെ ബന്ധപ്പെടുക."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ഈ ഉപകരണത്തിൽ തുടർന്നങ്ങോട്ട് നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ ലഭ്യമല്ല"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"വളരെയധികം പാസ്വേഡ് ശ്രമങ്ങൾ"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"വ്യക്തിപരമായ ഉപയോഗത്തിനായി, ഉപകരണത്തിന്റെ ഔദ്യോഗിക ഉപയോഗം അഡ്മിൻ അവസാനിപ്പിച്ചു"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"ഉപകരണം മാനേജുചെയ്യുന്നുണ്ട്"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"നിങ്ങളുടെ സ്ഥാപനമാണ് ഈ ഉപകരണം മാനേജുചെയ്യുന്നത്, നെറ്റ്വർക്ക് ട്രാഫിക്ക് നിരീക്ഷിക്കുകയും ചെയ്തേക്കാം, വിശദാംശങ്ങൾ അറിയാൻ ടാപ്പുചെയ്യുക."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"നിങ്ങളുടെ ഉപകരണം മായ്ക്കും"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"പ്രക്ഷേപണം അവസാനിച്ച ശേഷവും അവശേഷിക്കുന്ന സ്റ്റിക്കി പ്രക്ഷേപണങ്ങൾ അയയ്ക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. Android ടിവിയുടെ അമിതമായ ഉപയോഗം ഒരുപാട് മെമ്മറി ഉപയോഗിക്കാൻ കാരണമാകുകയും ടിവിയുടെ വേഗത കുറയ്ക്കുകയോ അതിനെ അസ്ഥിരമാക്കുകയോ ചെയ്തേക്കാം."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"സ്റ്റിക്കി പ്രക്ഷേപണങ്ങൾ അയയ്ക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു, പ്രക്ഷേപണം അവസാനിച്ചതിനുശേഷവും അത് നിലനിൽക്കുന്നു. അമിതോപയോഗം വളരെയധികം മെമ്മറി ഉപയോഗിക്കുന്നതിനാൽ, അത് ഫോണിന്റെ പ്രവർത്തനത്തെ മന്ദഗതിയിലാക്കുകയോ അസ്ഥിരമാക്കുകയോ ചെയ്യാം."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"നിങ്ങളുടെ കോൺടാക്റ്റുകൾ റീഡുചെയ്യുക"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"നിശ്ചിത ആളുകളെ മറ്റ് മാർഗങ്ങളിൽ നിങ്ങൾ എത്ര തവണ വിളിച്ചിട്ടുണ്ടെന്നതോ അവർക്ക് ഇമെയിൽ ചെയ്തിട്ടുണ്ടെന്നതോ ആശയവിനിമയം നടത്തിയിട്ടുണ്ടെന്നതോ ഉൾപ്പെടെ, നിങ്ങളുടെ ടാബ്ലെറ്റിൽ സംഭരിച്ചിരിക്കുന്ന നിങ്ങളുടെ കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ റീഡുചെയ്യാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. നിങ്ങളുടെ കോൺടാക്റ്റ് ഡാറ്റ സംരക്ഷിക്കാൻ ഈ അനുമതി അപ്ലിക്കേഷനുകളെ അനുവദിക്കുന്നു, ക്ഷുദ്രകരമായ അപ്ലിക്കേഷനുകൾ നിങ്ങളുടെ അറിവില്ലാതെ കോൺടാക്റ്റ് ഡാറ്റ പങ്കിടാനിടയുണ്ട്."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"നിർദ്ദിഷ്ട വ്യക്തികളെ നിങ്ങൾ എത്ര തവണ വിളിച്ചിട്ടുണ്ടെന്നും അവർക്ക് ഇമെയിൽ അയച്ചിട്ടുണ്ടെന്നും മറ്റ് മാർഗങ്ങളിലൂടെ അവരുമായി എത്ര തവണ ആശയവിനിമയം നടത്തിയിട്ടുണ്ടെന്നും ഉൾപ്പെടെ നിങ്ങളുടെ Android ടിവിയിൽ സംഭരിച്ചിരിക്കുന്ന കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ പരിഷ്ക്കരിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. ഈ അനുമതി നിങ്ങളുടെ കോൺടാക്റ്റ് ഡാറ്റ സംരക്ഷിക്കാൻ ആപ്പുകളെ അനുവദിക്കുന്നു, നിങ്ങളുടെ അറിവില്ലാതെ ദോഷകരമായ ആപ്പുകൾ കോൺടാക്റ്റ് ഡാറ്റ പങ്കിടുകയും ചെയ്തേക്കാം."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"നിശ്ചിത ആളുകളെ മറ്റ് മാർഗങ്ങളിൽ നിങ്ങൾ എത്ര തവണ വിളിച്ചിട്ടുണ്ടെന്നതോ അവർക്ക് ഇമെയിൽ ചെയ്തിട്ടുണ്ടെന്നതോ ആശയവിനിമയം നടത്തിയിട്ടുണ്ടെന്നതോ ഉൾപ്പെടെ, നിങ്ങളുടെ ഫോണിൽ സംഭരിച്ചിരിക്കുന്ന നിങ്ങളുടെ കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ റീഡുചെയ്യാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. നിങ്ങളുടെ കോൺടാക്റ്റ് ഡാറ്റ സംരക്ഷിക്കാൻ ഈ അനുമതി അപ്ലിക്കേഷനുകളെ അനുവദിക്കുന്നു, ക്ഷുദ്രകരമായ അപ്ലിക്കേഷനുകൾ നിങ്ങളുടെ അറിവില്ലാതെ കോൺടാക്റ്റ് ഡാറ്റ പങ്കിടാനിടയുണ്ട്."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"ടാബ്ലെറ്റിൽ സംഭരിച്ച നിങ്ങളുടെ കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. നിങ്ങളുടെ ടാബ്ലെറ്റിൽ കോണ്ടാക്റ്റുകൾ സൃഷ്ടിച്ച അക്കൗണ്ടുകളിലേക്കുള്ള ആക്സസും ആപ്പുകൾക്ക് ഉണ്ടായിരിക്കും. നിങ്ങൾ ഇൻസ്റ്റാൾ ചെയ്ത ആപ്പുകൾ സൃഷ്ടിച്ച അക്കൗണ്ടുകളും ഇതിൽ ഉൾപ്പെട്ടേക്കാം. നിങ്ങളുടെ കോണ്ടാക്റ്റ് ഡാറ്റ സംരക്ഷിക്കാൻ ആപ്പുകളെ ഈ അനുമതി അനുവദിക്കുന്നു, നിങ്ങളുടെ അറിവില്ലാതെ, ദോഷകരമായ ആപ്പുകൾ കോണ്ടാക്റ്റ് ഡാറ്റ പങ്കിടുകയും ചെയ്തേക്കാം."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"നിങ്ങളുടെ Android ടിവിയിൽ സംഭരിച്ച കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. നിങ്ങളുടെ Android ടിവിയിൽ കോണ്ടാക്റ്റുകൾ സൃഷ്ടിച്ച അക്കൗണ്ടുകളിലേക്കുള്ള ആക്സസും ആപ്പുകൾക്ക് ഉണ്ടായിരിക്കും. നിങ്ങൾ ഇൻസ്റ്റാൾ ചെയ്ത ആപ്പുകൾ സൃഷ്ടിച്ച അക്കൗണ്ടുകളും ഇതിൽ ഉൾപ്പെട്ടേക്കാം. നിങ്ങളുടെ കോണ്ടാക്റ്റ് ഡാറ്റ സംരക്ഷിക്കാൻ ആപ്പുകളെ ഈ അനുമതി അനുവദിക്കുന്നു, നിങ്ങളുടെ അറിവില്ലാതെ, ദോഷകരമായ ആപ്പുകൾ കോണ്ടാക്റ്റ് ഡാറ്റ പങ്കിടുകയും ചെയ്തേക്കാം."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ഉപകരണത്തിൽ സംഭരിച്ച നിങ്ങളുടെ കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ വായിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. നിങ്ങളുടെ ഫോണിൽ കോണ്ടാക്റ്റുകൾ സൃഷ്ടിച്ച അക്കൗണ്ടുകളിലേക്കുള്ള ആക്സസും ആപ്പുകൾക്ക് ഉണ്ടായിരിക്കും. നിങ്ങൾ ഇൻസ്റ്റാൾ ചെയ്ത ആപ്പുകൾ സൃഷ്ടിച്ച അക്കൗണ്ടുകളും ഇതിൽ ഉൾപ്പെട്ടേക്കാം. നിങ്ങളുടെ കോണ്ടാക്റ്റ് ഡാറ്റ സംരക്ഷിക്കാൻ ആപ്പുകളെ ഈ അനുമതി അനുവദിക്കുന്നു, നിങ്ങളുടെ അറിവില്ലാതെ, ദോഷകരമായ ആപ്പുകൾ കോണ്ടാക്റ്റ് ഡാറ്റ പങ്കിടുകയും ചെയ്തേക്കാം."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"നിങ്ങളുടെ കോൺടാക്റ്റുകൾ പരിഷ്ക്കരിക്കുക"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"നിശ്ചിത കോൺടാക്റ്റുകളെ മറ്റ് മാർഗങ്ങളിൽ നിങ്ങൾ എത്ര തവണ വിളിച്ചിട്ടുണ്ടെന്നതോ അവർക്ക് ഇമെയിൽ ചെയ്തിട്ടുണ്ടെന്നതോ ആശയവിനിമയം നടത്തിയിട്ടുണ്ടെന്നതോ ഉൾപ്പെടെ, നിങ്ങളുടെ ടാബ്ലെറ്റിൽ സംഭരിച്ചിരിക്കുന്ന നിങ്ങളുടെ കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ പരിഷ്ക്കരിക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഈ അനുമതി കോൺടാക്റ്റ് ഡാറ്റ ഇല്ലാതാക്കാൻ അപ്ലിക്കേഷനുകളെ അനുവദിക്കുന്നു."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"നിർദ്ദിഷ്ട കോൺടാക്റ്റുകളെ നിങ്ങൾ എത്ര തവണ വിളിച്ചിട്ടുണ്ടെന്നും അവർക്ക് ഇമെയിൽ അയച്ചിട്ടുണ്ടെന്നും മറ്റ് മാർഗങ്ങളിലൂടെ അവരുമായി എത്ര തവണ ആശയവിനിമയം നടത്തിയിട്ടുണ്ടെന്നും ഉൾപ്പെടെ നിങ്ങളുടെ Android ടിവിയിൽ സംഭരിച്ചിരിക്കുന്ന കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ പരിഷ്ക്കരിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. കോൺടാക്റ്റ് ഡാറ്റ ഇല്ലാതാക്കാൻ ഈ അനുമതി ആപ്പുകളെ അനുവദിക്കുന്നു."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"നിശ്ചിത കോൺടാക്റ്റുകളെ മറ്റ് മാർഗങ്ങളിൽ നിങ്ങൾ എത്ര തവണ വിളിച്ചിട്ടുണ്ടെന്നതോ അവർക്ക് ഇമെയിൽ ചെയ്തിട്ടുണ്ടെന്നതോ ആശയവിനിമയം നടത്തിയിട്ടുണ്ടെന്നതോ ഉൾപ്പെടെ, നിങ്ങളുടെ ഫോണിൽ സംഭരിച്ചിരിക്കുന്ന നിങ്ങളുടെ കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ പരിഷ്ക്കരിക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഈ അനുമതി കോൺടാക്റ്റ് ഡാറ്റ ഇല്ലാതാക്കാൻ അപ്ലിക്കേഷനുകളെ അനുവദിക്കുന്നു."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"ടാബ്ലെറ്റിൽ സംഭരിച്ച നിങ്ങളുടെ കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ പരിഷ്ക്കരിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. കോൺടാക്റ്റ് ഡാറ്റ ഇല്ലാതാക്കാൻ അപ്പുകളെ ഈ അനുമതി അനുവദിക്കുന്നു."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"നിങ്ങളുടെ Android ടിവിയിൽ സംഭരിച്ച കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ പരിഷ്ക്കരിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. കോൺടാക്റ്റ് ഡാറ്റ ഇല്ലാതാക്കാൻ അപ്പുകളെ ഈ അനുമതി അനുവദിക്കുന്നു."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ഫോണിൽ സംഭരിച്ച നിങ്ങളുടെ കോൺടാക്റ്റുകളെക്കുറിച്ചുള്ള ഡാറ്റ പരിഷ്ക്കരിക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു. കോൺടാക്റ്റ് ഡാറ്റ ഇല്ലാതാക്കാൻ അപ്പുകളെ ഈ അനുമതി അനുവദിക്കുന്നു."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"കോൾ ചരിത്രം റീഡ് ചെയ്യുക"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ഈ ആപ്പിന് നിങ്ങളുടെ കോൾ ചരിത്രം വായിക്കാൻ കഴിയും."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"കോൾ ചരിത്രം റൈറ്റ് ചെയ്യുക"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ലൊക്കേഷൻ ദാതാവിന്റെ അധിക കമാൻഡുകൾ ആക്സസ്സുചെയ്യുക"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ലൊക്കേഷൻ ദാതാവിന്റെ അധിക കമാൻഡുകൾ ആക്സസ്സുചെയ്യാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഇത് GPS-ന്റെയോ മറ്റ് ലൊക്കേഷൻ ഉറവിടങ്ങളുടെയോ പ്രവർത്തനത്തിൽ ഇടപെടാൻ അപ്ലിക്കേഷനെ അനുവദിക്കാനിടയുണ്ട്."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ഫോർഗ്രൗണ്ടിൽ മാത്രം കൃത്യമായ ലൊക്കേഷൻ ആക്സസ് ചെയ്യുക"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"ഫോർഗ്രൗണ്ടിൽ ഉള്ളപ്പോൾ മാത്രമേ ഈ ആപ്പിന് നിങ്ങളുടെ കൃത്യമായ ലൊക്കേഷൻ നേടാനാവൂ. ആപ്പിന് ഉപയോഗിക്കാനായി, ഈ ലൊക്കേഷൻ സേവനങ്ങൾ ഓണായിരിക്കുകയും നിങ്ങളുടെ ഫോണിൽ ലഭ്യമാവുകയും വേണം. ഇതിലൂടെ ബാറ്ററി ഉപഭോഗം വർദ്ധിച്ചേക്കാം."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"ഫോർഗ്രൗണ്ടിൽ മാത്രം, ഏകദേശ ലൊക്കേഷൻ (നെറ്റ്വർക്ക്-അടിസ്ഥാനമാക്കി) ആക്സസ് ചെയ്യുക"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"സെൽ ടവറുകളും വൈഫൈ നെറ്റ്വർക്കുകളും പോലുള്ള നെറ്റ്വർക്ക് ഉറവിടങ്ങൾ അടിസ്ഥാനമാക്കി, ഈ ആപ്പിന് നിങ്ങളുടെ ലൊക്കേഷൻ നേടാൻ കഴിയും. എന്നാൽ, ഫോർഗ്രൗണ്ടിൽ ഉള്ളപ്പോൾ മാത്രമേ ആപ്പിന് ഇതിന് കഴിയൂ. ആപ്പിന് ഉപയോഗിക്കാനായി, ഈ ലൊക്കേഷൻ സേവനങ്ങൾ ഓണായിരിക്കുകയും നിങ്ങളുടെ ടാബ്ലെറ്റിൽ ലഭ്യമാവുകയും വേണം."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"സെൽ ടവറുകളും വൈഫൈ നെറ്റ്വർക്കുകളും പോലെയുള്ള നെറ്റ്വർക്ക് ഉറവിടങ്ങളുടെ അടിസ്ഥാനത്തിൽ ഈ ആപ്പിന് നിങ്ങളുടെ ലൊക്കേഷൻ നേടാനാവും, എന്നാൽ ആപ്പ് ഫോർഗ്രൗണ്ടിൽ ആയിരിക്കുമ്പോൾ മാത്രമേ ഇത് സാധ്യമാവൂ. ആപ്പിന് ഉപയോഗിക്കാനായി ഈ ലൊക്കേഷൻ സേവനങ്ങൾ ഓണായിരിക്കുകയും നിങ്ങളുടെ Android ടിവിയിൽ ലഭ്യമാവുകയും വേണം."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"സെൽ ടവറുകളും വൈഫൈ നെറ്റ്വർക്കുകളും പോലുള്ള നെറ്റ്വർക്ക് ഉറവിടങ്ങൾ അടിസ്ഥാനമാക്കി, ഈ ആപ്പിന് നിങ്ങളുടെ ലൊക്കേഷൻ നേടാൻ കഴിയും. എന്നാൽ, ഫോർഗ്രൗണ്ടിൽ ഉള്ളപ്പോൾ മാത്രമേ ആപ്പിന് ഇതിന് കഴിയൂ. ആപ്പിന് ഉപയോഗിക്കാനായി, ഈ ലൊക്കേഷൻ സേവനങ്ങൾ ഓണായിരിക്കുകയും നിങ്ങളുടെ ഫോണിൽ ലഭ്യമാവുകയും വേണം."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"ഫോർഗ്രൗണ്ടിൽ ഉള്ളപ്പോൾ മാത്രമേ ഈ ആപ്പിന് നിങ്ങളുടെ കൃത്യമായ ലൊക്കേഷൻ ലഭിക്കൂ. ഈ ആപ്പിന് ലൊക്കേഷൻ സേവനങ്ങൾ ഉപയോഗിക്കാൻ കഴിയണമെങ്കിൽ, അവ നിങ്ങളുടെ ഉപകരണത്തിൽ ലഭ്യമായിരിക്കുകയും ഓണായിരിക്കുകയും വേണം. ഇത് ബാറ്ററി ഉപഭോഗം വർദ്ധിപ്പിച്ചേക്കാം."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"ഫോർഗ്രൗണ്ടിൽ മാത്രം ഏകദേശ ലൊക്കേഷൻ ആക്സസ് ചെയ്യുക"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"ഫോർഗ്രൗണ്ടിൽ ഉള്ളപ്പോൾ മാത്രമേ ഈ ആപ്പിന് നിങ്ങളുടെ ഏകദേശ ലൊക്കേഷൻ ലഭിക്കൂ. ഈ ആപ്പിന് ലൊക്കേഷൻ സേവനങ്ങൾ ഉപയോഗിക്കാൻ കഴിയണമെങ്കിൽ, അവ നിങ്ങളുടെ ഉപകരണത്തിൽ ലഭ്യമായിരിക്കുകയും ഓണായിരിക്കുകയും വേണം."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"പശ്ചാത്തലത്തിൽ ലൊക്കേഷൻ ആക്സസ് ചെയ്യുക"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ഏകദേശം അല്ലെങ്കിൽ കൃത്യമായ ലൊക്കേഷൻ ആക്സസിന് ഇത് അധികമായി അനുവദിച്ചതാണെങ്കിൽ, പശ്ചാത്തലത്തിൽ റൺ ചെയ്യുമ്പോഴും ആപ്പിന് ലൊക്കേഷൻ ആക്സസ് ചെയ്യാനാവും."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"പശ്ചാത്തലത്തിൽ പ്രവർത്തിക്കുമ്പോൾ, ഫോർഗ്രൗണ്ട് ലൊക്കേഷന് പുറമെ, ലൊക്കേഷൻ ആക്സസ് ചെയ്യാനും ഈ ആപ്പിന് കഴിയും."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"നിങ്ങളുടെ ഓഡിയോ ക്രമീകരണങ്ങൾ മാറ്റുക"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"വോളിയവും ഔട്ട്പുട്ടിനായി ഉപയോഗിച്ച സ്പീക്കറും പോലുള്ള ആഗോള ഓഡിയോ ക്രമീകരണങ്ങൾ പരിഷ്ക്കരിക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ടാബ്ലെറ്റിലെ ബ്ലൂടൂത്ത് കോൺഫിഗറേഷൻ കാണാനും ജോടിയാക്കിയ ഉപകരണങ്ങളുമായി കണക്ഷനുകൾ നടത്തി അംഗീകരിക്കാനും അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"നിങ്ങളുടെ Android ടിവിയിലെ Bluetooth കോൺഫിഗറേഷൻ കാണാനും ജോടിയാക്കിയ ഉപകരണങ്ങളുമായി കണക്ഷനുകൾ സൃഷ്ടിക്കാനും അംഗീകരിക്കാനും ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ഫോണിലെ ബ്ലൂടൂത്ത് കോൺഫിഗറേഷൻ കാണാനും ജോടിയാക്കിയ ഉപകരണങ്ങളുമായി കണക്ഷനുകൾ നടത്തി അംഗീകരിക്കാനും അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"സമീപ ഫീൽഡുമായുള്ള ആശയവിനിമയം നിയന്ത്രിക്കുക"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"നിയർ ഫീൽഡ് കമ്മ്യൂണിക്കേഷൻ (NFC) ടാഗുകളുമായും കാർഡുകളുമായും റീഡറുകളുമായുള്ള ആശയവിനിമയത്തിന് അപ്ലിക്കേഷനുകളെ അനുവദിക്കുന്നു."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"നിങ്ങളുടെ സ്ക്രീൻ ലോക്ക് പ്രവർത്തനരഹിതമാക്കുക"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തു"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ഫയലുകൾ കാണുന്നതിന് ടാപ്പുചെയ്യുക"</string>
<string name="pin_target" msgid="8036028973110156895">"പിൻ ചെയ്യുക"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"അൺപിൻ ചെയ്യുക"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ആപ്പ് വിവരം"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ഡെമോ ആരംഭിക്കുന്നു…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"ഇനിപ്പറയുന്ന ഇനങ്ങൾ "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" എന്നതിൽ അപ്ഡേറ്റ് ചെയ്യണോ: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"സംരക്ഷിക്കുക"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"വേണ്ട, നന്ദി"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ഇപ്പോൾ വേണ്ട"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ഒരിക്കലും വേണ്ട"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"അപ്ഡേറ്റ് ചെയ്യുക"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"തുടരുക"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"പാസ്വേഡ്"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"സ്ക്രീൻ വിഭജന മോഡ് മാറ്റുക"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"ലോക്ക് സ്ക്രീൻ"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"സ്ക്രീൻഷോട്ട്"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"പോപ്പ്-അപ്പ് വിൻഡോയിലെ <xliff:g id="APP_NAME">%1$s</xliff:g> ആപ്പ്."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിന്റെ അടിക്കുറിപ്പ് ബാർ."</string>
</resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 8c26113..9766475 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Ажлын профайлын админ апп байхгүй эсвэл эвдэрсэн байна. Үүний улмаас таны ажлын профайл болон холбогдох мэдээллийг устгасан болно. Тусламж хэрэгтэй бол админтай холбогдоно уу."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Таны ажлын профайл энэ төхөөрөмжид боломжгүй байна"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Нууц үгийг хэт олон удаа буруу оруулсан байна"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Админ хувийн хэрэглээнд зориулж төхөөрөмжийн эрхийг хассан"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Төхөөрөмжийг удирдсан"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Таны байгууллага энэ төхөөрөмжийг удирдаж, сүлжээний ачааллыг хянадаг. Дэлгэрэнгүй мэдээлэл авах бол товшино уу."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Таны төхөөрөмж устах болно."</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Аппад нэвтрүүлэг дууссаны дараа үлдэх бэхлэгдсэн нэвтрүүлэг илгээхийг зөвшөөрнө. Хэт их ашиглах нь санах ойн ачааллыг нэмэгдүүлж, улмаар таны Android ТВ төхөөрөмжийг удаан эсвэл тогтворгүй болгож болзошгүй."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Апп нь өргөн дамжуулал дууссаны дараа үлдсэн өргөн дамжуулалыг илгээх боломжтой. Ихээр ашиглах нь хэт их санах ой ашиглан утсыг удаашруулах болон тогтворгүй болгох боломжтой."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"өөрийн харилцагчдыг унших"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Апп нь таны утсаар ярьсан, имэйл илгээсэн давтамж эсвэл тусгай харилцагдчидтайгаа өөр аргаар холбоо барьсан байдал зэргийг агуулсан таблет дээр хадгалагдсан харилцагчдын талаарх датаг унших боломжтой. Энэ зөвшөөрөл нь апп-д таны харилцагчийн датаг хадгалах боломжийг олгох ба хортой апп нь танд мэдэгдэлгүйгээр харилцагчийн датаг хуваалцах боломжтой."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Аппaд таны тодорхой хувь хүн рүү хийсэн дуудлага, бичсэн имэйл эсвэл бусад аргаар харилцсан байдлын давтамж зэрэг таны Android ТВ төхөөрөмжид хадгалсан харилцагчдын талаарх өгөгдлийг өөрчлөхийг зөвшөөрнө. Энэ зөвшөөрөл нь аппуудад таны харилцагчийн өгөгдлийг хадгалахыг зөвшөөрөх бөгөөд хортой аппууд танд мэдэгдэлгүйгээр харилцагчийн өгөгдлийг бусадтай хуваалцаж болзошгүй."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Апп нь таны утсаар ярьсан, имэйл илгээсэн давтамж эсвэл тусгай харилцагчидтайгаа өөр аргаар холбоо барьсан байдал зэргийг агуулсан таны утсан дээр хадгалагдсан харилцагчдын талаарх датаг унших боломжтой. Энэ зөвшөөрөл нь апп-д таны харилцагчийн датаг хадгалах боломжийг олгох ба хортой апп нь танд мэдэгдэлгүйгээр харилцагчийн датаг хуваалцах боломжтой."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Аппaд таны таблет дээр хадгалагдсан харилцагчдын өгөгдлийг уншихыг зөвшөөрнө. Мөн аппууд нь таны таблет дээрх харилцагч үүсгэсэн бүртгэлд хандах боломжтой байна. Үүнд таны суулгасан аппуудын үүсгэсэн бүртгэлийг оролцуулж болзошгүй. Энэ зөвшөөрөл нь аппуудад таны харилцагчийн өгөгдлийг хадгалахыг зөвшөөрөх бөгөөд хортой аппууд нь танд мэдэгдэлгүйгээр харилцагчийн өгөгдлийг хуваалцаж болзошгүй."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Аппaд таны Android TВ төхөөрөмж дээр хадгалагдсан харилцагчдын өгөгдлийг уншихыг зөвшөөрнө. Мөн аппууд нь таны Android TВ төхөөрөмж дээрх харилцагч үүсгэсэн бүртгэлд хандах боломжтой байна. Үүнд таны суулгасан аппуудын үүсгэсэн бүртгэлийг оролцуулж болзошгүй. Энэ зөвшөөрөл нь аппуудад таны харилцагчийн өгөгдлийг хадгалахыг зөвшөөрөх бөгөөд хортой аппууд нь танд мэдэгдэлгүйгээр харилцагчийн өгөгдлийг хуваалцаж болзошгүй."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Аппaд таны утсан дээр хадгалагдсан харилцагчдын өгөгдлийг уншихыг зөвшөөрнө. Мөн аппууд нь таны утсан дээрх харилцагч үүсгэсэн бүртгэлд хандах боломжтой байна. Үүнд таны суулгасан аппуудын үүсгэсэн бүртгэлийг оролцуулж болзошгүй. Энэ зөвшөөрөл нь аппуудад таны харилцагчийн өгөгдлийг хадгалахыг зөвшөөрөх бөгөөд хортой аппууд нь танд мэдэгдэлгүйгээр харилцагчийн өгөгдлийг хуваалцаж болзошгүй."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"таны харилцагчдыг өөрчлөх"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Апп нь таны утсаар ярьсан, имэйл илгээсэн давтамж эсвэл тусгай харилцагчидтайгаа өөр аргаар холбоо барьсан байдал зэргийг агуулсан таны таблет дээр хадгалагдсан харилцагчдын талаарх датаг өөрчлөх боломжтой. Энэ зөвшөөрөл нь апп-д харилцагчийн датаг устгах боломжийг олгоно."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Аппaд таны тодорхой харилцагч руу хийсэн дуудлага, бичсэн имэйл эсвэл бусад аргаар харилцсан байдлын давтамж зэрэг таны Android ТВ төхөөрөмжид хадгалсан харилцагчдын талаарх өгөгдлийг өөрчлөхийг зөвшөөрнө. Энэ зөвшөөрөл нь аппуудад харилцагчийн өгөгдлийг устгахыг зөвшөөрнө."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Апп нь таны утсаар ярьсан, имэйл илгээсэн давтамж эсвэл харилцагдчидтайгаа өөр аргаар холбоо барьсан байдал зэргийг агуулсан утсан дээр хадгалагдсан харилцагчдын талаарх датаг өөрчлөх боломжтой. Энэ зөвшөөрөл нь апп-д харилцагчийн датаг устгах боломжийг олгоно."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Аппад таны таблет дээр хадгалагдсан харилцагчдын өгөгдлийг өөрчлөхийг зөвшөөрнө. Энэ зөвшөөрөл нь харилцагчийн өгөгдлийг устгахыг аппуудад зөвшөөрнө."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Аппaд таны Android TВ төхөөрөмж дээр хадгалагдсан харилцагчдын өгөгдлийг өөрчлөхийг зөвшөөрнө. Энэ зөвшөөрөл нь харилцагчийн өгөгдлийг устгахыг аппуудад зөвшөөрнө."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Аппад таны утсан дээр хадгалагдсан харилцагчдын өгөгдлийг өөрчлөхийг зөвшөөрнө. Энэ зөвшөөрөл нь харилцагчийн өгөгдлийг устгахыг аппуудад зөвшөөрнө."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"дуудлагын логийг унших"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Энэ апп таны дуудлагын түүхийг унших боломжтой."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"дуудлагын логруу бичих"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"байршил нийлүүлэгчийн нэмэлт тушаалд хандах"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Апп нь байршил нийлүүлэгчийн нэмэлт тушаалд хандах боломжтой. Энэ нь апп-д GPS эсвэл бусад байршлын үйлчилгээний ажиллагаанд нөлөөлөх боломжийг олгоно."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"нарийвчилсан байршилд зөвхөн нүүр хэсэгт хандах"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Энэ апп нь зөвхөн нүүр хэсэгт байх үедээ л таны байршлыг нарийн тогтоох боломжтой. Апп эдгээр байршлын үйлчилгээг ашиглахын тулд эдгээрийг таны утсан дээр асааж идэвхтэй байлгах шаардлагатай. Энэ нь батарейны хэрэглээг ихэсгэж болзошгүй."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"ойролцоох байршилд (сүлжээнд суурилсан) зөвхөн дэлгэц дээр хандах"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Энэ апп зөвхөн дэлгэц дээр байх үед үүрэн цамхаг, Wi-Fi сүлжээ зэрэг сүлжээний эх сурвалжид тулгуурлан таны байршлыг мэдэх боломжтой. Та энэ аппад эдгээр байршлын үйлчилгээг ашиглуулахын тулд эдгээрийг таблет дээрээ асааж, боломжтой байлгах шаардлагатай."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Энэ апп зөвхөн дэлгэц дээр байх үед үүрэн цамхаг, Wi-Fi сүлжээ зэрэг сүлжээний эх сурвалжид тулгуурлан таны байршлыг мэдэх боломжтой. Та энэ аппад эдгээр байршлын үйлчилгээг ашиглуулахын тулд эдгээрийг Android TB төхөөрөмж дээрээ асааж, боломжтой байлгах шаардлагатай."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Энэ апп зөвхөн дэлгэц дээр байх үед үүрэн цамхаг, Wi-Fi сүлжээ зэрэг сүлжээний эх сурвалжид тулгуурлан таны байршлыг мэдэх боломжтой. Та энэ аппад эдгээр байршлын үйлчилгээг ашиглуулахын тулд эдгээрийг утсан дээрээ асааж, боломжтой байлгах шаардлагатай."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Энэ апп нь зөвхөн дэлгэц дээр байх үедээ л таны байршлыг нарийн тогтоох боломжтой. Байршлын үйлчилгээ нь таны төхөөрөмж дээр асаалттай, апп ашиглах боломжтой байх ёстой. Энэ нь батарейны хэрэглээг нэмэгдүүлж болзошгүй."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"зөвхөн дэлгэц дээр байхад ойролцоо байршилд хандах"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Энэ апп нь зөвхөн дэлгэц дээр байх үедээ л таны байршлыг ойролцоогоор тогтоох боломжтой. Байршлын үйлчилгээ нь таны төхөөрөмж дээр асаалттай, апп ашиглах боломжтой байх ёстой."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"байршилд ард хандах"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Хэрэв үүнийг ойролцоо эсвэл нарийвчилсан байршлын хандалтад нэмэлтээр зөвшөөрсөн бол энэ апп ард ажиллах явцдаа байршилд хандаж болно."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Энэ апп нь дэлгэц дээр байхдаа байршилд хандахаас гадна арын хэсэгт ажиллах үедээ байршилд хандах боломжтой."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Аудио тохиргоо солих"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Апп нь дууны хэмжээ, спикерын гаралтад ашиглагдах глобал аудио тохиргоог өөрчлөх боломжтой."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"аудио бичих"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Апп нь таблет дээрх блютүүт тохиргоог харах боломжтой ба хос болох төхөөрөмжтэй холболтыг зөвшөөрөх болон хийх боломжтой."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Аппад таны Android ТВ төхөөрөмж дээрх Bluetooth-н тохируулгыг харах болон хослуулсан төхөөрөмжүүдтэй холболт хийж, холболтыг баталгаажуулахыг зөвшөөрнө."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Апп нь утсан дээрх Bluetooth тохиргоог харах боломжтой ба хос болох төхөөрөмжтэй холболтыг зөвшөөрөх болон хийх боломжтой."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ойролцоо талбарын холбоог удирдах"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Апп нь Ойролцоо Талбарын Холболт(NFC) таг, карт, болон уншигчтай холбогдох боломжтой."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"дэлгэцний түгжээг идэвхгүй болгох"</string>
@@ -1757,8 +1758,8 @@
<string name="package_updated_device_owner" msgid="7560272363805506941">"Таны админ шинэчилсэн"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Таны админ устгасан"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string>
- <string name="battery_saver_description_with_learn_more" msgid="1817385558636532621">"Батарейны ажиллах хугацааг уртасгахын тулд Батарей хэмнэгч нь:\n·Бараан загварыг асаадаг\n·Арын үйл ажиллагаа, зарим визуал эффект болон “Hey Google” зэрэг бусад онцлогийг унтрааж эсвэл хязгаарладаг\n\n"<annotation id="url">"Нэмэлт мэдээлэл авах"</annotation></string>
- <string name="battery_saver_description" msgid="7618492104632328184">"Батарейны ажиллах хугацааг уртасгахын тулд Батарей хэмнэгч нь:\n·Бараан загварыг асаадаг\n·Арын үйл ажиллагаа, зарим визуал эффект болон “Hey Google” зэрэг бусад онцлогийг унтрааж эсвэл хязгаарладаг"</string>
+ <string name="battery_saver_description_with_learn_more" msgid="1817385558636532621">"Батарейн ажиллах хугацааг уртасгахын тулд Батарей хэмнэгч нь:\n·Бараан загварыг асаадаг\n·Арын үйл ажиллагаа, зарим визуал эффект болон “Hey Google” зэрэг бусад онцлогийг унтрааж эсвэл хязгаарладаг\n\n"<annotation id="url">"Нэмэлт мэдээлэл авах"</annotation></string>
+ <string name="battery_saver_description" msgid="7618492104632328184">"Батарейн ажиллах хугацааг уртасгахын тулд Батарей хэмнэгч нь:\n·Бараан загварыг асаадаг\n·Арын үйл ажиллагаа, зарим визуал эффект болон “Hey Google” зэрэг бусад онцлогийг унтрааж эсвэл хязгаарладаг"</string>
<string name="data_saver_description" msgid="4995164271550590517">"Дата ашиглалтыг багасгахын тулд дата хэмнэгч нь зарим апп-н өгөгдлийг дэвсгэрт илгээх болон авахаас сэргийлдэг. Таны одоогийн ашиглаж буй апп нь өгөгдөлд хандах боломжтой хэдий ч тогтмол хандахгүй. Жишээлбэл зургийг товших хүртэл харагдахгүй."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Дата хэмнэгчийг асаах уу?"</string>
<string name="data_saver_enable_button" msgid="4399405762586419726">"Асаах"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g>-д холбогдсон"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Файлыг үзэхийн тулд дарна уу"</string>
<string name="pin_target" msgid="8036028973110156895">"PIN"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Unpin"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Апп-н мэдээлэл"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Жишээг эхлүүлж байна…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Эдгээр зүйлийг буюу <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> болон <xliff:g id="TYPE_2">%3$s</xliff:g>-г "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"-д шинэчлэх үү?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Хадгалах"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Үгүй, баярлалаа"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Одоо биш"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Хэзээ ч үгүй"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Шинэчлэх"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Үргэлжлүүлэх"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"нууц үг"</string>
@@ -1956,7 +1963,7 @@
<string name="notification_appops_overlay_active" msgid="5571732753262836481">"таны дэлгэцэд бусад аппын дээр харуулж байна"</string>
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Хэвшлийн горимын мэдээллийн мэдэгдэл"</string>
<string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Батарей ихэвчлэн цэнэглэдэг хугацаанаас өмнө дуусаж болзошгүй"</string>
- <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Батарейны ажиллах хугацааг уртасгахын тулд Батарей хэмнэгчийг идэвхжүүллээ"</string>
+ <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Батарейн ажиллах хугацааг уртасгахын тулд Батарей хэмнэгчийг идэвхжүүллээ"</string>
<string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Батарей хэмнэгч"</string>
<string name="battery_saver_sticky_disabled_notification_title" msgid="616803848887159814">"Батарей хэмнэгч батарейг дахин багасах хүртэл дахин идэвхжихгүй"</string>
<string name="battery_saver_sticky_disabled_notification_summary" msgid="9091127514013090563">"Батарейг хангалттай түвшинд цэнэглэлээ. Батарей хэмнэгч батарейг дахин багасах хүртэл дахин идэвхжихгүй."</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Дэлгэц хуваахыг унтраах/асаах"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Дэлгэцийг түгжих"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Дэлгэцийн зураг дарах"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Үзэгдэх цонхонд байгаа <xliff:g id="APP_NAME">%1$s</xliff:g> апп."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н гарчгийн талбар."</string>
</resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 2782caf..9e38391 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"कार्य प्रोफाइल प्रशासक अॅप गहाळ आहे किंवा करप्ट आहे. परिणामी, तुमचे कार्य प्रोफाइल आणि संबंधित डेटा हटवले गेले आहेत. सहाय्यासाठी आपल्या प्रशासकाशी संपर्क साधा."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"तुमचे कार्य प्रोफाइल आता या डिव्हाइसवर उपलब्ध नाही"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"बर्याचदा पासवर्ड टाकण्याचा प्रयत्न केला"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"वैयक्तिक वापरासाठी ॲडमिनने नियंत्रण सोडलेले डिव्हाइस"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"डिव्हाइस व्यवस्थापित केले आहे"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"तुमची संस्था हे डिव्हाइस व्यवस्थापित करते आणि नेटवर्क रहदारीचे निरीक्षण करू शकते. तपशीलांसाठी टॅप करा."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"तुमचे डिव्हाइस मिटविले जाईल"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"चिकट प्रसारणे पाठविण्यासाठी ॲपला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर देखील तसेच राहते. अत्याधिक वापरामुळे बरीच मेमरी वापरली जाऊन तो तुमच्या Android TV डिव्हाइसला धीमा किंवा अस्थिर करू शकतो."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"रोचक प्रसारणे पाठविण्यासाठी अॅप ला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर देखील तसेच राहते. अत्याधिक वापरामुळे बरीच मेमरी वापरली जाऊन तो फोनला धीमा किंवा अस्थिर करू शकतो."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"तुमचे संपर्क वाचा"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"तुम्ही कॉल केलेल्या, ईमेल केलेल्या किंवा विशिष्ट लोकांशी अन्य मार्गांनी संवाद प्रस्थापित केलेल्या लोकांच्या फ्रिक्वेन्सीसह, आपल्या टॅब्लेटवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा वाचण्यासाठी अॅप ला अनुमती देते. ही परवानगी तुमचा संपर्क डेटा सेव्ह करण्याची अॅप्स ला अनुमती देते आणि दुर्भावनापूर्ण अॅप्स आपल्या माहितीशिवाय संपर्क डेटा शेअर करू शकतात."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"तुम्ही केलेले कॉल, केलेले ईमेल किंवा विशिष्ट संपर्कांसह अन्य मार्गांनी केलेले कम्युनिकेशन यांची वारंवारता, यासह तुमच्या Android TV डिव्हाइसवर स्टोअर केलेल्या तुमच्या संपर्कांविषयीचा डेटा वाचण्याची ॲपला अनुमती देते. ही परवानगी अॅप्सना तुमचा संपर्क डेटा सेव्ह करण्याची अनुमती देते आणि ही दुर्भावनापूर्ण अॅप्स तुम्हाला न कळवता संपर्क डेटा शेअर करू शकतात."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"तुम्ही कॉल केलेल्या, ईमेल केलेल्या किंवा विशिष्ट लोकांशी अन्य मार्गांनी संवाद प्रस्थापित केलेल्या लोकांच्या फ्रिक्वेन्सीसह, आपल्या फोनवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा वाचण्यासाठी अॅप ला अनुमती देते. ही परवानगी तुमचा संपर्क डेटा सेव्ह करण्याची अॅप्स ला अनुमती देते आणि दुर्भावनापूर्ण अॅप्स आपल्या माहितीशिवाय संपर्क डेटा शेअर करू शकतात."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"तुमच्या टॅबलेटवर स्टोअर केलेल्या तुमच्या संपर्कांविषयीचा डेटा वाचण्याची ॲपला अनुमती देते. अॅप्सना संपर्क तयार केलेल्या तुमच्या टॅबलेटवरील खात्याचा अॅक्सेसदेखील असेल. यामध्ये तुम्ही इंस्टॉल केलेल्या ॲप्सने तयार केलेल्या खात्यांचा समावेश असू शकतात. ही परवानगी ॲप्सना तुमचा संपर्क डेटा सेव्ह करण्याची अनुमती देते आणि दुर्भावनापूर्ण ॲप्स तुम्हाला न कळवता संपर्क डेटा शेअर करू शकतात."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"तुमच्या Android TV डिव्हाइस स्टोअर केलेल्या तुमच्या संपर्कांविषयीचा डेटा वाचण्याची ॲपला अनुमती देते. अॅप्सना संपर्क तयार केलेल्या तुमच्या Android TV डिव्हाइसवरील खात्याचा अॅक्सेसदेखील असेल. यामध्ये तुम्ही इंस्टॉल केलेल्या ॲप्सने तयार केलेल्या खात्यांचा समावेश असू शकतात. ही परवानगी ॲप्सना तुमचा संपर्क डेटा सेव्ह करण्याची अनुमती देते आणि दुर्भावनापूर्ण ॲप्स तुम्हाला न कळवता संपर्क डेटा शेअर करू शकतात."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"तुमच्या फोनवर स्टोअर केलेल्या तुमच्या संपर्कांविषयीचा डेटा वाचण्याची ॲपला अनुमती देते. अॅप्सना संपर्क तयार केलेल्या तुमच्या फोनवरील खात्याचा अॅक्सेसदेखील असेल. यामध्ये तुम्ही इंस्टॉल केलेल्या ॲप्सने तयार केलेल्या खात्यांचा समावेश असू शकतात. ही परवानगी ॲप्सना तुमचा संपर्क डेटा सेव्ह करण्याची अनुमती देते आणि दुर्भावनापूर्ण ॲप्स तुम्हाला न कळवता संपर्क डेटा शेअर करू शकतात."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"तुमचे संपर्क सुधारित करा"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"तुम्ही विशिष्ट संपर्कांशी अन्य मार्गांनी कॉल केलेल्या, ईमेल केलेल्या किंवा संवाद प्रस्थापित केलेल्या फ्रिक्वेन्सीसह, आपल्या टॅब्लेटवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा सुधारित करण्यासाठी अॅप ला अनुमती देते. ही परवानगी संपर्क डेटा हटविण्यासाठी अॅप ला अनुमती देते."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"तुम्ही विशिष्ट संपर्कांशी कॉल, ईमेल किंवा इतर मार्गांनी संवाद प्रस्थापित केल्याची वारंवारता यांच्या समावेशासह, तुमच्या Android TV वर स्टोअर केलेल्या तुमच्या संपर्कांविषयीचा डेटा सुधारित करण्यासाठी ॲपला अनुमती देते. ही परवानगी ॲपला संपर्क डेटा हटविण्यासाठी अनुमती देते."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"तुम्ही विशिष्ट संपर्कांशी अन्य मार्गांनी कॉल केलेल्या, ईमेल केलेल्या किंवा संवाद प्रस्थापित केलेल्या फ्रिक्वेन्सीसह, आपल्या फोनवर स्टोअर केलेल्या आपल्या संपर्कांविषयीचा डेटा सुधारित करण्यासाठी अॅप ला अनुमती देते. ही परवानगी संपर्क डेटा हटविण्यासाठी अॅप ला अनुमती देते."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"तुमच्या टॅबलेटवर स्टोअर केलेल्या तुमच्या संपर्कांविषयीच्या डेटामध्ये बदल करण्यासाठी अॅपला अनुमती देते. ही परवानगी अॅप्सला संपर्क डेटा हटवण्याची परवानगी देते."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV वर स्टोअर केलेल्या तुमच्या संपर्कांविषयीच्या डेटामध्ये बदल करण्यासाठी अॅपला अनुमती देते. ही परवानगी अॅप्सला संपर्क डेटा हटवण्याची परवानगी देते."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"तुमच्या फोनवर स्टोअर केलेल्या तुमच्या संपर्कांविषयीच्या डेटामध्ये बदल करण्यासाठी अॅपला अनुमती देते. ही परवानगी अॅप्सला संपर्क डेटा हटवण्याची परवानगी देते."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"कॉल लॉग वाचा"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"हा अॅप तुमचा कॉल इतिहास वाचू शकता."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"कॉल लॉग लिहा"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"अतिरिक्त स्थान प्रदाता आदेश अॅक्सेस करा"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"अॅपला अतिरिक्त स्थान प्रदाता आदेशावर प्रवेश करण्याची अनुमती देते. हे कदाचित अॅपला GPS किंवा इतर स्थान स्रोत च्या ऑपरेशनमध्ये हस्तक्षेप करण्याची अनुमती देऊ शकते."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"फक्त फोरग्राउंडमध्ये अचूकपणे अॅक्सेस करा"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच तुमचे अचूक स्थान मिळवू शकते. या स्थान सेवा सुरू करणे आणि त्या वापरण्यासाठी अॅपसाठी तुमच्या फोनवर उपलब्ध करणे आवश्यक आहे, यामुळे बॅटरी वापर वाढू शकतो."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"फक्त फोरग्राउंडमध्ये अंदाजे स्थान (नेटवर्क आधारित) अॅक्सेस करा"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. त्या वापरण्याकरता अॅपसाठी, या स्थान सेवा सुरू करणे आणि त्या तुमच्या टॅबलेटवर उपलब्ध करणे आवश्यक आहे."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"हे ॲप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. ॲपने वापरण्याकरिता, या स्थान सेवा सुरू करणे आणि त्या तुमच्या Android TV डिव्हाइसवर उपलब्ध करणे आवश्यक आहे."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच, सेल टॉवर आणि वाय-फाय नेटवर्क सारख्या नेटवर्क स्रोतवर आधारित तुमचे स्थान मिळवू शकते. ते वापरण्याकरता अॅपसाठी, या स्थान सेवा सुरू करणे आणि त्या तुमच्या फोनवर उपलब्ध करणे आवश्यक आहे."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"हे अॅप फक्त फोरग्राउंडमध्ये असतानाच तुमचे अचूक स्थान मिळवू शकते. स्थातुमच्या डिव्हाइसवर स्थान सेवा सुरू असणे आणि उपलब्ध असणे आवश्यक आहे जेणेकरून ॲप त्यांचा वापर करू शकतील. यामुळे बॅटरी वापर वाढू शकतो."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"फक्त फोअरग्राउंडमध्ये अचूक स्थान अॅक्सेस करा"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"हे अॅप फक्त फोरग्राउंडमध्ये असताना तुमचे अचूक स्थान मिळवू शकते. तुमच्या डिव्हाइसवर स्थान सेवा सुरू असणे आणि उपलब्ध असणे आवश्यक आहे जेणेकरून ॲप त्यांचा वापर करू शकतील."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"बॅकग्राउंडमध्ये स्थान अॅक्सेस करू शकतो"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"याला अंदाजे किंवा अचूक स्थान अॅक्सेस करण्यास अतिरिक्त मंजूरी दिल्यास, बॅकग्राउंडमध्ये चालतांना अॅप स्थान अॅक्सेस करू शकतो."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"हे अॅप फोरग्राउंड स्थान ॲक्सेस व्यतिरिक्त, बॅकग्राउंडमध्ये सुरू असताना स्थान ॲक्सेस करू शकते."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"आपल्या ऑडिओ सेटिंग्ज बदला"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"व्हॉल्यूम आणि आउटपुटसाठी कोणता स्पीकर वापरला आहे यासारख्या समग्र ऑडिओ सेटिंग्ज सुधारित करण्यासाठी अॅप ला अनुमती देते."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ऑडिओ रेकॉर्ड"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"टॅबलेटवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Android TV डिव्हाइसवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन तयार करण्यासाठी आणि स्वीकारण्यासाठी, ॲपला अनुमती देते."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"फोनवर ब्लूटूथ चे कॉंफिगरेशन पाहण्यासाठी आणि पेअर केलेल्या डीव्हाइससह कनेक्शन इंस्टॉल करण्यासाठी आणि स्वीकारण्यासाठी, अॅप ला अनुमती देते."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"फील्ड जवळील कम्युनिकेशन नियंत्रित करा"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"फील्ड जवळील कम्युनिकेशन (NFC) टॅग, कार्डे आणि वाचक यांच्यासह संवाद करण्यासाठी ॲपला अनुमती देते."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"तुमचे स्क्रीन लॉक अक्षम करा"</string>
@@ -900,7 +901,7 @@
<string name="factorytest_no_action" msgid="339252838115675515">"FACTORY_TEST क्रिया प्रदान करणारे कोणतेही पॅकेज आढळले नाही."</string>
<string name="factorytest_reboot" msgid="2050147445567257365">"रीबूट करा"</string>
<string name="js_dialog_title" msgid="7464775045615023241">"\"<xliff:g id="TITLE">%s</xliff:g>\" वरील पृष्ठ हे म्हणते:"</string>
- <string name="js_dialog_title_default" msgid="3769524569903332476">"Javascript"</string>
+ <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
<string name="js_dialog_before_unload_title" msgid="7012587995876771246">"नेव्हिगेशनची पुष्टी करा"</string>
<string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"हे पृष्ठ सोडा"</string>
<string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"या पेजवर रहा"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> शी कनेक्ट केलेले"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"फायली पाहण्यासाठी टॅप करा"</string>
<string name="pin_target" msgid="8036028973110156895">"पिन"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"अनपिन करा"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"अॅप माहिती"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"डेमो प्रारंभ करत आहे..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"हे आयटम "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> आणि <xliff:g id="TYPE_2">%3$s</xliff:g> मध्ये अपडेट करायचे का?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"सेव्ह करा"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"नाही, नको"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"आता नाही"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"कधीही नाही"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"अपडेट करा"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"पुढे सुरू ठेवा"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"पासवर्ड"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"विभाजित स्क्रीन टॉगल करा"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"स्क्रीन लॉक करा"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"स्क्रीनशॉट"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"पॉप-अप विंडोमध्ये <xliff:g id="APP_NAME">%1$s</xliff:g> ॲप."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> चा शीर्षक बार."</string>
</resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index a40eca0..0670fad 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Apl pentadbir profil kerja tiada atau rosak. Akibatnya, profil kerja anda dan data yang berkaitan telah dipadamkan. Hubungi pentadbir anda untuk mendapatkan bantuan."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profil kerja anda tidak lagi tersedia pada peranti ini"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Terlalu banyak percubaan kata laluan"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Pentadbir melepaskan peranti untuk kegunaan peribadi"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Peranti ini diurus"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Organisasi anda mengurus peranti ini dan mungkin memantau trafik rangkaian. Ketik untuk mendapatkan butiran."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Peranti anda akan dipadam"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Membenarkan apl menghantar siaran lekit, yang kekal selepas siaran tamat. Penggunaan secara berlebihan boleh menyebabkan peranti Android TV anda menjadi perlahan atau tidak stabil kerana menggunakan terlalu banyak memori."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Membenarkan apl untuk menghantar siaran melekit, yang kekal selepas siaran berakhir. Penggunaan berlebihan boleh membuat telefon perlahan atau tidak stabil dengan menyebabkannya menggunakan memori yang terlalu banyak."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"baca kenalan anda"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Membenarkan apl membaca data tentang kenalan anda yang tersimpan di tablet anda, termasuk kekerapan anda memanggil, menghantar e-mel atau berkomunikasi dalam cara lain dengan individu tertentu. Kebenaran ini membenarkan apl untuk menyimpan data kenalan anda dan apl hasad boleh berkongsi data kenalan anda tanpa pengetahuan anda."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Membenarkan apl membaca data tentang kenalan anda yang tersimpan pada peranti Android TV, termasuk kekerapan anda membuat panggilan, menghantar e-mel atau berkomunikasi melalui cara lain dengan individu tertentu. Kebenaran ini membenarkan apl menyimpan data kenalan anda dan apl hasad mungkin berkongsi data kenalan tanpa pengetahuan anda."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Membenarkan apl membaca data tentang kenalan anda yang tersimpan di telefon anda, termasuk kekerapan anda memanggil, menghantar e-mel atau berkomunikasi dalam cara lain dengan individu tertentu. Kebenaran ini membenarkan apl untuk menyimpan data kenalan anda dan apl hasad boleh berkongsi data kenalan anda tanpa pengetahuan anda."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Membenarkan apl membaca data tentang kenalan anda yang tersimpan pada tablet anda. Apl juga akan mempunyai akses kepada akaun pada tablet anda yang telah dibuat kenalan. Ini mungkin termasuk akaun yang dibuat oleh apl yang telah anda pasang. Kebenaran ini membolehkan apl menyimpan data kenalan anda dan apl hasad mungkin berkongsi data kenalan tanpa pengetahuan anda."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Membenarkan apl membaca data tentang kenalan anda yang tersimpan pada peranti Android TV anda. Apl juga akan mempunyai akses kepada akaun pada peranti Android TV anda yang telah dibuat kenalan. Ini mungkin termasuk akaun yang dibuat oleh apl yang telah anda pasang. Kebenaran ini membolehkan apl menyimpan data kenalan anda dan apl hasad mungkin berkongsi data kenalan tanpa pengetahuan anda."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Membenarkan apl membaca data tentang kenalan anda yang tersimpan pada telefon anda. Apl juga mempunyai akses kepada akaun pada telefon anda yang telah dibuat kenalan. Ini mungkin termasuk akaun yang dibuat oleh apl yang telah anda pasang. Kebenaran ini membolehkan apl menyimpan data kenalan anda dan apl hasad mungkin berkongsi data kenalan tanpa pengetahuan anda."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ubah suai kenalan anda"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Membenarkan apl mengubah suai data tentang kenalan anda yang tersimpan pada tablet anda, termasuk kekerapan siapa anda panggil, hantar e-mel atau berkomunikasi dalam cara lain dengan kenalan tertentu. Kebenaran ini membenarkan apl untuk memadam data kenalan."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Membenarkan apl mengubah suai data tentang kenalan anda yang tersimpan pada peranti Android TV anda, termasuk kekerapan anda membuat panggilan, menghantar e-mel atau berkomunikasi melalui cara lain dengan kenalan tertentu. Kebenaran ini membenarkan apl memadam data kenalan."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Membenarkan apl mengubah suai data tentang kenalan anda yang tersimpan pada telefon anda, termasuk kekerapan siapa anda panggil, hantar e-mel atau berkomunikasi dalam cara lain dengan kenalan tertentu. Kebenaran ini membenarkan apl untuk memadam data kenalan."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Membenarkan apl mengubah suai data tentang kenalan anda yang tersimpan pada tablet anda. Kebenaran ini membenarkan apl memadamkan data kenalan."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Membenarkan apl mengubah suai data tentang kenalan anda yang tersimpan pada peranti Android TV anda. Kebenaran ini membenarkan apl memadamkan data kenalan."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Membenarkan apl mengubah suai data tentang kenalan anda yang tersimpan pada telefon anda. Kebenaran ini membenarkan apl memadamkan data kenalan."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"baca log panggilan"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Apl ini boleh membaca sejarah panggilan anda."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"tulis log panggilan"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"akses perintah tambahan pembekal lokasi"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Membenarkan apl mengakses arahan pembekal lokasi tambahan. Ini boleh membenarkan apl untuk campur tangan dengan operasi GPS atau sumber lokasi lain."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"akses lokasi tepat hanya di latar depan"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Apl ini boleh mendapatkan lokasi tepat anda hanya apabila apl tersebut berada di latar depan. Perkhidmatan lokasi ini mesti dihidupkan dan tersedia pada telefon anda untuk membolehkan apl menggunakan perkhidmatan tersebut. Tindakan ini mungkin meningkatkan penggunaan bateri."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"akses lokasi anggaran (berdasarkan rangkaian) hanya di latar depan"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Apl ini boleh mendapatkan lokasi anda berdasarkan sumber rangkaian seperti menara selular dan rangkaian Wi-Fi, tetapi hanya apabila apl itu di latar depan. Perkhidmatan lokasi ini mesti dihidupkan dan tersedia pada tablet anda untuk membolehkan apl menggunakan perkhidmatan tersebut."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Apl ini boleh mendapatkan lokasi anda berdasarkan sumber rangkaian seperti menara selular dan rangkaian Wi-Fi, tetapi hanya apabila apl itu di latar depan. Perkhidmatan lokasi ini mesti dihidupkan dan tersedia pada peranti Android TV anda untuk membolehkan apl menggunakan perkhidmatan tersebut."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Apl ini boleh mendapatkan lokasi anda berdasarkan sumber rangkaian seperti menara selular dan rangkaian Wi-Fi, tetapi hanya apabila apl itu di latar depan. Perkhidmatan lokasi ini mesti dihidupkan dan tersedia pada telefon anda untuk membolehkan apl menggunakan perkhidmatan tersebut."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Apl ini boleh mendapatkan lokasi tepat anda hanya apabila apl tersebut berada di latar depan. Perkhidmatan lokasi mesti dihidupkan dan tersedia pada peranti anda untuk membolehkan apl menggunakan perkhidmatan tersebut. Tindakan ini mungkin meningkatkan penggunaan bateri."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"akses lokasi anggaran hanya di latar depan"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Apl ini boleh mendapatkan lokasi anggaran anda hanya apabila apl tersebut berada di latar depan. Perkhidmatan lokasi mesti dihidupkan dan tersedia pada peranti anda untuk membolehkan apl menggunakan perkhidmatan tersebut."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"akses lokasi di latar belakang"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Jika tindakan ini dibenarkan bagi akses lokasi anggaran atau lokasi tepat, apl tersebut boleh mengakses lokasi itu semasa berjalan di latar belakang."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Apl ini boleh mengakses lokasi semasa berjalan di latar belakang, di samping akses lokasi di latar depan."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"tukar tetapan audio anda"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Membenarkan apl untuk mengubah suai tetapan audio global seperti kelantangan dan pembesar suara mana digunakan untuk output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"rakam audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Membenarkan apl melihat konfigurasi Bluetooth pada tablet dan untuk membuat serta menerima sambungan dengan peranti yang dipasangkan."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Membenarkan apl melihat konfigurasi Bluetooth pada peranti Android TV anda dan membuat serta menerima sambungan dengan peranti yang digandingkan."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Membenarkan apl melihat konfigurasi Bluetooth pada telefon dan membuat serta menerima sambungan dengan peranti yang dipasangkan."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"mengawal Komunikasi Medan Dekat"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Membenarkan apl berkomunikasi dengan teg, kad dan pembaca Komunikasi Medan Dekat (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"lumpuhkan kunci skrin anda"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Disambungkan ke <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Ketik untuk melihat fail"</string>
<string name="pin_target" msgid="8036028973110156895">"Semat"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Nyahsemat"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Maklumat apl"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Memulakan tunjuk cara…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Kemas kini item ini dalam "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> dan <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Simpan"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Tidak, terima kasih"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Bukan sekarang"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Jangan"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Kemas kini"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Teruskan"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"kata laluan"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Togol Skrin Pisah"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Skrin Kunci"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Tangkapan skrin"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Apl <xliff:g id="APP_NAME">%1$s</xliff:g> dalam tetingkap Timbul."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Bar kapsyen <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 64e2376..67c5f89 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"အလုပ်ပရိုဖိုင် စီမံခန့်ခွဲရန်အက်ပ် မရှိပါ သို့မဟုတ် ပျက်စီးနေပါသည်။ ထို့ကြောင့် သင်၏ အလုပ်ပရိုဖိုင်နှင့် ဆက်စပ်နေသော ဒေတာများကို ဖျက်လိုက်ပါပြီ။ အကူအညီရယူရန် သင်၏စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ဤစက်ပစ္စည်းတွင် သင်၏ အလုပ်ပရိုဖိုင်မရှိတော့ပါ"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"စကားဝှက်ထည့်သွင်းရန် ကြိုးစားသည့် အကြိမ်အရေအတွက် အလွန်များသွား၍ ဖြစ်ပါသည်"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"ပုဂ္ဂိုလ်ရေးအသုံးပြုရန်အတွက် စီမံခန့်ခွဲသူက စက်ပစ္စည်းထိန်းချုပ်မှုကို ရပ်တန့်လိုက်သည်"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"စက်ပစ္စည်းကို စီမံခန့်ခွဲထားပါသည်"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"ဤစက်ပစ္စည်းကို သင်၏ အဖွဲ့အစည်းက စီမံပြီး ကွန်ရက်အသွားအလာကို စောင့်ကြည့်နိုင်ပါသည်။ ထပ်မံလေ့လာရန် တို့ပါ။"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"သင့်ကိရိယာအား ပယ်ဖျက်လိမ့်မည်"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ထုတ်လွှင့်ခြင်းများ ပြီးဆုံးသွားသည့်နောက် ဆက်လက်တည်ရှိနေသည့် ထုတ်လွှင့်မှုများကို အက်ပ်အား ပို့ခွင့်ပြုသည်။ အလွန်အကျွံအသုံးပြုပါက မှတ်ဉာဏ်အသုံးပြုမှု လွန်ကဲပြီး သင့် Android TV စက်ကို နှေးကွေးစေခြင်း သို့မဟုတ် မတည်ငြိမ်ခြင်းတို့ ဖြစ်စေနိုင်သည်။"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"အက်ပ်အား ကြာရှည်ခံ ထုတ်လွှင့်မှု ပြုပါ။ ဤထုတ်လွှင့်မှုများဟာ ထုတ်လွှင့်မှု ပြီးဆုံးပြီးသွားတည့်တိုင် ကျန်နေမည် ဖြစ်ပါသည်။ အလွန်အကျွံသုံးခြင်းကြောင့် မှတ်ဉာဏ်အသုံးများပြီး ဖုန်းနှေးခြင်း၊ မတည်ငြိမ်ခြင်း ဖြစ်နိုင်ပါသည်"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"အဆက်အသွယ်များအား ဖတ်ခြင်း"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"အပလီကေးရှင်းအား ခေါ်ဆိုသော အကြိမ်ရေ၊ အီးမေးလ်အကြိမ်ရေ၊ တခြားဆက်သွယ်မှုများစသည်ကဲ့သို့ သင့်တက်ဘလက်မှာ သိမ်းဆည်းထားသော အဆက်အသွယ်များရဲ့ အချက်အလက်ကို ဖတ်ခွင့်ပြုပါ။ ဤသို့ခွင့်ပြုခြင်းအားဖြင့် အပလီကေးရှင်းများကို သင့် အဆက်အသွယ်၏ အချက်မလက်များကို သိမ်းရန် ခွင့်ပြုပြီး အန္တရာယ်ရှိသော အပလီကေးရှင်းများမှ ထိုအချက်အလက်များ ကို သင် မသိစေပဲ ဖြန့်ဝေနိုင််မည် ဖြစ်ပါသည်။"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"အချို့သော လူပုဂ္ဂိုလ်များသို့ ခေါ်ဆိုသော၊ အီးမေးလ်ပို့သော သို့မဟုတ် အခြားနည်းလမ်းဖြင့် ဆက်သွယ်သော အကြိမ်ရေများအပါအဝင် သင့် Android TV စက်ပေါ်တွင် သိမ်းဆည်းထားသည့် အဆက်အသွယ်များအကြောင်း ဒေတာများကို အက်ပ်အား မွမ်းမံခွင့်ပြုသည်။ ဤခွင့်ပြုချက်သည် သင်၏ အဆက်အသွယ်ဒေတာကို အက်ပ်အား သိမ်းခွင့်ပေးသောကြောင့် သံသယဖြစ်ဖွယ်အက်ပ်များသည် သင်မသိဘဲ အဆက်အသွယ်ဒေတာများကို မျှဝေနိုင်သည်။"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"အပလီကေးရှင်းအား ခေါ်ဆိုသော အကြိမ်ရေ၊ အီးမေးလ်အကြိမ်ရေ၊ တခြားဆက်သွယ်မှုများစသည်ကဲ့သို့ သင့်ဖုန်းမှာ သိမ်းဆည်းထားသော အဆက်အသွယ်များရဲ့ အချက်အလက်ကို ဖတ်ခွင့်ပြုပါ။ ဤသို့ခွင့်ပြုခြင်းအားဖြင့် အပလီကေးရှင်းများကို သင့် အဆက်အသွယ်၏ အချက်မလက်များကို သိမ်းရန် ခွင့်ပြုပြီး အန္တရာယ်ရှိသော အပလီကေးရှင်းများမှ ထိုအချက်အလက်များ ကို သင် မသိစေပဲ ဖြန့်ဝေနိုင််မည် ဖြစ်ပါသည်။"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"သင့်တက်ဘလက်ထဲတွင် သိမ်းဆည်းထားသော အဆက်အသွယ်များ၏ အချက်အလက်ကို အက်ပ်အား ဖတ်ခွင့်ပြုသည်။ အက်ပ်သည် သင့်တက်ဘလက် မှတစ်ဆင့် အဆက်အသွယ်များကို ပြုလုပ်ထားသော အကောင့်များကို အသုံးပြုခွင့် ရလိမ့်မည်။ သင်ထည့်သွင်းထားသော အက်ပ်များမှတစ်ဆင့် ပြုလုပ်ထားသော အကောင့်များ ပါဝင်နိုင်ပါသည်။ ဤခွင့်ပြုချက်က သင့်အဆက်အသွယ်၏ အချက်အလက်များကို အက်ပ်များအား သိမ်းခွင့်ပြုပြီး အန္တရာယ်ရှိသော အက်ပ်များက ထိုအချက်အလက်များကို သင်မသိဘဲ ဖြန့်ဝေနိုင်သည်။"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"သင်၏ Android TV ထဲတွင် သိမ်းဆည်းထားသော အဆက်အသွယ်များ၏ အချက်အလက်ကို အက်ပ်အား ဖတ်ခွင့်ပြုသည်။ အက်ပ်သည် သင့် Android TV မှတစ်ဆင့် အဆက်အသွယ်များကို ပြုလုပ်ထားသော အကောင့်များကို အသုံးပြုခွင့် ရလိမ့်မည်။ သင်ထည့်သွင်းထားသော အက်ပ်များမှတစ်ဆင့် ပြုလုပ်ထားသော အကောင့်များ ပါဝင်နိုင်ပါသည်။ ဤခွင့်ပြုချက်က သင့်အဆက်အသွယ်၏ အချက်အလက်များကို အက်ပ်များအား သိမ်းခွင့်ပြုပြီး အန္တရာယ်ရှိသော အက်ပ်များက ထိုအချက်အလက်များကို သင်မသိဘဲ ဖြန့်ဝေနိုင်သည်။"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"သင်၏ ဖုန်းထဲတွင် သိမ်းဆည်းထားသော အဆက်အသွယ်များ၏ အချက်အလက်ကို အက်ပ်အား ဖတ်ခွင့်ပြုသည်။ အက်ပ်သည် သင့်ဖုန်းမှတစ်ဆင့် အဆက်အသွယ်များကို ပြုလုပ်ထားသော အကောင့်များကို အသုံးပြုခွင့် ရလိမ့်မည်။ သင်ထည့်သွင်းထားသော အက်ပ်များမှတစ်ဆင့် ပြုလုပ်ထားသော အကောင့်များ ပါဝင်နိုင်ပါသည်။ ဤခွင့်ပြုချက်က သင့်အဆက်အသွယ်၏ အချက်အလက်များကို အက်ပ်များအား သိမ်းခွင့်ပြုပြီး အန္တရာယ်ရှိသော အက်ပ်များက ထိုအချက်အလက်များကို သင်မသိဘဲ ဖြန့်ဝေနိုင်သည်။"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"အဆက်အသွယ်များအား ပြင်ဆင်ခြင်း"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"အပလီကေးရှင်းအား သင့်တက်ဘလက်မှာ သိမ်းဆည်းထားသော အဆက်အသွယ်များရဲ့ အချက်အလက် (အထူးအဆက်အသွယ်များအား ခေါ်ဆိုသော အကြိမ်ရေ၊ အီးမေးလ်ပို့သောအကြိမ်ရေ သို့ အခြားနည်းလမ်းဖြင့်ဆက်သွယ်မှုများ) ကို ပြင်ဆင်ခွင့်ပြုခြင်း။ ဒီခွင့်ပြုချက်က အပလီကေးရှင်းများအား အဆက်အသွယ် အချက်အလက်များ ဖျက်စီးခြင်း လုပ်ဆောင်စေနိုင်မှာ ဖြစ်ပါသည်။"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"အချို့သော အဆက်အသွယ်များသို့ ခေါ်ဆိုသော၊ အီးမေးလ်ပို့သော သို့မဟုတ် အခြားနည်းလမ်းဖြင့် ဆက်သွယ်သော အကြိမ်ရေများအပါအဝင် သင့် Android TV စက်ပေါ်တွင် သိမ်းဆည်းထားသည့် အဆက်အသွယ်များအကြောင်း ဒေတာများကို အက်ပ်အား မွမ်းမံခွင့်ပြုသည်။ ဤခွင့်ပြုချက်သည် အဆက်အသွယ်ဒေတာကို အက်ပ်အား ဖျက်ခွင့်ပေးသည်။"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"အပလီကေးရှင်းအား သင့်ဖုန်းမှာ သိမ်းဆည်းထားသော အဆက်အသွယ်များရဲ့ အချက်အလက် (အထူးအဆက်အသွယ်များအား ခေါ်ဆိုသော အကြိမ်ရေ၊ အီးမေးလ်ပို့သောအကြိမ်ရေ သို့ အခြားနည်းလမ်းဖြင့်ဆက်သွယ်မှုများ) ကို ပြင်ဆင်ခွင့်ပြုခြင်း။ ဒီခွင့်ပြုချက်က အပလီကေးရှင်းများအား အဆက်အသွယ် အချက်အလက်များ ဖျက်စီးခြင်း လုပ်ဆောင်စေနိုင်မှာ ဖြစ်ပါသည်။"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"သင့်တက်ဘလက်တွင် သိမ်းဆည်းထားသော အဆက်အသွယ်များ၏ အချက်အလက်ကို ပြင်ဆင်ရန် အက်ပ်အား ခွင့်ပြုပါ။ ဤခွင့်ပြုချက်က အက်ပ်အား သင့်အဆက်အသွယ်များ၏ အချက်အလက်ကို ဖျက်ခွင့်ပြုသည်။"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"သင့် Android TV တွင် သိမ်းဆည်းထားသော အဆက်အသွယ်များ၏ အချက်အလက်ကို ပြင်ဆင်ရန် အက်ပ်အား ခွင့်ပြုပါ။ ဤခွင့်ပြုချက်က အက်ပ်အား သင့်အဆက်အသွယ်များ၏ အချက်အလက်ကို ဖျက်ခွင့်ပြုသည်။"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"သင့်ဖုန်းတွင် သိမ်းဆည်းထားသော အဆက်အသွယ်များ၏ အချက်အလက်ကို ပြင်ဆင်ရန် အက်ပ်အား ခွင့်ပြုပါ။ ဤခွင့်ပြုချက်က အက်ပ်အား သင့်အဆက်အသွယ်များ၏ အချက်အလက်ကို ဖျက်ခွင့်ပြုသည်။"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ခေါ်ဆိုမှု မှတ်တမ်းအား ဖတ်ခြင်း"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ဤအက်ပ်သည် သင့်ခေါ်ဆိုမှုမှတ်တမ်းကို ကြည့်ရှုနိုင်ပါသည်။"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"ခေါ်ဆိုမှုမှတ်တမ်း ရေးသားခြင်း"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"တည်နေရာပံ့ပိုးမှုညွှန်ကြားချက်အပိုအား ဝင်ရောက်ကြည့်ခြင်း"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"အက်ပ်အား တည်နေရာ စီမံပေးရေး ညွှန်ကြားချက် အပိုများကို ရယူခွင့်ပြုသည်။ သို့ဖြစ်၍ အက်ပ်သည် GPS သို့မဟုတ် အခြား တည်နေရာ ရင်းမြစ်ကို သုံးကြသူတို့၏ လုပ်ငန်းများကို ဝင်စွက်ခွင့် ပြုနိုင်သည်။"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"မျက်နှာစာတွင်သာ တည်နေရာအတိအကျ အသုံးပြုခြင်း"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"မျက်နှာစာတွင် ဖွင့်ထားမှသာ ဤအက်ပ်က သင်၏တည်နေရာအတိအကျကို ရယူနိုင်ပါသည်။ သင်၏ဖုန်းတွင် အက်ပ်ကအသုံးပြုရန်အတွက် ဤတည်နေရာဝန်ဆောင်မှုများကို ဖွင့်ထားပြီး အသုံးပြု၍ ရပါမည်။ ၎င်းက ဘက်ထရီ ပိုကုန်နိုင်ပါသည်။"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"မျက်နှာစာတွင်သာ (ကွန်ရက် အခြေပြု) တည်နေရာခန့်မှန်း အသုံးပြုခြင်း"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ဤအက်ပ်က ဆဲလ်တာဝါများနှင့် Wi-Fi ကွန်ရက်များကဲ့သို့ ကွန်ရက်ရင်းမြစ်များအပေါ် အခြေခံပြီး သင်၏တည်နေရာကို ရယူနိုင်သော်လည်း အက်ပ်ကို မျက်နှာစာတွင်ဖွင့်ထားမှ ရပါမည်။ အက်ပ်က အသုံးပြုနိုင်ရန်အတွက် ဤတည်နေရာ ဝန်ဆောင်မှုများကို ဖွင့်ထားရမည် ဖြစ်ပြီး သင့်တက်ဘလက်ပေါ်တွင် ရရှိနိုင်ရပါမည်။"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"ဤအက်ပ်သည် ဆဲလ်တာဝါများနှင့် Wi-Fi ကွန်ရက်များကဲ့သို့ ကွန်ရက်ရင်းမြစ်များပေါ်တွင် အခြေခံပြီး သင်၏တည်နေရာကို ရယူနိုင်သည်၊ သို့သော် အက်ပ်ကို မျက်နှာစာတွင်ဖွင့်ထားရပါမည်။ အက်ပ်က အသုံးပြုနိုင်ရန်အတွက် ဤတည်နေရာ ဝန်ဆောင်မှုများကို ဖွင့်ထားရမည် ဖြစ်ပြီး သင့် Android TV ပေါ်တွင် ရှိမှသာ သုံးနိုင်ပါမည်။"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ဤအက်ပ်က ဆဲလ်တာဝါများနှင့် Wi-Fi ကွန်ရက်များကဲ့သို့ ကွန်ရက်ရင်းမြစ်များအပေါ် အခြေခံပြီး သင်၏တည်နေရာကို ရယူနိုင်သော်လည်း အက်ပ်ကို မျက်နှာစာတွင်ဖွင့်ထားမှ ရပါမည်။ အက်ပ်က အသုံးပြုနိုင်ရန်အတွက် ဤတည်နေရာ ဝန်ဆောင်မှုများကို ဖွင့်ထားရမည် ဖြစ်ပြီး သင့်ဖုန်းပေါ်တွင် ရရှိနိုင်ရပါမည်။"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"မျက်နှာစာတွင် ဖွင့်ထားမှသာ ဤအက်ပ်က သင်၏ တည်နေရာအတိအကျကို ရယူနိုင်ပါသည်။ သင်၏ စက်တွင် အက်ပ်ကအသုံးပြုရန်အတွက် တည်နေရာဝန်ဆောင်မှုများကို ဖွင့်ထားပြီးမှ အသုံးပြု၍ ရပါမည်။ ၎င်းက ဘက်ထရီ ပိုကုန်နိုင်ပါသည်။"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"မျက်နှာစာတွင်သာ ခန့်မှန်းခြေ တည်နေရာ အသုံးပြုခြင်း"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"မျက်နှာစာတွင် ဖွင့်ထားမှသာ ဤအက်ပ်က သင်၏ အနီးစပ်ဆုံးတည်နေရာကို ရယူနိုင်ပါသည်။ အက်ပ်က ဤတည်နေရာဝန်ဆောင်မှုများကို အသုံးပြုရန်အတွက် သင်၏စက်တွင် ၎င်းတို့ရှိနေပြီး ဖွင့်ထားရပါမည်။"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"နောက်ခံတွင် တည်နေရာကို အသုံးပြုရန်"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ခန့်မှန်းခြေ သို့မဟုတ် တိကျသော တည်နေရာ ဝင်သုံးခွင့်အတွက် ၎င်းကို နောက်ဆက်တွဲ ခွင့်ပြုထားပါက နောက်ခံတွင် လုပ်ဆောင်နေစဉ် အက်ပ်က တည်နေရာကို ရယူအသုံးပြုနိုင်ပါသည်။"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"နောက်ခံတွင် လုပ်ဆောင်နေစဉ် ဤအက်ပ်က တည်နေရာကို ရယူအသုံးပြုနိုင်သည်သာမက မျက်နှာစာတည်နေရာကိုပါ အသုံးပြုနိုင်မည်။"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"သင့်အသံအပြင်အဆင်အားပြောင်းခြင်း"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"အပလီကေးရှင်းအား အသံအတိုးအကျယ်နှင့် အထွက်ကို မည်သည့်စပီကာကို သုံးရန်စသည်ဖြင့် စက်တစ်ခုလုံးနှင့်ဆိုင်သော အသံဆိုင်ရာ ဆက်တင်များ ပြင်ဆင်ခွင့် ပြုရန်"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"အသံဖမ်းခြင်း"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"အပလီကေးရှင်းအား တက်ဘလက်ပေါ်မှ ဘလူးတုသ် အပြင်အဆင်အား ကြည့်ခွင့်၊ တခြားစက်များနဲ့ ဆက်သွယ်ခြင်း၊ ဆက်သွယ်ခြင်းကို လက်ခံခွင့်ပြုပါ။"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"သင့် Android TV စက်ပစ္စည်းပေါ်ရှိ ဘလူးတုသ် စီစဉ်သတ်မှတ်ချက်များကို ကြည့်ရှုခွင့်အပြင် တွဲချိတ်ထားသည့် စက်ပစ္စည်းများနှင့် ချိတ်ဆက်မှုပြုလုပ်ခြင်းနှင့် လက်ခံခြင်းတို့ကို အက်ပ်အား ပြုလုပ်ခွင့်ပေးသည်။"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"အပလီကေးရှင်းအား ဖုန်းမှဘလူးတု အပြင်အဆင်အား ကြည့်ခွင့်၊ တခြားစက်များနဲ့ ဆက်သွယ်ခြင်း၊ ဆက်သွယ်ခြင်းကို လက်ခံခွင့်ပြုပါ။"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communicationအား ထိန်းချုပ်ရန်"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"အက်ပ်အား တာတို စက်ကွင်း ဆက်သွယ်ရေး (NFC) တဲဂ်များ၊ ကဒ်များ နှင့် ဖတ်ကြသူတို့နှင့် ဆက်သွယ်ပြောဆိုခွင့် ပြုသည်။"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ဖန်သားပြင် သော့ချခြင်းအား မလုပ်နိုင်အောင် ပိတ်ရန်"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> ချိတ်ဆက်ထားသည်"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ဖိုင်များကိုကြည့်ရန် တို့ပါ"</string>
<string name="pin_target" msgid="8036028973110156895">"တွဲပါ"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"ဖြုတ်ပါ"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"အက်ပ်အချက်အလက်"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"သရုပ်ပြချက်ကို စတင်နေသည်…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"ဤအချက်အလက်များကို "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"တွင် အပ်ဒိတ်လုပ်လိုပါသလား- <xliff:g id="TYPE_0">%1$s</xliff:g>၊ <xliff:g id="TYPE_1">%2$s</xliff:g> နှင့် <xliff:g id="TYPE_2">%3$s</xliff:g>။"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"သိမ်းရန်"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"မလိုပါ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ယခုမလုပ်ပါ"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ဘယ်တော့မှ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"အပ်ဒိတ်လုပ်ရန်"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ရှေ့ဆက်ရန်"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"စကားဝှက်"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းကို နှိပ်ပါ"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"လော့ခ်မျက်နှာပြင်"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ဖန်သားပြင်ဓာတ်ပုံ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"ပေါ့ပ်အပ်ဝင်းဒိုးတွင်ရှိသော <xliff:g id="APP_NAME">%1$s</xliff:g>အက်ပ်။"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>၏ ခေါင်းစီး ဘား။"</string>
</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 69e341e..dc3ce6e 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Administratorappen for jobbprofilen mangler eller er skadet. Dette har ført til at jobbprofilen og alle data knyttet til den, har blitt slettet. Ta kontakt med administratoren for å få hjelp."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Jobbprofilen din er ikke lenger tilgjengelig på denne enheten"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"For mange passordforsøk"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratoren overførte enheten til personlig bruk"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Enheten administreres"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Organisasjonen din kontrollerer denne enheten og kan overvåke nettverkstrafikk. Trykk for å få mer informasjon."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Enheten blir slettet"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Lar appen sende varige kringkastinger («sticky broadcasts»), som ikke avsluttes etter at kringkastingen er over. Overdreven bruk kan gjøre Android TV-enheten din treg eller ustabil ved at den bruker for mye minne."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Lar appen sende faste kringkastinger («sticky broadcasts») som blir værende etter at kringkastingen er over. Overdreven bruk kan gjøre telefonen treg eller ustabil ved å bruke for mye minne."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lese kontaktene dine"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Lar appen lese informasjon om kontaktene lagret på nettbrettet ditt, inkludert hvor ofte du har ringt, sendt e-post til, eller på andre måter kommunisert med spesifikke personer. Denne tillatelsen lar apper lagre kontaktdata. Merk at skadelige apper kan dele disse dataene uten at du vet om det."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Lar appen lese data om kontaktene som er lagret på Android TV-enheten din, inkludert hvor hyppig du har ringt, sendt e-post eller på andre måter har kommunisert med bestemte kontakter. Denne tillatelsen lar apper lagre kontaktdata, og skadelige apper kan dele dem uten ditt samtykke."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Lar appen lese informasjon om kontaktene lagret på telefonen din, inkludert hvor ofte du har ringt, sendt e-post til eller på andre måter kommunisert med spesifikke personer. Denne tillatelsen lar apper lagre kontaktdata. Merk at skadelige apper kan dele disse dataene uten at du vet om det."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Lar appen lese data om kontaktene dine som er lagret på nettbrettet ditt. Apper får også tilgang til kontoene på nettbrettet ditt som har opprettet kontakter. Dette kan inkludere kontoer som er opprettet av apper du har installert. Denne tillatelsen lar apper lagre kontaktdataene dine. Vær oppmerksom på at skadelige apper kan dele disse dataene uten at du vet om det."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Lar appen lese data om kontaktene dine som er lagret på Android TV-enheten din. Apper får også tilgang til kontoene på Android TV-enheten din som har opprettet kontakter. Dette kan inkludere kontoer som er opprettet av apper du har installert. Denne tillatelsen lar apper lagre kontaktdataene dine. Vær oppmerksom på at skadelige apper kan dele disse dataene uten at du vet om det."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Lar appen lese data om kontaktene dine som er lagret på telefonen din. Apper får også tilgang til kontoene på telefonen din som har opprettet kontakter. Dette kan inkludere kontoer som er opprettet av apper du har installert. Denne tillatelsen lar apper lagre kontaktdataene dine. Vær oppmerksom på at skadelige apper kan dele disse dataene uten at du vet om det."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"endre kontaktene dine"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Lar appen endre informasjon om kontaktene lagret på nettbrettet ditt, inkludert hvor ofte du har ringt, sendt e-post til eller på andre måter kommunisert med bestemte kontakter. Denne tillatelsen lar apper slette kontaktdata."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Lar appen endre informasjon om kontaktene som er lagret på Android TV-enheten din, inkludert hvor ofte du har ringt, sendt e-post til eller på andre måter kommunisert med bestemte kontakter. Denne tillatelsen lar apper slette kontaktdata."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Lar appen endre informasjon om kontaktene lagret på telefonen din, inkludert hvor ofte du har ringt, sendt e-post til eller på andre måter kommunisert med bestemte kontakter. Denne tillatelsen lar apper slette kontaktdata."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Lar appen endre informasjon om kontaktene som er lagret på nettbrettet ditt. Denne tillatelsen lar apper slette kontaktdata."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Lar appen endre informasjon om kontaktene som er lagret på Android TV-enheten din. Denne tillatelsen lar apper slette kontaktdata."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Lar appen endre informasjon om kontaktene som er lagret på telefonen din. Denne tillatelsen lar apper slette kontaktdata."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lese anropsloggen"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Denne appen kan lese anropsloggen din."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"endre anropsloggen"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"bruke ekstra posisjonskommandoer"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Appen gis tillatelse til å bruke ekstra kommandoer fra posisjonsleverandører. Dette kan gi appen tillatelse til å påvirke bruken av GPS eller andre posisjonskilder."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"bare tilgang til nøyaktig posisjon i forgrunnen"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Denne appen kan bare få den nøyaktige posisjonen din når den er på i forgrunnen. Disse posisjonstjenestene må være slått på og tilgjengelige på telefonen din for at appen skal kunne bruke dem. Dette kan øke batteriforbruket."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"bare tilgang til omtrentlig posisjon (nettverksbasert) i forgrunnen"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Denne appen kan bare få posisjonen din basert på nettverkskilder, for eksempel mobilmaster og Wi-Fi-nettverk, når den er på i forgrunnen. Disse posisjonstjenestene må være slått på og tilgjengelige på nettbrettet ditt for at appen skal kunne bruke dem."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Denne appen kan bare få posisjonen din basert på nettverkskilder, for eksempel mobilmaster og Wi-Fi-nettverk, når den er på i forgrunnen. Disse posisjonstjenestene må være slått på og tilgjengelige på Android TV-en din for at appen skal kunne bruke dem."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Denne appen kan bare få posisjonen din basert på nettverkskilder, for eksempel mobilmaster og Wi-Fi-nettverk, når den er på i forgrunnen. Disse posisjonstjenestene må være slått på og tilgjengelige på telefonen din for at appen skal kunne bruke dem."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Denne appen kan bare få den nøyaktige posisjonen din når den er på i forgrunnen. Posisjonstjenestene må være slått på og tilgjengelige på enheten din for at appen skal kunne bruke dem. Dette kan øke batteriforbruket."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"bare tilgang til omtrentlig posisjon i forgrunnen"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Denne appen kan bare få den omtrentlige posisjonen din når den er i forgrunnen. Posisjonstjenestene må være slått på og tilgjengelige på enheten din for at appen skal kunne bruke dem."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"tilgang til posisjon i bakgrunnen"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Hvis du gir denne tillatelsen, får appen tilgang til posisjonen mens den kjører i bakgrunnen, i tillegg til tilgang til omtrentlig eller presis posisjon."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Denne appen har tilgang til posisjon når den kjører i bakgrunnen, i tillegg til tilgang til posisjonen i forgrunnen."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"endre lydinnstillinger"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lar appen endre globale lydinnstillinger slik som volum og hvilken høyttaler som brukes for lydavspilling."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ta opp lyd"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Lar appen se Bluetooth-konfigurasjonen på nettbrettet, samt opprette og godta tilkoblinger med sammenkoblede enheter."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Lar appen se Bluetooth-konfigurasjonen på Android TV-enheten din samt opprette og godta tilkoblinger med sammenkoblede enheter."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Lar appen se Bluetooth-konfigurasjonen på telefonen, samt opprette og godta tilkoblinger med sammenkoblede enheter."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontroller overføring av data med NFC-teknologi"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Lar appen kommunisere med etiketter, kort og lesere som benytter NFC-teknologi."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktivere skjermlåsen"</string>
@@ -846,7 +847,7 @@
<string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Du har prøvd å låse opp Android TV-enheten din <xliff:g id="NUMBER">%d</xliff:g> ganger. Android TV-enheten din tilbakestilles nå til fabrikkstandard."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Du har foretatt <xliff:g id="NUMBER">%d</xliff:g> mislykkede opplåsinger av telefonen. Telefonen blir nå tilbakestilt til fabrikkinnstillingene."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Prøv igjen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
- <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Glemt mønsteret?"</string>
+ <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Har du glemt mønsteret?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Opplåsing av konto"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"For mange forsøk på tegning av mønster"</string>
<string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Logg på med Google-kontoen din for å låse opp."</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Koblet til <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Trykk for å se filer"</string>
<string name="pin_target" msgid="8036028973110156895">"Fest"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Løsne"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Info om appen"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Starter demo …"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Vil du oppdatere disse elementene i "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> og <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Lagre"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nei takk"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ikke nå"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Aldri"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Oppdater"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Fortsett"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"passord"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Slå delt skjerm av/på"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Låseskjerm"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Skjermdump"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g>-appen i forgrunnsvindu."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Tekstingsfelt i <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 0fb2213..19d6ccf7 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"उक्त कार्य प्रोफाइलको प्रशासकीय अनुप्रयोग छैन वा बिग्रेको छ। त्यसले गर्दा, तपाईंको कार्य प्रोफाइल र सम्बन्धित डेटालाई मेटिएको छ। सहायताका लागि आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"तपाईंको कार्य प्रोफाइल अब उप्रान्त यस यन्त्रमा उपलब्ध छैन"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"पासवर्ड प्रविष्ट गर्ने अत्यधिक गलत प्रयासहरू भए"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"व्यवस्थापकले यन्त्रलाई व्यक्तिगत प्रयोगका लागि अस्वीकार गर्नुभयो"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"यन्त्र व्यवस्थित गरिएको छ"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"तपाईंको संगठनले यस यन्त्रको व्यवस्थापन गर्दछ र नेटवर्क ट्राफिकको अनुगमन गर्न सक्छ। विवरणहरूका लागि ट्याप गर्नुहोस्।"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"तपाईंको यन्त्र मेटिनेछ"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"अनुप्रयोगलाई प्रसारण समाप्त भइसकेपछि पनि रहिरहने स्टिकी प्रसारणहरू पठाउने अनुमति दिन्छ। यो सुविधाको अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग हुने भएकाले तपाईंको Android TV यन्त्र सुस्त वा अस्थिर हुन सक्छ।"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"औपचारिक प्रसारणलाई पठाउनको लागि एक अनुप्रयोगलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले फोनलाई ढिलो र अस्थिर बनाउन सक्छ।"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"तपाईँका सम्पर्कहरू पढ्नुहोस्"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"अनुप्रयोगलाई निर्दिष्ट व्यक्तिगतसँग अन्य तरिकाहरूबाट कल गर्नु भएका, इमेल गर्नु भएका वा अन्तर्क्रिया गर्नुभएका आवृतिसहितको तपाईंको ट्याब्लेटमा भण्डारण गरिएका सम्पर्कहरूको डेटा पढ्न अनुमति दिन्छ। यो अनुमतिले तपाईंको सम्पर्क डेटा बचत गर्न अनुमति दिन्छ, र खराब अनुप्रयोगहरूले तपाईंको जानकारी बिना सम्पर्क डेटा साझेदारी गर्न सक्दछन्।"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"अनुप्रयोगलाई तपाईंले निश्चित व्यक्तिहरूलाई कति पटक फोन, इमेल वा अन्य तरिकाले सम्पर्क गर्नुभयो भन्ने डेटासहित तपाईंको Android TV यन्त्रमा भण्डार गरिएका सम्पर्क ठेगानाहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। यस अनुमतिले अनुप्रयोगहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सुरक्षित गर्न दिने भएकाले हानिकारक अनुप्रयोगहरूले सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"तपाईँले विशेष व्यक्तिहरूसँग अर्को तरिकाबाट कल गर्नुभएका, इमेल गर्नुभएका वा संचार गर्नुभएका आवृतिसहित तपाईँको फोनमा भण्डारण भएका डेटाको बारेमा पढ्नको लागि अनुप्रयोगलाई अनुमति दिन्छ। यो अनुमतिले अनुप्रयोगलाई तपाईँको सम्पर्क डेटा बचत गर्नको लागि अनुमति दिन्छ, र तपाईँको ज्ञान बिना नै खराब अनुप्रयोगहरूले सायद सम्पर्क डेटा साझेदारी गर्न सक्छन्।"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"अनुप्रयोगलाई तपाईंको ट्याब्लेटमा भण्डार गरिएका सम्पर्क ठेगानाहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। अनुप्रयोगहरूले सम्पर्क ठेगानाहरू बनाउने तपाईंको ट्याब्लेटमा भण्डार गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका अनुप्रयोगहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले अनुप्रयोगहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सुरक्षित गर्न दिने भएकाले हानिकारक अनुप्रयोगहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"अनुप्रयोगलाई तपाईंको Android TV यन्त्रमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा पढ्न अनुमति दिन्छ। अनुप्रयोगहरूले सम्पर्क ठेगानाहरू बनाउने तपाईंको Android TV यन्त्रमा भण्डार गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका अनुप्रयोगहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले अनुप्रयोगहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सुरक्षित गर्न दिने भएकाले हानिकारक अनुप्रयोगहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"अनुप्रयोगलाई तपाईंको फोनमा भण्डार गरिएका सम्पर्क ठेगानाहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। अनुप्रयोगहरूले सम्पर्क ठेगानाहरू बनाउने तपाईंको फोनमा भण्डार गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका अनुप्रयोगहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले अनुप्रयोगहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सुरक्षित गर्न दिने भएकाले हानिकारक अनुप्रयोगहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"तपाईँका सम्पर्कहरू परिवर्तन गर्नुहोस्"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"अन्य तरिकाका आवृतिहरूसँग जुन तपाईँले कल, इमेल, वा विशेष सम्पर्क गर्नुभएकासहित तपाईँको ट्याब्लेटमा भण्डारण भएका सम्पर्कहरूको बारेको डेटालाई परिवर्तन गर्नको लागि अनुप्रयोगलाई अनुमति दिन्छ। यस अनुमतिले सम्पर्क डेटालाई मेटाउनको लागि अनुमति दिन्छ।"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"अनुप्रयोगलाई तपाईंले सम्पर्क सूचीमा भएका निश्चित व्यक्तिहरूलाई कति पटक फोन, इमेल वा अन्य तरिकाले सम्पर्क गर्नुभयो भन्ने डेटासहित तपाईंको Android TV यन्त्रमा भण्डार गरिएका सम्पर्क ठेगानाहरूसँग सम्बन्धित डेटा परिमार्जन गर्ने अनुमति दिन्छ। यस अनुमतिले अनुप्रयोगहरूलाई सम्पर्क ठेगानासम्बन्धी डेटा मेट्न दिन्छ।"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"तपाईँले बारम्बार कल गरेका, इमेल गरेका, वा विशेष सम्पर्कहरूसँग सञ्चार गरेका सहित तपाईँको फोनमा भण्डारण गरेका तपाईँका सम्पर्कहरू परिमार्जन गर्न अनुप्रयोगलाई अनुमति दिन्छ। यो अनुमतिले अनुप्रयोगलाई सम्पर्क डेटा मेटाउन दिन्छ।"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"अनुप्रयोगलाई तपाईंको ट्याब्लेटमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले अनुप्रयोगलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"अनुप्रयोगलाई तपाईंको Android TV यन्त्रमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले अनुप्रयोगलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"अनुप्रयोगलाई तपाईंको फोनमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले अनुप्रयोगलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"कल लग पढ्नुहोस्"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"यस अनुप्रयोगले तपाईंको फोन सम्पर्कको इतिहास पढ्न सक्छ।"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"कल लग लेख्नुहोस्"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"अधिक स्थान प्रदायक आदेशहरू पहुँच गर्नुहोस्"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"अनुप्रयोगलाई अतिरिक्त स्थान प्रदायक आदेशहरू पहुँच गर्न अनुमति दिन्छ। यो अनुप्रयोगलाई GPS वा अन्य स्थान स्रोतहरूको संचालन साथै हस्तक्षेप गर्न अनुमति दिन सक्छ।"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"अग्रभूमिमा मात्र सटीक स्थानमाथि पहुँच राख्नुहोस्"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"यो अनुप्रयोगले अग्रभागमा चलिरहेको अवस्थामा मात्र तपाईंलाई स्थानको सटिक जानकारी दिन सक्छ। यी स्थानसम्बन्धी सेवाहरू अनिवार्य रूपमा सक्रिय गरिएका हुनु पर्छ र अनुप्रयोगले यिनीहरूको प्रयोग गर्न सकोस् भन्नाका खातिर तपाईंको फोनमै उपलब्ध हुन्छन्। यस कार्यले गर्दा ब्याट्री बढी खर्च हुन सक्छ।"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"अग्रभूमिमा मात्र अनुमानित स्थान (नेटवर्कमा आधारित) माथि पहुँच राख्नुहोस्"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"यस अनुप्रयोगले सेलका टावर र Wi-Fi नेटवर्कहरू जस्ता नेटवर्कका स्रोतहरूको आधारमा तपाईंको स्थान बारे जानकारी प्राप्त गर्न सक्छ। यो अनुप्रयोग ती स्रोतहरूको प्रयोग गर्न सक्षम होस् भन्नका खातिर यी स्थानसम्बन्धी सेवाहरूलाई अनिवार्य रूपमा सक्रिय पार्नुपर्छ र यी तपाईंको ट्याब्लेटमा उपलब्ध हुनु पर्छ।"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"यो अनुप्रयोगले अग्रभूमिमा चलिरहेको बेला मात्र यसले मोबाइलको टावर र Wi-Fi का नेटवर्कहरू जस्ता स्रोतहरूका आधारमा तपाईंको स्थानसम्बन्धी जानकारी प्राप्त गर्न सक्छ। यस अनुप्रयोगले स्थानसम्बन्धी जानकारी प्रयोग गर्न सकोस् भन्नाका खातिर तपाईंको Android TV यन्त्रमा यी स्थानसम्बन्धी सेवाहरू अनिवार्य रूपमा उपलब्ध हुनुका साथै सक्रिय गरिएको हुनु पर्छ।"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"यस अनुप्रयोगले सेलका टावर र Wi-Fi नेटवर्कहरू जस्ता नेटवर्कका स्रोतहरूको आधारमा तपाईंको स्थान बारे जानकारी प्राप्त गर्न सक्छ। यो अनुप्रयोग ती स्रोतहरूको प्रयोग गर्न सक्षम होस् भन्नका खातिर यी स्थानसम्बन्धी सेवाहरूलाई अनिवार्य रूपमा सक्रिय पार्नुपर्छ र यी तपाईंको फोनमा उपलब्ध हुनु पर्छ।"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"यो अनुप्रयोगले अग्रभागमा चलिरहेको अवस्थामा मात्र तपाईंलाई स्थानको सटिक जानकारी दिन सक्छ। स्थानसम्बन्धी सेवाहरू अनिवार्य रूपमा सक्रिय गरिएका हुनु पर्छ र अनुप्रयोगले यिनीहरूको प्रयोग गर्न सकोस् भन्नाका खातिर तपाईंको यन्त्रमै उपलब्ध हुनु पर्छ। यस कार्यले गर्दा ब्याट्री बढी खर्च हुन सक्छ।"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"अग्रभागमा मात्र अनुमानित स्थानमाथि पहुँच राख्नुहोस्"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"यो अनुप्रयोगले अग्रभागमा चलिरहेको अवस्थामा मात्र तपाईंको स्थानको अनुमानित जानकारी दिन सक्छ। स्थानसम्बन्धी सेवाहरू अनिवार्य रूपमा सक्रिय गरिएका हुनु पर्छ र अनुप्रयोगले यिनीहरूको प्रयोग गर्न सकोस् भन्नाका खातिर तपाईंको यन्त्रमै उपलब्ध हुनु पर्छ।"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"पृष्ठभूमिमा स्थानसम्बन्धी पहुँच"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"यसका अतिरिक्त यसलाई अनुमानित वा सटिक स्थानमाथि पहुँच राख्ने अनुमति दिइएको छ भने उक्त अनुप्रयोगले पृष्ठभूमिमा चलिरहेको बेला स्थानमाथि पहुँच राख्न सक्छ।"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"यो अनुप्रयोगले अग्रभागमा चल्दा स्थानसम्बन्धी पहुँच प्राप्त गर्नुका साथै पृष्ठभूमिमा चल्दा पनि स्थानसम्बन्धी पहुँच प्राप्त गर्न सक्छ।"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"तपाईँका अडियो सेटिङहरू परिवर्तन गर्नुहोस्"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"अनुप्रयोगलाई ग्लोबल अडियो सेटिङहरू परिमार्जन गर्न अनुमति दिन्छ, जस्तै आवाजको मात्रा र आउटपुटको लागि कुन स्पिकर प्रयोग गर्ने।"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"अडियो रेकर्ड गर्नुहोस्"</string>
@@ -428,7 +425,7 @@
<string name="permdesc_systemCamera" msgid="544730545441964482">"यस विशेषाधिकार प्राप्त अनुप्रयोगले जुनसुकै समय प्रणालीको क्यामेरा प्रयोग गरी तस्बिर खिच्न र भिडियो रेकर्ड गर्न सक्छ। अनुप्रयोगसँग पनि android.permission.CAMERA सम्बन्धी अनुमति हुनु पर्छ"</string>
<string name="permlab_vibrate" msgid="8596800035791962017">"कम्पन नियन्त्रण गर्नुहोस्"</string>
<string name="permdesc_vibrate" msgid="8733343234582083721">"अनुप्रयोगलाई भाइब्रेटर नियन्त्रण गर्न अनुमति दिन्छ।"</string>
- <string name="permlab_callPhone" msgid="1798582257194643320">"फोन नम्बरहरूमा सिधै कल गर्नुहोस्"</string>
+ <string name="permlab_callPhone" msgid="1798582257194643320">"फोन नम्बरहरूमा सीधै कल गर्नुहोस्"</string>
<string name="permdesc_callPhone" msgid="5439809516131609109">"तपाईँको हस्तक्षेप बेगरै फोन नम्बर कल गर्न अनुप्रयोगलाई अनुमति दिन्छ। यसले अनपेक्षित शुल्क वा कलहरू गराउन सक्छ। यसले अनुप्रयोगलाई आपतकालीन नम्बरहरू कल गर्न अनुमति दिँदैन विचार गर्नुहोस्। खराब अनुप्रयोगहरूले तपाईँको स्वीकार बिना कलहरू गरेर तपाईँलाई बढी पैसा तिराउन सक्छ।"</string>
<string name="permlab_accessImsCallService" msgid="442192920714863782">"IMS कल सेवा पहुँच गर्नुहोस्"</string>
<string name="permdesc_accessImsCallService" msgid="6328551241649687162">"तपाईँको हस्तक्षेप बिना नै कल गर्न IMS सेवा प्रयोग गर्न अनुप्रयोगलाई अनुमति दिन्छ।"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ट्याब्लेटमा ब्लुटुथको कन्फिगुरेसनलाई हेर्न र बनाउन र जोडी उपकरणहरूसँग जडानहरूलाई स्वीकार गर्न अनुप्रयोगलाई अनुमति दिन्छ।"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"अनुप्रयोगलाई तपाईंको Android TV यन्त्रको ब्लुटुथको कन्फिगुरेसन हेर्ने तथा जोडा बनाइएका यन्त्रहरूसँग जोडिने वा ती यन्त्रहरूले पठाएका जोडिने अनुरोध स्वीकार्ने अनुमति दिन्छ।"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"अनुप्रयोगलाई फोनमा ब्लुटुथको कन्फिगरेसन हेर्न र जोडी भएका उपकरणहरूसँग जडानहरू बनाउन र स्वीकार गर्न अनुमति दिन्छ।"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"नजिक क्षेत्र संचार नियन्त्रणहरू"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"अनुप्रयोगलाई नयाँ क्षेत्र संचार (NFC) ट्यागहरू, कार्डहरू र पाठकहरूसँग अन्तर्क्रिया गर्न अनुमति दिन्छ।"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"स्क्रिन लक असक्षम पार्नुहोस्"</string>
@@ -1868,7 +1869,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> मा जडान गरिएको छ"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"फाइलहरू हेर्न ट्याप गर्नुहोस्"</string>
<string name="pin_target" msgid="8036028973110156895">"पिन गर्नुहोस्"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"अनपिन गर्नुहोस्"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"अनुप्रयोगका बारे जानकारी"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"डेमो सुरु गर्दै…"</string>
@@ -1911,6 +1916,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"यी वस्तुहरू "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" मा अद्यावधिक गर्नुहोस्: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> र <xliff:g id="TYPE_2">%3$s</xliff:g> हो?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"सुरक्षित गर्नुहोस्"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"पर्दैन, धन्यवाद"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"अहिले होइन"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"कहिल्यै होइन"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"अद्यावधिक गर्नुहोस्"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"जारी राख्नुहोस्"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"पासवर्ड"</string>
@@ -2006,5 +2013,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"विभाजित स्क्रिन टगल गर्नुहोस्"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"लक स्क्रिन"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"स्क्रिनसट"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"पपअप विन्डोमा <xliff:g id="APP_NAME">%1$s</xliff:g> अनुप्रयोग छ।"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> को क्याप्सन बार।"</string>
</resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 363c3c3..d935815 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"De beheer-app van het werkprofiel ontbreekt of is beschadigd. Als gevolg hiervan zijn je werkprofiel en alle gerelateerde gegevens verwijderd. Neem contact op met je beheerder voor hulp."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Je werkprofiel is niet meer beschikbaar op dit apparaat"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Te veel wachtwoordpogingen"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"De beheerder heeft het apparaat afgestaan voor persoonlijk gebruik"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Apparaat wordt beheerd"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Dit apparaat wordt beheerd door je organisatie. Het netwerkverkeer kan worden bijgehouden. Tik voor meer informatie."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Je apparaat wordt gewist"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Hiermee kan de app sticky broadcasts verzenden die worden behouden nadat de broadcast is beëindigd. Bij overmatig gebruik kan het Android TV-apparaat traag of instabiel worden omdat er te veel geheugenruimte wordt gebruikt."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Hiermee kan de app sticky broadcasts verzenden die behouden blijven nadat de broadcast is beëindigd. Bij overmatig gebruik kan de telefoon traag of instabiel worden omdat er te veel geheugenruimte wordt gebruikt."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"je contacten lezen"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Hiermee kan de app gegevens lezen over de contacten die zijn opgeslagen op je tablet, inclusief de frequentie waarmee je hebt gebeld, gemaild of op andere manieren hebt gecommuniceerd met specifieke personen. Met deze toestemming kunnen apps je contactgegevens opslaan, en schadelijke apps kunnen zonder je medeweten contactgegevens delen."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Hiermee kan de app gegevens lezen over de contacten die zijn opgeslagen op je Android TV-apparaat, inclusief de frequentie waarmee je hebt gebeld, gemaild of op andere manieren hebt gecommuniceerd met specifieke personen. Met dit recht kunnen apps je contactgegevens opslaan en kunnen schadelijke apps zonder je medeweten contactgegevens delen."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Hiermee kan de app gegevens lezen over de contacten die zijn opgeslagen op je telefoon, inclusief de frequentie waarmee je hebt gebeld, gemaild of op andere manieren hebt gecommuniceerd met specifieke personen. Met deze toestemming kunnen apps je contactgegevens opslaan, en schadelijke apps kunnen zonder je medeweten contactgegevens delen."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Hiermee kan de app gegevens lezen over de contacten die zijn opgeslagen op je tablet. Apps hebben ook toegang tot de accounts op je tablet waarvoor contacten zijn gemaakt. Dit kan accounts omvatten die zijn gemaakt door apps die je hebt geïnstalleerd. Met dit recht kunnen apps je contactgegevens opslaan en kunnen schadelijke apps zonder je medeweten contactgegevens delen."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Hiermee kan de app gegevens lezen over de contacten die zijn opgeslagen op je Android TV. Apps hebben ook toegang tot de accounts op je Android TV-apparaat waarvoor contacten zijn gemaakt. Dit kan accounts omvatten die zijn gemaakt door apps die je hebt geïnstalleerd. Met dit recht kunnen apps je contactgegevens opslaan en kunnen schadelijke apps zonder je medeweten contactgegevens delen."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Hiermee kan de app gegevens lezen over de contacten die zijn opgeslagen op je telefoon. Apps hebben ook toegang tot de accounts op je telefoon waarvoor contacten zijn gemaakt. Dit kan accounts omvatten die zijn gemaakt door apps die je hebt geïnstalleerd. Met dit recht kunnen apps je contactgegevens opslaan en kunnen schadelijke apps zonder je medeweten contactgegevens delen."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"je contacten aanpassen"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Hiermee kan de app gegevens wijzigen over de contacten die zijn opgeslagen op je tablet, inclusief de frequentie waarmee je hebt gebeld, gemaild of op andere manieren hebt gecommuniceerd met specifieke contacten. Met deze toestemming kunnen apps contactgegevens verwijderen."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Hiermee kan de app gegevens wijzigen over de contacten die zijn opgeslagen op je Android TV-apparaat, inclusief de frequentie waarmee je hebt gebeld, gemaild of op andere manieren hebt gecommuniceerd met specifieke contacten. Met dit recht kunnen apps contactgegevens verwijderen."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Hiermee kan de app gegevens wijzigen over de contacten die zijn opgeslagen op je telefoon, inclusief de frequentie waarmee je hebt gebeld, gemaild of op andere manieren hebt gecommuniceerd met specifieke contacten. Met deze toestemming kunnen apps contactgegevens verwijderen."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Hiermee kan de app gegevens wijzigen over de contacten die zijn opgeslagen op je tablet. Met dit recht kunnen apps contactgegevens verwijderen."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Hiermee kan de app gegevens wijzigen over de contacten die zijn opgeslagen op je Android TV. Met dit recht kunnen apps contactgegevens verwijderen."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Hiermee kan de app gegevens wijzigen over de contacten die zijn opgeslagen op je telefoon. Met dit recht kunnen apps contactgegevens verwijderen."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"gesprekslijst lezen"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Deze app kan je gespreksgeschiedenis lezen."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"gesprekslijst schrijven"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"toegang tot extra opdrachten van locatieaanbieder"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Hiermee kan de app toegang krijgen tot extra opdrachten voor de locatieprovider. De app kan hiermee de werking van gps of andere locatiebronnen te verstoren."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"alleen toegang tot precieze locatie op de voorgrond"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Deze app kan je exacte locatie ophalen wanneer de app op de voorgrond wordt uitgevoerd. De app kan alleen gebruikmaken van deze locatieservices als ze zijn ingeschakeld en beschikbaar zijn op je telefoon. Hierdoor kan het batterijverbruik toenemen."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"alleen toegang tot geschatte locatie (op basis van netwerk) op de voorgrond"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Deze app kan je locatie ophalen op basis van netwerkbronnen zoals zendmasten en wifi-netwerken, maar alleen wanneer de app zich op de voorgrond bevindt. De app kan alleen gebruikmaken van deze locatieservices als ze zijn ingeschakeld en beschikbaar zijn op je tablet."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Deze app kan je locatie ophalen op basis van netwerkbronnen zoals zendmasten en wifi-netwerken, maar alleen als de app zich op de voorgrond bevindt. De app kan alleen gebruikmaken van deze locatieservices als ze zijn ingeschakeld en beschikbaar zijn op je Android TV-apparaat."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Deze app kan je locatie ophalen op basis van netwerkbronnen zoals zendmasten en wifi-netwerken, maar alleen wanneer de app zich op de voorgrond bevindt. De app kan alleen gebruikmaken van deze locatieservices als ze zijn ingeschakeld en beschikbaar zijn op je telefoon."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Deze app kan je exacte locatie ophalen als de app op de voorgrond wordt uitgevoerd. De app kan alleen gebruikmaken van de locatieservices als die zijn ingeschakeld en beschikbaar zijn op je apparaat. Hierdoor kan het batterijverbruik toenemen."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"alleen toegang tot geschatte locatie op de voorgrond"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Deze app kan je geschatte locatie alleen opvragen als de app op de voorgrond wordt gebruikt. De app kan de locatieservices alleen gebruiken als ze zijn ingeschakeld en beschikbaar zijn op je apparaat."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"toegang tot locatie op de achtergrond"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Als dit wordt verleend als aanvulling op toegang tot de geschatte of precieze locatie, kan de app toegang tot de locatie krijgen terwijl de app actief is op de achtergrond."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Deze app heeft niet alleen op de voorgrond toegang tot je locatie, maar ook als deze actief is op de achtergrond."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"je audio-instellingen wijzigen"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Hiermee kan de app algemene audio-instellingen wijzigen zoals het volume en welke luidspreker wordt gebruikt voor de uitvoer."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"audio opnemen"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Hiermee kan de app de Bluetooth-configuratie van de tablet bekijken en verbindingen met gekoppelde apparaten maken en accepteren."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Hiermee kan de app de Bluetooth-configuratie van het Android TV-apparaat bekijken en verbindingen met gekoppelde apparaten maken en accepteren."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Hiermee kan de app de Bluetooth-configuratie van de telefoon bekijken en verbindingen met gekoppelde apparaten maken en accepteren."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"Near Field Communication regelen"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Hiermee kan de app communiceren met NFC-tags (Near Field Communication), kaarten en lezers."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"je schermvergrendeling uitschakelen"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Verbonden met <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tik om bestanden te bekijken"</string>
<string name="pin_target" msgid="8036028973110156895">"Vastzetten"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Losmaken"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"App-info"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Demo starten…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Deze items updaten in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> en <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Opslaan"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nee, bedankt"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Niet nu"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nooit"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Updaten"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Doorgaan"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"Wachtwoord"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Gesplitst scherm schakelen"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Scherm vergrendelen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"App <xliff:g id="APP_NAME">%1$s</xliff:g> in pop-upvenster."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Ondertitelingsbalk van <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 566d778..84ad4cf 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"ଆଡମିନ୍ ଆପ୍ ନାହିଁ କିମ୍ବା ଭୁଲ ଅଛି। ଫଳସ୍ୱରୂପ, ଆପଣଙ୍କ ୱାର୍କ ପ୍ରୋଫାଇଲ୍ ଏବଂ ସମ୍ବନ୍ଧୀୟ ଡାଟା ଡିଲିଟ୍ କରାଯାଇଛି। ସହାୟତା ପାଇଁ ଆପଣଙ୍କ ଆଡମିନଙ୍କୁ ଯୋଗାଯୋଗ କରନ୍ତୁ।"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ଏହି ଡିଭାଇସରେ ଆପଣଙ୍କ ୱର୍କ ପ୍ରୋଫାଇଲ୍ ଆଉ ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ବହୁତ ଥର ଭୁଲ ପାସ୍ୱର୍ଡ ଲେଖିଛନ୍ତି"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"ବ୍ୟକ୍ତିଗତ ବ୍ୟବହାର ପାଇଁ ଆଡ୍ମିନ୍ ଡିଭାଇସ୍କୁ ଅଲଗା କରିଛନ୍ତି"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"ଡିଭାଇସକୁ ପରିଚାଳନା କରାଯାଉଛି"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"ଆପଣଙ୍କ ସଂସ୍ଥା ଏହି ଡିଭାଇସକୁ ପରିଚାଳନା କରନ୍ତି ଏବଂ ନେଟୱର୍କ ଟ୍ରାଫିକ୍ ନୀରିକ୍ଷଣ କରନ୍ତି। ବିବରଣୀ ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"ଆପଣଙ୍କ ଡିଭାଇସ୍ ବର୍ତ୍ତମାନ ଲିଭାଯିବ"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ଷ୍ଟିକି ବ୍ରଡକାଷ୍ଟ୍ ପଠାଇବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ, ଯାହା ବ୍ରଡକାଷ୍ଟ୍ ଶେଷ ହେବାପରେ ରହିଥାଏ। ଅତ୍ୟଧିକ ବ୍ୟବହାର ଦ୍ୱାରା ଅଧିକ ମେମୋରୀ ବ୍ୟବହାର ହୋଇ ଆପଣଙ୍କର Android ଟିଭି ଡିଭାଇସ୍କୁ ଧୀର କିମ୍ବା ଅସ୍ଥିର କରିପାରେ।"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ଷ୍ଟିକୀ ବ୍ରଡ୍କାଷ୍ଟ ପଠାଇବାକୁ ଆପ୍କୁ ଅନୁମତି ଦିଏ, ଯାହା ବ୍ରଡ୍କାଷ୍ଟ ଶେଷ ହେବାପରେ ରହିଥାଏ। ଅତିରିକ୍ତ ବ୍ୟବହାର ଦ୍ୱାରା ଅଧିକ ମେମୋରୀ ବ୍ୟବହାର ହୋଇ ଫୋନ୍କୁ ମନ୍ଥର କିମ୍ବା ଅସ୍ଥିର କରିପାରେ।"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ଆପଣଙ୍କ ଯୋଗାଯୋଗ ପଢ଼ନ୍ତୁ"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"ଜଣେ ନିର୍ଦ୍ଦିଷ୍ଟ ବ୍ୟକ୍ତିଙ୍କ ସହ ଆପଣ କେତେଥର କଲ୍, ଇମେଲ୍, ତଥା ଯୋଗାଯୋଗ କରିଛନ୍ତି, ତାହାର ନିୟମିତତା ସମେତ ଆପଣଙ୍କ ଟାବଲେଟ୍ରେ ଷ୍ଟୋର୍ ହୋଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ପଢ଼ିବାକୁ ଆପ୍କୁ ଅନୁମତି ଦିଏ। ଏହ ଅନୁମତି ଦ୍ୱାରା ଆପଣଙ୍କ କଲ୍ ଲଗ୍ ସେଭ୍ କରିବାକୁ ଆପ୍କୁ ଅନୁମତି ଦିଏ ତଥା ହାନୀକାରକ ଆପ୍ ଆପଣଙ୍କ ଅଜ୍ଞାତରେ କଲ୍ ଲଗ୍ ଡାଟା ଶେୟାର କରିପାରନ୍ତି।"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"ଅନ୍ୟ ପ୍ରକାରରେ ନିର୍ଦ୍ଦିଷ୍ଟ ବ୍ୟକ୍ତିମାନଙ୍କ ସହ ଆପଣ କେତେ ବ୍ୟବଧାନରେ କଲ୍, ଇମେଲ୍ ତଥା ଯୋଗାଯୋଗ କରିଛନ୍ତି, ସେସବୁକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରି ଆପଣଙ୍କ Android ଟିଭିର ଡିଭାଇସ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ପଢ଼ିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଏହି ଅନୁମତି ଆପଣଙ୍କର ଯୋଗାଯୋଗ ଡାଟାକୁ ସେଭ୍ କରିବା ପାଇଁ ଏବଂ ଆପଣଙ୍କ ବିନା ଜ୍ଞାତସାରରେ କ୍ଷତିକାରକ ଆପ୍ଗୁଡ଼ିକୁ ହୁଏତ ସେୟାର୍ କରିବା ପାଇଁ ଆପ୍ଗୁଡ଼ିକୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"ଜଣେ ନିର୍ଦ୍ଦିଷ୍ଟ ବ୍ୟକ୍ତିଙ୍କ ସହ ଆପଣ କେତେ ବ୍ୟବଧାନରେ କଲ୍, ଇମେଲ ତଥା ଯୋଗାଯୋଗ କରିଛନ୍ତି, ସେସବୁ ଅନ୍ତର୍ଭୁକ୍ତ କରି ଆପଣଙ୍କ ଫୋନ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ପଢିବାକୁ ଆପ୍କୁ ଅନୁମତି ଦିଏ। ଏହି ଅନୁମତି ଦ୍ୱାରା ଆପ୍ଟି ଆପଣଙ୍କ ଯୋଗାଯୋଗ ଡାଟା ସେଭ୍ କରିପାରିବ ଏବଂ ହାନୀକାରକ ଆପ୍ ଆପଣଙ୍କ ବିନା ଜ୍ଞାତସାରରେ ଯୋଗାଯୋଗ ଡାଟା ସେୟାର୍ କରିପାରନ୍ତି।"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"ଏହା ଆପଣଙ୍କ ଟାବ୍ଲେଟ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗଗୁଡ଼ିକ ବିଷୟରେ ଡାଟା ପଢ଼ିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଆପଣଙ୍କର ଟାବ୍ଲେଟ୍ରେ ଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକ ଯେଉଁଥିରେ ଯୋଗାଯୋଗଗୁଡ଼ିକ ତିଆରି ହୋଇଛି, ସେଗୁଡ଼ିକୁ ଆପ୍ସର ଆକ୍ସେସ୍ ରହିବ। ଆପଣ ଇନ୍ଷ୍ଟଲ୍ କରିଥିବା ଆପ୍ସ ମାଧ୍ୟମରେ ତିଆରି କରାଯାଇଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଏହା ସାମିଲ କରିପାରେ। ଏହି ଅନୁମତି ଆପ୍ସକୁ ଆପଣଙ୍କର ଯୋଗାଯୋଗ ଡାଟା ସେଭ୍ କରିବାକୁ ଦିଏ ଏବଂ ହାନୀକାରକ ଆପ୍ ଆପଣଙ୍କ ଅଜାଣତରେ ଯୋଗାଯୋଗ ଡାଟା ସେୟାର୍ କରିପାରେ।"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"ଏହା ଆପଣଙ୍କ Android TV ଡିଭାଇସ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ପଢ଼ିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଆପଣଙ୍କର Android TV ଡିଭାଇସ୍ରେ ଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକ ଯେଉଁଥିରେ ଯୋଗାଯୋଗଗୁଡ଼ିକ ତିଆରି ହୋଇଛି, ସେଗୁଡ଼ିକୁ ମଧ୍ୟ ଆପ୍ସର ଆକ୍ସେସ୍ ରହିବ। ଆପଣ ଇନ୍ଷ୍ଟଲ୍ କରିଥିବା ଆପ୍ସ ମାଧ୍ୟମରେ ତିଆରି କରାଯାଇଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଏହା ସାମିଲ କରିପାରେ। ଏହି ଅନୁମତି ଆପ୍ସକୁ ଆପଣଙ୍କର ଯୋଗାଯୋଗ ଡାଟା ସେଭ୍ କରିବାକୁ ଦିଏ ଏବଂ ହାନୀକାରକ ଆପ୍ ଆପଣଙ୍କ ଅଜାଣତରେ ଯୋଗାଯୋଗ ଡାଟା ସେୟାର୍ କରିପାରେ।"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ଏହା ଆପଣଙ୍କ ଫୋନ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗଗୁଡ଼ିକ ବିଷୟରେ ଡାଟା ପଢ଼ିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଆପଣଙ୍କର ଫୋନ୍ରେ ଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକ ଯେଉଁଥିରେ ଯୋଗାଯୋଗଗୁଡ଼ିକ ତିଆରି ହୋଇଛି, ସେଗୁଡ଼ିକୁ ଆପ୍ସର ଆକ୍ସେସ୍ ରହିବ। ଆପଣ ଇନ୍ଷ୍ଟଲ୍ କରିଥିବା ଆପ୍ସ ମାଧ୍ୟମରେ ତିଆରି କରାଯାଇଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଏହା ସାମିଲ କରିପାରେ। ଏହି ଅନୁମତି ଆପ୍ସକୁ ଆପଣଙ୍କର ଯୋଗାଯୋଗ ଡାଟା ସେଭ୍ କରିବାକୁ ଦିଏ ଏବଂ ହାନୀକାରକ ଆପ୍ ଆପଣଙ୍କ ଅଜାଣତରେ ଯୋଗାଯୋଗ ଡାଟା ସେୟାର୍ କରିପାରେ।"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ନିଜ ଯୋଗାଯୋଗ ସଂଶୋଧନ କରନ୍ତୁ"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"ଜଣେ ନିର୍ଦ୍ଦିଷ୍ଟ ଯୋଗାଯୋଗଙ୍କ ସହ ଆପଣ କେତେ ବ୍ୟବଧାନରେ କଲ୍, ଇମେଲ ତଥା ଯୋଗାଯୋଗ କରିଛନ୍ତି, ସେସବୁ ଅନ୍ତର୍ଭୁକ୍ତ କରି ଆପଣଙ୍କ ଟାବଲେଟ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟକ ଡାଟା ବଦଳାଇବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦିଏ। ଏହି ଅନୁମତି ଦ୍ୱାରା ଆପ୍ଟି ଯୋଗାଯୋଗ ଡାଟା ଡିଲିଟ୍ କରିପାରେ।"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"ଅନ୍ୟ ପ୍ରକାରରେ ନିର୍ଦ୍ଦିଷ୍ଟ ଯୋଗାଯୋଗ ସହ ଆପଣ କେତେ ବ୍ୟବଧାନରେ କଲ୍, ଇମେଲ୍ ତଥା ଯୋଗାଯୋଗ କରିଛନ୍ତି, ସେସବୁକୁ ଅନ୍ତର୍ଭୁକ୍ତ କରି ଆପଣଙ୍କ Android ଟିଭି ଡିଭାଇସ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ସଂଶୋଧନ କରିବାକୁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଯୋଗାଯୋଗ ଡାଟା ଡିଲିଟ୍ କରିବା ପାଇଁ ଆପ୍ସକୁ ଏହାର ଅନୁମତି ଦେଇଥାଏ।"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"ଜଣେ ନିର୍ଦ୍ଦିଷ୍ଟ ଯୋଗାଯୋଗଙ୍କ ସହ ଆପଣ କେତେ ବ୍ୟବଧାନରେ କଲ୍, ଇମେଲ ତଥା ଯୋଗାଯୋଗ କରିଛନ୍ତି, ସେସବୁ ଅନ୍ତର୍ଭୁକ୍ତ କରି ଆପଣଙ୍କ ଫୋନ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ବଦଳାଇବାକୁ ଆପ୍କୁ ଅନୁମତି ଦିଏ। ଏହି ଅନୁମତି ଦ୍ୱାରା ଆପ୍ଟି ଯୋଗାଯୋଗ ଡାଟା ଡିଲିଟ୍ କରିପାରେ।"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"ଏହା ଆପଣଙ୍କ ଟାବ୍ଲେଟ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ସଂଶୋଧନ କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଏହି ଅନୁମତି ଆପ୍ସକୁ ଆପଣଙ୍କର ଯୋଗାଯୋଗ ଡାଟା ଡିଲିଟ୍ କରିବାକୁ ଦିଏ।"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"ଏହା ଆପଣଙ୍କ Android TV ଡିଭାଇସ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ସଂଶୋଧନ କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଏହି ଅନୁମତି ଆପ୍ସକୁ ଆପଣଙ୍କର ଯୋଗାଯୋଗ ଡାଟା ଡିଲିଟ୍ କରିବାକୁ ଦିଏ।"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ଏହା ଆପଣଙ୍କ ଫୋନ୍ରେ ଷ୍ଟୋର୍ କରାଯାଇଥିବା ଯୋଗାଯୋଗ ବିଷୟରେ ଡାଟା ପରିବର୍ତ୍ତନ କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ। ଏହି ଅନୁମତି ଆପ୍ସକୁ ଆପଣଙ୍କର ଯୋଗାଯୋଗ ଡାଟା ଡିଲିଟ୍ କରିବାକୁ ଦିଏ।"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"କଲ୍ ଲଗ୍ ପଢ଼ନ୍ତୁ"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ଏହି ଆପ୍ ଆପଣଙ୍କ କଲ୍ ହିଷ୍ଟୋରୀ ପଢ଼ିପାରେ।"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"କଲ୍ ଲଗ୍ ଲେଖନ୍ତୁ"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ଅତିରିକ୍ତ ଲୋକେସନ୍ ପ୍ରଦାନକାରୀ କମାଣ୍ଡକୁ ଆକ୍ସେସ୍ କରନ୍ତୁ"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ଅତିରିକ୍ତ ଲୋକେସନ୍ ପ୍ରଦାନକାରୀ କମାଣ୍ଡ ଆକ୍ସେସ୍ କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦିଏ। GPS କିମ୍ବା ଅନ୍ୟ ଲୋକେସନ୍ ସୋର୍ସଗୁଡିକରେ ଆପ୍ଟି ପ୍ରଭାବ ପକାଇପାରେ।"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"କେବଳ ସମ୍ମୁଖଭାଗରେ ସଠିକ୍ ଲୋକେଶନ୍ର ଆକ୍ସେସ୍ କରନ୍ତୁ"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"ଏହି ଆପ୍ ଯେତେବେଳେ ସମ୍ମୁଖଭାଗରେ ଥିବାବେଳେ ଆପଣଙ୍କର ସଠିକ୍ ଲୋକେସନ୍ ପ୍ରାପ୍ତ କରିପାରିବ। ଏହି ଲୋକେସନ୍ ସେବାଗୁଡ଼ିକ ନିଶ୍ଚିତରୂପେ ଅନ୍ ରହିବା ଦରକାର ଏବଂ ଆପ୍ର ବ୍ୟବହାର ପାଇଁ ଫୋନ୍ରେ ଉପଲବ୍ଧ ଥିବା ଦରକାର। ଏହା ବ୍ୟାଟେରୀ ଅଧିକା ଖର୍ଚ୍ଚ କରିପାରେ।"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"କେବଳ ସମ୍ମୁଖଭାଗରେ ହାରାହାରି ଲୋକେସନ୍ (ନେଟ୍ୱର୍କ-ଆଧାରିତ)ର ଆକ୍ସେସ୍ କରନ୍ତୁ"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ସେଲ୍ ଟାୱାର ଓ ୱାଇ-ଫାଇ ନେଟ୍ୱର୍କ ପରି ଉତ୍ସକୁ ଆଧାର କରି ଏହି ଆପ୍ ଆପଣଙ୍କ ଲୋକେସନ୍ ପ୍ରାପ୍ତ କରିପାରିବ। ଏହି ଲୋକେସନ୍ ସେବାଗୁଡ଼ିକର ବ୍ୟବହାର କରିବାକୁ ସେଗୁଡ଼ିକ ଚାଲୁ କରାଯିବା ଏବଂ ଆପଣଙ୍କ ଟାବ୍ଲେଟ୍ରେ ଉପଲବ୍ଧ ଥିବା ଜରୁରୀ ଅଟେ।"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"ଏହି ଆପ୍, ସେଲ୍ ଟାୱାର ଓ ୱାଇ-ଫାଇ ନେଟ୍ୱାର୍କ ପରି ଉତ୍ସକୁ ଆଧାର କରି ଆପଣଙ୍କ ଲୋକେସନ୍ ପ୍ରାପ୍ତ କରିପାରିବ, କିନ୍ତୁ ଯେତେବେଳେ ଆପ୍ କେବଳ ସମ୍ମୁଖଭାଗରେ ରହିଥାଏ। ଏହି ଲୋକେସନ୍ ସେବାଗୁଡ଼ିକୁ ଚାଲୁ କରାଯିବା ଏବଂ ଆପ୍ ସେଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବାକୁ ସକ୍ଷମ ହେବା ପାଇଁ ଆପଣଙ୍କର Android ଟିଭି ଡିଭାଇସ୍ରେ ଉପଲବ୍ଧ ହେବା ଉଚିତ।"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ସେଲ୍ ଟାୱାର ଓ ୱାଇ-ଫାଇ ନେଟ୍ୱର୍କ ପରି ଉତ୍ସକୁ ଆଧାର କରି ଏହି ଆପ୍ ଆପଣଙ୍କ ଲୋକେସନ୍ ପ୍ରାପ୍ତ କରିପାରିବ। ଏହି ଲୋକେସନ୍ ସେବାଗୁଡ଼ିକର ବ୍ୟବହାର କରିବାକୁ ସେଗୁଡ଼ିକ ଚାଲୁ କରାଯିବା ଏବଂ ଆପଣଙ୍କ ଫୋନ୍ରେ ଉପଲବ୍ଧ ଥିବା ଜରୁରୀ ଅଟେ।"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"ଏହି ଆପ୍ କେବଳ ସମ୍ମୁଖ ପଟରେ ଥିବାବେଳେ ଆପଣଙ୍କର ସଠିକ୍ ଲୋକେସନ୍ ପ୍ରାପ୍ତ କରିପାରିବ। ଆପଣଙ୍କ ଡିଭାଇସ୍ରେ ଲୋକେସନ୍ ସେବା ଚାଲୁ ଏବଂ ଉପଲବ୍ଧ ହେବା ଦରକାର ଯେପରି ଆପ୍ ସେଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବାକୁ ସକ୍ଷମ ହେବ। ଏହା ବ୍ୟାଟେରୀ ଅଧିକା ଖର୍ଚ୍ଚ କରିପାରେ।"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"କେବଳ ସମ୍ମୁଖପଟରେ ହାରାହାରି ଲୋକେସନ୍ ଆକ୍ସେସ୍ କରନ୍ତୁ"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"ଏହି ଆପ୍ କେବଳ ସେତେବେଳେ ଆପଣଙ୍କର ଆନୁମାନିକ ଲୋକେସନ୍ ପାଇପାରିବ, ଯେତେବେଳେ ଏହା ସମ୍ମୁଖପଟରେ ଥିବ। ଆପଣଙ୍କ ଡିଭାଇସ୍ରେ ଲୋକେସନ୍ ସେବାଗୁଡ଼ିକ ଚାଲୁ ଏବଂ ଉପଲବ୍ଧ ହେବା ଦରକାର ଯେପରି ଆପ୍ ସେଗୁଡ଼ିକୁ ବ୍ୟବହାର କରିବାକୁ ସକ୍ଷମ ହେବ।"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"ବ୍ୟାକ୍ଗ୍ରାଉଣ୍ଡ୍ରେ ଲୋକେସନ୍ ଆକ୍ସେସ୍ କରନ୍ତୁ"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ଅନୁମାନିତ କିମ୍ବା ବିଲ୍କୁଲ୍ ସଠିକ୍ ସ୍ଥାନ ଆକ୍ସେସ୍ କରିବାର ଅନୁମତି ଅତିରିକ୍ତ ଭାବରେ ଦିଆଗଲେ, ବ୍ୟାକ୍ଗ୍ରାଉଣ୍ଡରେ ଚାଲୁଥିବା ସମୟରେ ଆପ୍ ଆପଣଙ୍କର ସ୍ଥାନର ଆକ୍ସେସ୍ କରିପାରିବ।"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"ଏହି ଆପ୍ ସମ୍ମୁଖପଟ ଲୋକେସନ୍ ଆକ୍ସେସ୍ କରିବା ସହ ପୃଷ୍ଠପଟରେ ଚାଲିବା ସମୟରେ ଲୋକେସନ୍ ଆକ୍ସେସ୍ କରିପାରିବ।"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ଆପଣଙ୍କ ଅଡିଓ ସେଟିଙ୍ଗକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ଆପ୍କୁ ଗ୍ଲୋବାଲ୍ ଅଡିଓ ସେଟିଙ୍ଗ, ଯେପରିକି ଭଲ୍ୟୁମ୍କୁ ସଂଶୋଧିତ କରିବାକୁ ଏବଂ ଆଉଟପୁଟ୍ ପାଇଁ ସ୍ପିକର୍ ବ୍ୟବହାର କରିବାକୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ଟାବଲେଟ୍ରେ ଥିବା ବ୍ଲୁ-ଟୁଥ୍ର କନଫିଗରେଶନ୍ ଦେଖିବାକୁ ଏବଂ ପେୟାର୍ କରାଯାଇଥିବା ଡିଭାଇସ୍ ସହିତ ସଂଯୋଗ ସ୍ୱୀକାର କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ଆପଣଙ୍କର Android ଟିଭି ଡିଭାଇସ୍ରେ ବ୍ଲୁଟୁଥ୍ର କନଫିଗ୍ରେସନ୍ ଦେଖିବା ପାଇଁ ଏବଂ ପେୟାର୍ କରାଯାଇଥିବା ଡିଭାଇସ୍ଗୁଡ଼ିକ ସହ ସଂଯୋଗଗୁଡ଼ିକୁ ତିଆରି ଏବଂ ସ୍ୱୀକାର କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ଫୋନ୍ରେ ଥିବା ବ୍ଲୁ-ଟୁଥ୍ର କନଫିଗରେଶନ୍ ଦେଖିବାକୁ ଏବଂ ପେୟାର୍ କରାଯାଇଥିବା ଡିଭାଇସ୍ ସହିତ ସଂଯୋଗ ସ୍ୱୀକାର କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ନିଅର୍ ଫିଲ୍ଡ କମ୍ୟୁନିକେଶନ୍ ଉପରେ ନିୟନ୍ତ୍ରଣ ରଖନ୍ତୁ"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ନିଅର୍ ଫିଲ୍ଡ କମ୍ୟୁନିକେସନ୍ନ (NFC) ଟାଗ୍, କାର୍ଡ ଓ ରିଡରଗୁଡ଼ିକ ସହ ଯୋଗାଯୋଗ କରିବା ପାଇଁ ଆପ୍କୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ଆପଣଙ୍କ ସ୍କ୍ରୀନ୍ ଲକ୍ ଅକ୍ଷମ କରନ୍ତୁ"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> ସହ କନେକ୍ଟ କରାଗଲା"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ଫାଇଲ୍ ଦେଖିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
<string name="pin_target" msgid="8036028973110156895">"ପିନ୍"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"ଅନପିନ୍ କରନ୍ତୁ"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ଆପ୍ ସୂଚନା"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ଡେମୋ ଆରମ୍ଭ କରାଯାଉଛି…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"ରେ: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ଏବଂ <xliff:g id="TYPE_2">%3$s</xliff:g> ଆଇଟମ୍ଗୁଡ଼ିକ ଅପ୍ଡେଟ୍ କରିବେ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"ସେଭ୍ କରନ୍ତୁ"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"ନାଁ, ପଚାରିଥିବାରୁ ଧନ୍ୟବାଦ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ଏବେ ନୁହେଁ"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"କେବେ ବି ନୁହେଁ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"ଅପ୍ଡେଟ୍ କରନ୍ତୁ"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ଜାରି ରଖନ୍ତୁ"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"ପାସୱର୍ଡ୍"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"ଦୁଇଟି ସ୍କ୍ରିନ୍ ମଧ୍ୟରେ ଟୋଗଲ୍ କରନ୍ତୁ"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"ସ୍କ୍ରିନ୍ ଲକ୍ କରନ୍ତୁ"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ସ୍କ୍ରିନ୍ସଟ୍ ନିଅନ୍ତୁ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"ପପ୍-ଅପ୍ ୱିଣ୍ଡୋରେ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପ୍"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>ର କ୍ୟାପ୍ସନ୍ ବାର୍।"</string>
</resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 7760db0..be40fa3 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਪ੍ਰਸ਼ਾਸਕ ਐਪ ਜਾਂ ਤਾਂ ਗੁੰਮਸ਼ੁਦਾ ਹੈ ਜਾਂ ਖਰਾਬ ਹੈ। ਨਤੀਜੇ ਵਜੋਂ, ਤੁਹਾਡੀ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਅਤੇ ਸਬੰਧਿਤ ਡਾਟਾ ਮਿਟਾਇਆ ਗਿਆ ਹੈ। ਸਹਾਇਤਾ ਲਈ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ਤੁਹਾਡਾ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਹੁਣ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ਕਈ ਵਾਰ ਗਲਤ ਪਾਸਵਰਡ ਦਾਖਲ ਕੀਤਾ ਗਿਆ"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"ਪ੍ਰਸ਼ਾਸਕ ਨੇ ਨਿੱਜੀ ਵਰਤੋਂ ਲਈ ਡੀਵਾਈਸ ਤਿਆਗਿਆ"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"ਡੀਵਾਈਸ ਪ੍ਰਬੰਧਨ ਅਧੀਨ ਹੈ"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"ਤੁਹਾਡਾ ਸੰਗਠਨ ਇਸ ਡੀਵਾਈਸ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਦਾ ਹੈ ਅਤੇ ਨੈੱਟਵਰਕ ਟਰੈਫਿਕ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦਾ ਹੈ। ਵੇਰਵਿਆਂ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਮਿਟਾਇਆ ਜਾਏਗਾ"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ਐਪ ਨੂੰ ਸਟਿੱਕੀ ਪ੍ਰਸਾਰਨ ਭੇਜਣ ਦਿੰਦੀ ਹੈ, ਜੋ ਪ੍ਰਸਾਰਨ ਦੇ ਖਤਮ ਹੋਣ ਤੋਂ ਬਾਅਦ ਵੀ ਰਹਿੰਦੇ ਹਨ। ਹੱਦੋਂ ਵੱਧ ਵਰਤੋਂ ਇਸ ਵੱਲੋਂ ਬਹੁਤ ਜ਼ਿਆਦਾ ਮੈਮੋਰੀ ਵਰਤੇ ਜਾਣ ਦਾ ਕਾਰਨ ਬਣਕੇ ਡੀਵਾਈਸ ਨੂੰ ਧੀਮਾ ਜਾਂ ਅਸਥਿਰ ਬਣਾ ਸਕਦੀ ਹੈ।"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ਐਪ ਨੂੰ ਸਟਿਕੀ ਪ੍ਰਸਾਰਨ ਭੇਜਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ, ਜੋ ਪ੍ਰਸਾਰਨ ਦੇ ਖਤਮ ਹੋਣ ਤੋਂ ਬਾਅਦ ਰਹਿੰਦੇ ਹਨ। ਵਾਧੂ ਵਰਤੋਂ ਫ਼ੋਨ ਨੂੰ ਹੌਲੀ ਜਾਂ ਬਹੁਤ ਜ਼ਿਆਦਾ ਮੈਮਰੀ ਵਰਤ ਕੇ ਇਸਨੂੰ ਅਸਥਿਰ ਬਣਾ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ਆਪਣੇ ਸੰਪਰਕ ਪੜ੍ਹੋ"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ, ਉਸ ਬਾਰੰਬਾਰਤਾ ਸਮੇਤ ਜਿਸ ਨਾਲ ਤੁਸੀਂ ਕਾਲ ਕੀਤੀ ਹੈ, ਈਮੇਲ ਕੀਤੀ ਹੈ ਜਾਂ ਖਾਸ ਵਿਅਕਤੀਆਂ ਨਾਲ ਹੋਰ ਤਰੀਕਿਆਂ ਨਾਲ ਸੰਚਾਰ ਕੀਤਾ ਹੈ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਤੁਹਾਡਾ ਸੰਪਰਕ ਡਾਟਾ ਰੱਖਿਅਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ ਅਤੇ ਖਰਾਬ ਐਪਾਂ ਤੁਹਾਡੀ ਜਾਣਕਾਰੀ ਤੋਂ ਬਿਨਾਂ ਸੰਪਰਕ ਡਾਟਾ ਸਾਂਝਾ ਕਰ ਸਕਦੀਆਂ ਹਨ।"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਪੜ੍ਹਨ ਦਿੰਦੀ ਹੈ, ਇਸ ਵਿੱਚ ਸ਼ਾਮਲ ਹੈ ਕਿ ਤੁਸੀਂ ਵਿਅਕਤੀ ਵਿਸ਼ੇਸ਼ਾਂ ਨਾਲ ਕਿਸ ਬਾਰੰਬਾਰਤਾ ਨਾਲ ਕਾਲ ਕੀਤੀ ਹੈ, ਈਮੇਲ ਕੀਤੀ ਹੈ ਜਾਂ ਹੋਰ ਤਰੀਕਿਆਂ ਨਾਲ ਸੰਚਾਰ ਕੀਤਾ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਤੁਹਾਡਾ ਸੰਪਰਕ ਡਾਟਾ ਰੱਖਿਅਤ ਕਰਨ ਦਿੰਦੀ ਹੈ ਅਤੇ ਭੈੜੀਆਂ ਐਪਾਂ ਤੁਹਾਡੀ ਜਾਣਕਾਰੀ ਤੋਂ ਬਿਨਾਂ ਸੰਪਰਕ ਡਾਟਾ ਸਾਂਝਾ ਕਰ ਸਕਦੀਆਂ ਹਨ।"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ, ਉਸ ਬਾਰੰਬਾਰਤਾ ਸਮੇਤ ਜਿਸ ਨਾਲ ਤੁਸੀਂ ਕਾਲ ਕੀਤੀ ਹੈ, ਈਮੇਲ ਕੀਤੀ ਹੈ ਜਾਂ ਖਾਸ ਵਿਅਕਤੀਆਂ ਨਾਲ ਹੋਰ ਤਰੀਕਿਆਂ ਨਾਲ ਸੰਚਾਰ ਕੀਤਾ ਹੈ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਤੁਹਾਡਾ ਸੰਪਰਕ ਡਾਟਾ ਰੱਖਿਅਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ ਅਤੇ ਖਰਾਬ ਐਪਾਂ ਤੁਹਾਡੀ ਜਾਣਕਾਰੀ ਤੋਂ ਬਿਨਾਂ ਸੰਪਰਕ ਡਾਟਾ ਸਾਂਝਾ ਕਰ ਸਕਦੀਆਂ ਹਨ।"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਪੜ੍ਹਨ ਦਿੰਦੀ ਹੈ। ਐਪਾਂ ਕੋਲ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ ਦੇ ਉਹਨਾਂ ਖਾਤਿਆਂ ਤੱਕ ਵੀ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜਿਨ੍ਹਾਂ ਨੇ ਸੰਪਰਕ ਬਣਾਏ ਹਨ। ਇਸ ਵਿੱਚ ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਥਾਪਤ ਕੀਤੀਆਂ ਐਪਾਂ ਵੱਲੋਂ ਬਣਾਏ ਗਏ ਖਾਤੇ ਸ਼ਾਮਲ ਹੋ ਸਕਦੇ ਹਨ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਤੁਹਾਡਾ ਸੰਪਰਕ ਡਾਟਾ ਰੱਖਿਅਤ ਕਰਨ ਦਿੰਦੀ ਹੈ ਅਤੇ ਨੁਕਸਾਨਦੇਹ ਐਪਾਂ ਤੁਹਾਡੀ ਜਾਣਕਾਰੀ ਤੋਂ ਬਿਨਾਂ ਸੰਪਰਕ ਡਾਟਾ ਸਾਂਝਾ ਕਰ ਸਕਦੀਆਂ ਹਨ।"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਪੜ੍ਹਨ ਦਿੰਦੀ ਹੈ। ਐਪਾਂ ਕੋਲ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ ਦੇ ਉਹਨਾਂ ਖਾਤਿਆਂ ਤੱਕ ਵੀ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜਿਨ੍ਹਾਂ ਨੇ ਸੰਪਰਕ ਬਣਾਏ ਹਨ। ਇਸ ਵਿੱਚ ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਥਾਪਤ ਕੀਤੀਆਂ ਐਪਾਂ ਵੱਲੋਂ ਬਣਾਏ ਗਏ ਖਾਤੇ ਸ਼ਾਮਲ ਹੋ ਸਕਦੇ ਹਨ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਤੁਹਾਡਾ ਸੰਪਰਕ ਡਾਟਾ ਰੱਖਿਅਤ ਕਰਨ ਦਿੰਦੀ ਹੈ ਅਤੇ ਨੁਕਸਾਨਦੇਹ ਐਪਾਂ ਤੁਹਾਡੀ ਜਾਣਕਾਰੀ ਤੋਂ ਬਿਨਾਂ ਸੰਪਰਕ ਡਾਟਾ ਸਾਂਝਾ ਕਰ ਸਕਦੀਆਂ ਹਨ।"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਪੜ੍ਹਨ ਦਿੰਦੀ ਹੈ। ਐਪਾਂ ਕੋਲ ਤੁਹਾਡੇ ਫ਼ੋਨ ਦੇ ਉਹਨਾਂ ਖਾਤਿਆਂ ਤੱਕ ਵੀ ਪਹੁੰਚ ਹੋਵੇਗੀ ਜਿਨ੍ਹਾਂ ਨੇ ਸੰਪਰਕ ਬਣਾਏ ਹਨ। ਇਸ ਵਿੱਚ ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਥਾਪਤ ਕੀਤੀਆਂ ਐਪਾਂ ਵੱਲੋਂ ਬਣਾਏ ਗਏ ਖਾਤੇ ਸ਼ਾਮਲ ਹੋ ਸਕਦੇ ਹਨ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਤੁਹਾਡਾ ਸੰਪਰਕ ਡਾਟਾ ਰੱਖਿਅਤ ਕਰਨ ਦਿੰਦੀ ਹੈ ਅਤੇ ਨੁਕਸਾਨਦੇਹ ਐਪਾਂ ਤੁਹਾਡੀ ਜਾਣਕਾਰੀ ਤੋਂ ਬਿਨਾਂ ਸੰਪਰਕ ਡਾਟਾ ਸਾਂਝਾ ਕਰ ਸਕਦੀਆਂ ਹਨ।"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ਆਪਣੇ ਸੰਪਰਕ ਸੰਸ਼ੋਧਿਤ ਕਰੋ"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"ਐਪ ਨੂੰ ਤੁਹਾਡੀ ਟੈਬਲੈੱਟ ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ, ਉਸ ਬਾਰੰਬਾਰਤਾ ਸਮੇਤ ਜਿਸ ਨਾਲ ਤੁਸੀਂ ਕਾਲ ਕੀਤੀ ਹੈ, ਈਮੇਲ ਕੀਤੀ ਹੈ ਜਾਂ ਖ਼ਾਸ ਵਿਅਕਤੀਆਂ ਨਾਲ ਹੋਰ ਤਰੀਕਿਆਂ ਨਾਲ ਸੰਚਾਰ ਕੀਤਾ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਸੰਪਰਕ ਡਾਟਾ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਸੋਧਣ ਦਿੰਦੀ ਹੈ, ਇਸ ਵਿੱਚ ਉਹ ਬਾਰੰਬਾਰਤਾ ਵੀ ਸ਼ਾਮਲ ਹੈ ਜਿਸ ਨਾਲ ਤੁਸੀਂ ਵਿਸ਼ੇਸ਼ ਸੰਪਰਕਾਂ ਨੂੰ ਕਾਲ ਕੀਤੀ ਹੈ, ਈਮੇਲ ਕੀਤੀ ਹੈ ਜਾਂ ਹੋਰ ਤਰੀਕਿਆਂ ਨਾਲ ਸੰਚਾਰ ਕੀਤਾ ਹੈ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਸੰਪਰਕ ਡਾਟਾ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਪੜ੍ਹਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ, ਉਸ ਬਾਰੰਬਾਰਤਾ ਸਮੇਤ ਜਿਸ ਨਾਲ ਤੁਸੀਂ ਕਾਲ ਕੀਤੀ ਹੈ, ਈਮੇਲ ਕੀਤੀ ਹੈ ਜਾਂ ਖ਼ਾਸ ਵਿਅਕਤੀਆਂ ਨਾਲ ਹੋਰ ਤਰੀਕਿਆਂ ਨਾਲ ਸੰਚਾਰ ਕੀਤਾ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਤੁਹਾਡਾ ਸੰਪਰਕ ਡਾਟਾ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਸੋਧਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਸੰਪਰਕ ਡਾਟਾ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਸੋਧਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਸੰਪਰਕ ਡਾਟਾ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਬਾਰੇ ਡਾਟਾ ਸੋਧਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ। ਇਹ ਇਜਾਜ਼ਤ ਐਪਾਂ ਨੂੰ ਸੰਪਰਕ ਡਾਟਾ ਮਿਟਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ਕਾਲ ਲੌਗ ਪੜ੍ਹੋ"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ਇਹ ਐਪ ਤੁਹਾਡਾ ਕਾਲ ਇਤਿਹਾਸ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"ਕਾਲ ਲੌਗ ਲਿਖੋ"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ਵਾਧੂ ਟਿਕਾਣਾ ਪ੍ਰਦਾਤਾ ਕਮਾਂਡਾਂ ਤੱਕ ਪਹੁੰਚ"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ਐਪ ਨੂੰ ਵਾਧੂ ਟਿਕਾਣਾ ਪ੍ਰਦਾਤਾ ਕਮਾਂਡਾਂ ਤੱਕ ਪਹੁੰਚ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਐਪ ਨੂੰ GPS ਜਾਂ ਹੋਰ ਟਿਕਾਣਾ ਸਰੋਤਾਂ ਦੇ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾਉਣ ਦੀ ਆਗਿਆ ਦੇ ਸਕਦਾ ਹੈ।"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ਸਿਰਫ਼ ਫੋਰਗ੍ਰਾਊਂਡ ਵਿੱਚ ਸਟੀਕ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰੋ"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"ਇਹ ਐਪ ਫੋਰਗ੍ਰਾਉਂਡ ਵਿੱਚ ਹੋਣ \'ਤੇ ਹੀ ਤੁਹਾਡਾ ਸਟੀਕ ਟਿਕਾਣਾ ਪਤਾ ਕਰ ਸਕਦੀ ਹੈ। ਐਪ ਵੱਲੋਂ ਟਿਕਾਣਾ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਇਹ ਸੇਵਾਵਾਂ ਤੁਹਾਡੇ ਫ਼ੋਨ \'ਤੇ ਉਪਲਬਧ ਹੋਣੀਆਂ ਅਤੇ ਚਾਲੂ ਕੀਤੀਆਂ ਹੋਣੀਆਂ ਲਾਜ਼ਮੀ ਹਨ। ਇਸ ਨਾਲ ਬੈਟਰੀ ਦੀ ਖਪਤ ਵਧ ਸਕਦੀ ਹੈ।"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"ਸਿਰਫ਼ ਫੋਰਗ੍ਰਾਊਂਡ ਵਿੱਚ ਅਨੁਮਾਨਿਤ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰੋ (ਨੈੱਟਵਰਕ-ਆਧਾਰਿਤ)"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ਇਹ ਐਪ ਨੈੱਟਵਰਕ ਸਰੋਤਾਂ ਜਿਵੇਂ ਕਿ ਸੈੱਲ ਟਾਵਰਾਂ ਅਤੇ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕਾਂ \'ਤੇ ਆਧਾਰਿਤ ਤੁਹਾਡਾ ਟਿਕਾਣਾ ਪਤਾ ਕਰ ਸਕਦੀ ਹੈ, ਪਰ ਸਿਰਫ਼ ਐਪ ਦੇ ਫੋਰਗ੍ਰਾਊਂਡ ਹੋਣ \'ਤੇ ਹੀ। ਐਪ ਵੱਲੋਂ ਟਿਕਾਣਾ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕੀਤੇ ਜਾਣ ਦੇ ਯੋਗ ਹੋਣ ਲਈ ਇਹ ਸੇਵਾਵਾਂ ਤੁਹਾਡੀ ਟੈਬਲੈੱਟ \'ਤੇ ਉਪਲਬਧ ਹੋਣੀਆਂ ਅਤੇ ਚਾਲੂ ਕੀਤੀਆਂ ਹੋਣੀਆਂ ਲਾਜ਼ਮੀ ਹਨ।"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"ਇਹ ਐਪ ਨੈੱਟਵਰਕ ਸਰੋਤਾਂ ਜਿਵੇਂ ਕਿ ਸੈੱਲ ਟਾਵਰਾਂ ਅਤੇ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕਾਂ \'ਤੇ ਆਧਾਰਿਤ ਤੁਹਾਡਾ ਟਿਕਾਣਾ ਪਤਾ ਕਰ ਸਕਦੀ ਹੈ, ਪਰ ਸਿਰਫ਼ ਐਪ ਦੇ ਫੋਰਗ੍ਰਾਊਂਡ ਹੋਣ \'ਤੇ ਹੀ। ਐਪ ਵੱਲੋਂ ਟਿਕਾਣਾ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕੀਤੇ ਜਾਣ ਦੇ ਯੋਗ ਹੋਣ ਲਈ ਇਹ ਸੇਵਾਵਾਂ ਤੁਹਾਡੇ Android TV ਟੀਵੀ ਡੀਵਾਈਸ \'ਤੇ ਉਪਲਬਧ ਹੋਣੀਆਂ ਅਤੇ ਚਾਲੂ ਕੀਤੀਆਂ ਹੋਣੀਆਂ ਲਾਜ਼ਮੀ ਹਨ।"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ਇਹ ਐਪ ਨੈੱਟਵਰਕ ਸਰੋਤਾਂ ਜਿਵੇਂ ਕਿ ਸੈੱਲ ਟਾਵਰਾਂ ਅਤੇ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕਾਂ \'ਤੇ ਆਧਾਰਿਤ ਤੁਹਾਡਾ ਟਿਕਾਣਾ ਪਤਾ ਕਰ ਸਕਦੀ ਹੈ, ਪਰ ਸਿਰਫ਼ ਐਪ ਦੇ ਫੋਰਗ੍ਰਾਊਂਡ ਹੋਣ \'ਤੇ ਹੀ। ਐਪ ਵੱਲੋਂ ਟਿਕਾਣਾ ਸੇਵਾਵਾਂ ਦੀ ਵਰਤੋਂ ਕੀਤੇ ਜਾਣ ਦੇ ਯੋਗ ਹੋਣ ਲਈ ਇਹ ਸੇਵਾਵਾਂ ਤੁਹਾਡੇ ਫ਼ੋਨ \'ਤੇ ਉਪਲਬਧ ਹੋਣੀਆਂ ਅਤੇ ਚਾਲੂ ਕੀਤੀਆਂ ਹੋਣੀਆਂ ਲਾਜ਼ਮੀ ਹਨ।"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"ਇਹ ਐਪ ਸਿਰਫ਼ ਉਦੋਂ ਤੁਹਾਡਾ ਸਟੀਕ ਟਿਕਾਣਾ ਪਤਾ ਕਰ ਸਕਦੀ ਹੈ, ਜਦੋਂ ਇਹ ਸਕ੍ਰੀਨ \'ਤੇ ਹੋਵੇ। ਐਪ ਦੇ ਵਰਤਣ ਲਈ ਟਿਕਾਣਾ ਸੇਵਾਵਾਂ ਦਾ ਚਾਲੂ ਹੋਣਾ ਅਤੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਉਪਲਬਧ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ। ਇਸ ਨਾਲ ਬੈਟਰੀ ਦੀ ਖਪਤ ਵਧ ਸਕਦੀ ਹੈ।"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"ਸਿਰਫ਼ ਸਕ੍ਰੀਨ \'ਤੇ ਅੰਦਾਜ਼ਨ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰੋ"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"ਇਹ ਐਪ ਸਿਰਫ਼ ਉਦੋਂ ਤੁਹਾਡਾ ਅੰਦਾਜ਼ਨ ਟਿਕਾਣਾ ਪਤਾ ਕਰ ਸਕਦੀ ਹੈ, ਜਦੋਂ ਇਹ ਸਕ੍ਰੀਨ \'ਤੇ ਹੋਵੇ। ਐਪ ਦੇ ਵਰਤਣ ਲਈ ਟਿਕਾਣਾ ਸੇਵਾਵਾਂ ਦਾ ਚਾਲੂ ਹੋਣਾ ਅਤੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਉਪਲਬਧ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ।"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"ਐਪ ਨੂੰ ਵਧੀਕ ਤੌਰ \'ਤੇ ਅਨੁਮਾਨਿਤ ਜਾਂ ਸਟੀਕ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਦੇਣ \'ਤੇ ਇਹ ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚੱਲਣ ਵੇਲੇ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ।"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"ਸਕ੍ਰੀਨ \'ਤੇ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਤੋਂ ਇਲਾਵਾ, ਇਹ ਐਪ ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚੱਲਣ ਵੇਲੇ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ਆਪਣੀਆਂ ਆਡੀਓ ਸੈਟਿੰਗਾਂ ਬਦਲੋ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ਐਪ ਨੂੰ ਗਲੋਬਲ ਆਡੀਓ ਸੈਟਿੰਗਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ ਜਿਵੇਂ ਅਵਾਜ਼ ਅਤੇ ਆਊਟਪੁਟ ਲਈ ਕਿਹੜਾ ਸਪੀਕਰ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ।"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">" ਆਡੀਓ ਰਿਕਾਰਡ ਕਰਨ"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ਐਪ ਨੂੰ ਟੈਬਲੈੱਟ ਤੇ ਬਲੂਟੁੱਥ ਦਾ ਸੰਰੂਪਣ ਦੇਖਣ, ਜੋੜਾਬੱਧ ਕੀਤੇ ਡੀਵਾਈਸਾਂ ਨਾਲ ਕਨੈਕਸ਼ਨ ਬਣਾਉਣ ਅਤੇ ਸਵੀਕਾਰ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਬਲੂਟੁੱਥ ਦਾ ਸੰਰੂਪਣ ਦੇਖਣ, ਜੋੜਾਬੱਧ ਕੀਤੇ ਡੀਵਾਈਸਾਂ ਨਾਲ ਕਨੈਕਸ਼ਨ ਬਣਾਉਣ ਅਤੇ ਸਵੀਕਾਰ ਕਰਨ ਦਿੰਦੀ ਹੈ।"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ਐਪ ਨੂੰ ਬਲੂਟੁੱਥ ਤੇ ਬਲੂਟੁੱਥ ਦਾ ਸੰਰੂਪਣ ਦੇਖਣ, ਜੋੜਾਬੱਧ ਕੀਤੇ ਡੀਵਾਈਸਾਂ ਨਾਲ ਕਨੈਕਸ਼ਨ ਬਣਾਉਣ ਅਤੇ ਸਵੀਕਾਰ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ਨਜ਼ਦੀਕੀ ਖੇਤਰ ਸੰਚਾਰ ਤੇ ਨਿਯੰਤਰਣ ਪਾਓ"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ਐਪ ਨੂੰ ਨਜ਼ਦੀਕੀ ਖੇਤਰ ਸੰਚਾਰ (NFC) ਟੈਗਾਂ, ਕਾਰਡਾਂ ਅਤੇ ਰੀਡਰਾਂ ਨਾਲ ਸੰਚਾਰ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ਆਪਣਾ ਸਕ੍ਰੀਨ ਲਾਕ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੋਈ"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ਫ਼ਾਈਲਾਂ ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="pin_target" msgid="8036028973110156895">"ਪਿੰਨ ਕਰੋ"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"ਅਨਪਿੰਨ ਕਰੋ"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ਐਪ ਜਾਣਕਾਰੀ"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ਡੈਮੋ ਚਾਲੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"ਕੀ ਤੁਸੀਂ "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ਵਿੱਚ ਇਹਨਾਂ ਆਈਟਮਾਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, ਅਤੇ <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"ਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ਹੁਣੇ ਨਹੀਂ"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ਕਦੇ ਵੀ ਨਹੀਂ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"ਅੱਪਡੇਟ ਕਰੋ"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ਜਾਰੀ ਰੱਖੋ"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"ਪਾਸਵਰਡ"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"ਲਾਕ ਸਕ੍ਰੀਨ"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"ਪੌਪ-ਅੱਪ ਵਿੰਡੋ ਵਿੱਚ <xliff:g id="APP_NAME">%1$s</xliff:g> ਐਪ।"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਦੀ ਸੁਰਖੀ ਪੱਟੀ।"</string>
</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 4ff7cc4..bc97691 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Brakuje aplikacji administratora profilu do pracy lub jest ona uszkodzona. Dlatego Twój profil służbowy i związane z nim dane zostały usunięte. Skontaktuj się ze swoim administratorem, by uzyskać pomoc."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Twój profil służbowy nie jest już dostępny na tym urządzeniu"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Zbyt wiele prób podania hasła"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator odstąpił urządzenie do użytku osobistego"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Urządzenie jest zarządzane"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Twoja organizacja zarządza tym urządzeniem i może monitorować ruch w sieci. Kliknij, by dowiedzieć się więcej."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Twoje urządzenie zostanie wyczyszczone"</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Pozwala aplikacji wysyłać trwałe transmisje, które są zachowywane po ich zakończeniu. Nadmierne korzystanie z tej funkcji może powodować wolne lub niestabilne działanie urządzenia z Androidem TV z powodu użycia zbyt dużej ilości pamięci."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Pozwala aplikacji na wysyłanie transmisji trwałych, które pozostają aktywne po zakończeniu połączenia. Nadmierne używanie może spowolnić lub zdestabilizować telefon przez wymuszenie zbyt dużego użycia pamięci."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"odczytywanie kontaktów"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Pozwala aplikacji na odczyt danych o kontaktach zapisanych na tablecie, w tym informacji o częstotliwości rozmawiania, przesyłania e-maili i komunikowania się w inny sposób z poszczególnymi osobami. Aplikacje z tym uprawnieniem mogą zapisywać dane kontaktów, a złośliwe aplikacje mogą je udostępniać bez Twojej wiedzy."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Pozwala aplikacji odczytywać dane o Twoich kontaktach zapisanych na urządzeniu z Androidem TV, wraz z informacjami o tym, jak często dzwonisz lub piszesz e-maile do określonych osób albo komunikujesz się z nimi na inne sposoby. Aplikacje z tym uprawnieniem mogą zapisywać dane kontaktów, a złośliwe aplikacje mogą je udostępniać bez Twojej wiedzy."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Pozwala aplikacji na odczyt danych o kontaktach zapisanych na telefonie, w tym informacji o częstotliwości rozmawiania, przesyłania e-maili i komunikowania się w inny sposób z poszczególnymi osobami. Aplikacje z tym uprawnieniem mogą zapisywać dane kontaktów, a złośliwe aplikacje mogą je udostępniać bez Twojej wiedzy."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Pozwala aplikacji odczytywać dane Twoich kontaktów zapisane na tablecie. Aplikacje będą również miały dostęp do kont na tablecie, na których utworzono kontakty. Może to obejmować konta utworzone przez zainstalowane aplikacje. Aplikacje z tymi uprawnieniami mogą zapisywać dane kontaktów, a złośliwe aplikacje mogą je udostępniać bez Twojej wiedzy."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Pozwala aplikacji odczytywać dane Twoich kontaktów zapisane na urządzeniu z Androidem TV. Aplikacje będą również miały dostęp do kont na urządzeniu z Androidem TV, na których utworzono kontakty. Może to obejmować konta utworzone przez zainstalowane aplikacje. Aplikacje z tymi uprawnieniami mogą zapisywać dane kontaktów, a złośliwe aplikacje mogą je udostępniać bez Twojej wiedzy."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Pozwala aplikacji odczytywać dane Twoich kontaktów zapisane na telefonie. Aplikacje będą również miały dostęp do kont na telefonie, na których utworzono kontakty. Może to obejmować konta utworzone przez zainstalowane aplikacje. Aplikacje z tymi uprawnieniami mogą zapisywać dane kontaktów, a złośliwe aplikacje mogą je udostępniać bez Twojej wiedzy."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modyfikowanie kontaktów"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Pozwala aplikacji na modyfikowanie danych o kontaktach zapisanych na tablecie, w tym informacji o częstotliwości rozmawiania, przesyłania e-maili i komunikowania się w inny sposób z poszczególnymi kontaktami. Aplikacje z tym uprawnieniem mogą usuwać dane kontaktów."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Pozwala aplikacji modyfikować dane o Twoich kontaktach zapisane na urządzeniu z Androidem TV, wraz z informacjami o tym, jak często dzwonisz lub piszesz e-maile do określonych osób albo komunikujesz się z nimi na inne sposoby. Aplikacje z tym uprawnieniem mogą usuwać dane kontaktów."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Pozwala aplikacji na modyfikowanie danych o kontaktach zapisanych na telefonie, w tym informacji o częstotliwości rozmawiania, przesyłania e-maili i komunikowania się w inny sposób z poszczególnymi kontaktami. Aplikacje z tym uprawnieniem mogą usuwać dane kontaktów."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Pozwala aplikacji odczytywać dane Twoich kontaktów zapisane na tablecie. Aplikacje z tymi uprawnieniami mogą usuwać dane kontaktów."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Pozwala aplikacji odczytywać dane Twoich kontaktów zapisane na urządzeniu z Androidem TV. Aplikacje z tymi uprawnieniami mogą usuwać dane kontaktów."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Pozwala aplikacji odczytywać dane Twoich kontaktów zapisane na telefonie. Aplikacje z tymi uprawnieniami mogą usuwać dane kontaktów."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"czytanie rejestru połączeń"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Ta aplikacja może odczytywać historię połączeń."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"zapisywanie rejestru połączeń"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"dostęp do dodatkowych poleceń dostawcy informacji o lokalizacji"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Pozwala aplikacji na dostęp do dodatkowych poleceń dostawcy informacji o lokalizacji. Aplikacje z tym uprawnieniem mogą wpływać na działanie GPS-a lub innych źródeł lokalizacji."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"dostęp do dokładnej lokalizacji tylko na pierwszym planie"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Ta aplikacja może określić Twoją dokładną lokalizację tylko wtedy, gdy działa na pierwszym planie. Te usługi lokalizacyjne muszą być włączone i dostępne na telefonie, by aplikacja mogła z nich korzystać. Może to zwiększyć zużycie baterii."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"dostęp do przybliżonej lokalizacji (na podstawie sieci) tylko na pierwszym planie"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ta aplikacja może określać Twoją lokalizację na podstawie źródeł sieciowych takich jak stacje bazowe i sieci Wi-Fi, ale tylko wtedy, gdy działa na pierwszym planie. Wymienione usługi lokalizacyjne muszą być włączone i dostępne na tablecie, by aplikacja mogła z nich korzystać."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ta aplikacja może określać Twoją lokalizację na podstawie źródeł sieciowych takich jak stacje bazowe i sieci Wi-Fi, ale tylko wtedy, gdy działa na pierwszym planie. Aby aplikacja mogła korzystać z tych usług lokalizacyjnych, muszą one być włączone i dostępne na urządzeniu z Androidem TV."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ta aplikacja może określać Twoją lokalizację na podstawie źródeł sieciowych takich jak stacje bazowe i sieci Wi-Fi, ale tylko wtedy, gdy działa na pierwszym planie. Wymienione usługi lokalizacyjne muszą być włączone i dostępne na telefonie, by aplikacja mogła z nich korzystać."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ta aplikacja może określić Twoją dokładną lokalizację tylko wtedy, gdy działa na pierwszym planie. Usługi lokalizacyjne muszą być włączone i dostępne na urządzeniu, by aplikacja mogła z nich korzystać. Może to zwiększyć zużycie baterii."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"dostęp do przybliżonej lokalizacji tylko na pierwszym planie"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ta aplikacja może określić Twoją przybliżoną lokalizację tylko wtedy, gdy działa na pierwszym planie. Usługi lokalizacyjne muszą być włączone i dostępne na urządzeniu, by aplikacja mogła z nich korzystać."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"dostęp do lokalizacji w tle"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Jeśli te uprawnienia zostaną przyznane wraz z dostępem do dokładnej lub przybliżonej lokalizacji, aplikacja będzie mogła uzyskiwać dostęp do lokalizacji podczas działania w tle."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ta aplikacja może korzystać z lokalizacji, gdy działa w tle, niezależnie od dostępu do lokalizacji na pierwszym planie."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"zmienianie ustawień audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Pozwala aplikacji na modyfikowanie globalnych ustawień dźwięku, takich jak głośność oraz urządzenie wyjściowe."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"nagrywanie dźwięku"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Pozwala aplikacji na dostęp do konfiguracji Bluetooth na tablecie oraz na nawiązywanie i akceptowanie połączeń ze sparowanych urządzeń."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Pozwala aplikacji odczytywać konfigurację Bluetootha na urządzeniu z Androidem TV oraz nawiązywać i akceptować połączenia ze sparowanymi urządzeniami."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Pozwala aplikacji na dostęp do konfiguracji Bluetooth na telefonie oraz na nawiązywanie i akceptowanie połączeń ze sparowanych urządzeń."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontrolowanie łączności Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Pozwala aplikacji na komunikowanie się z tagami, kartami i czytnikami NFC (Near Field Communication)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"wyłączanie blokady ekranu"</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Połączono z: <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Dotknij, by wyświetlić pliki"</string>
<string name="pin_target" msgid="8036028973110156895">"Przypnij"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Odepnij"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"O aplikacji"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Uruchamiam tryb demo…"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Zaktualizować w: "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" te elementy: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> i <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Zapisz"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nie, dziękuję"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Nie teraz"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nigdy"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Zaktualizuj"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Dalej"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"hasło"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Przełącz podzielony ekran"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Ekran blokady"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Zrzut ekranu"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> w wyskakującym okienku."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Pasek napisów w aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 1750d2a..108d187 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"O app para administrador do perfil de trabalho não foi encontrado ou está corrompido. Consequentemente, seu perfil de trabalho e os dados relacionados foram excluídos. Entre em contato com seu administrador para receber assistência."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Seu perfil de trabalho não está mais disponível neste dispositivo"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Muitas tentativas de senha"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador renunciou ao dispositivo para uso pessoal"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"O dispositivo é gerenciado"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Sua organização gerencia este dispositivo e pode monitorar o tráfego de rede. Toque para ver detalhes."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Seu dispositivo será limpo"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permite ao app enviar transmissões fixas, que permanecem após o fim da transmissão. O uso excessivo pode causar lentidão ou instabilidade no seu dispositivo Android TV, devido ao aumento do uso de memória."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permite que o app envie transmissões fixas, que permanecem depois que a transmissão termina. O uso excessivo pode deixar o telefone lento ou instável, fazendo com que ele use muita memória."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ler seus contatos"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permite que o app leia dados dos contatos armazenados no tablet, incluindo a frequência com que você chamou, enviou e-mails ou se comunicou de qualquer outra forma com indivíduos específicos. Esta permissão autoriza o app a salvar seus dados de contato, e apps maliciosos podem compartilhar esses dados de contato sem seu conhecimento."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permite que o app leia os dados sobre os contatos armazenados no seu dispositivo Android TV, incluindo a frequência de chamadas, e-mails e outras comunicações com pessoas específicas. Essa autorização permite que os apps salvem os dados dos seus contatos,. Tenha cuidado porque apps maliciosos podem compartilhar esses dados sem seu conhecimento."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permite que o app leia dados dos contatos armazenados no telefone, incluindo a frequência com que você chamou, enviou e-mails ou se comunicou de qualquer outra forma com indivíduos específicos. Esta permissão autoriza o app a salvar seus dados de contato, e apps maliciosos podem compartilhar esses dados de contato sem seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite que o app leia dados sobre seus contatos armazenados no tablet. Os apps também terão acesso às contas no tablet que criaram contatos. Isso pode incluir contas criadas pelos apps que você instalou. Essa permissão autoriza os apps a salvarem os dados dos seus contatos, e apps maliciosos podem compartilhar esses dados sem seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite que o app leia dados sobre seus contatos armazenados no dispositivo Android TV. Os apps também terão acesso às contas no dispositivo Android TV que criaram contatos. Isso pode incluir contas criadas pelos apps que você instalou. Essa permissão autoriza os apps a salvarem os dados dos seus contatos, e apps maliciosos podem compartilhar esses dados sem seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite que o app leia dados sobre seus contatos armazenados no smartphone. Os apps também terão acesso às contas no smartphone que criaram contatos. Isso pode incluir contas criadas pelos apps que você instalou. Essa permissão autoriza os apps a salvarem os dados dos seus contatos, e apps maliciosos podem compartilhar esses dados sem seu conhecimento."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modificar seus contatos"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permite que o app modifique os dados sobre os contatos armazenados no tablet, incluindo a frequência com que você fez chamadas, enviou e-mails ou se comunicou de outras formas com contatos específicos. Esta permissão autoriza o app a excluir dados de contatos."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permite que o app modifique os dados dos contatos armazenados no seu dispositivo Android TV, incluindo a frequência de chamadas, e-mails e outras comunicações com contatos específicos. Essa permissão autoriza os apps a excluir dados dos contatos."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permite que o app modifique os dados dos contatos armazenados no telefone, incluindo a frequência com que você fez chamadas, enviou e-mails ou se comunicou de outras formas com contatos específicos. Esta permissão autoriza o app a excluir dados de contatos."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permite que o app modifique os dados sobre os contatos armazenados no tablet. Essa permissão autoriza os apps a excluírem dados de contato."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permite que o app modifique os dados sobre os contatos armazenados no dispositivo Android TV. Essa permissão autoriza os apps a excluírem dados de contato."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permite que o app modifique os dados sobre os contatos armazenados no smartphone. Essa permissão autoriza os apps a excluírem dados de contato."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ler registro de chamadas"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Este app pode ler seu histórico de chamadas."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"salvar no registo de chamadas"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"acessar comandos extras do provedor de localização"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permite que o app acesse comandos do provedor não relacionados à localização. Isso pode permitir que o app interfira no funcionamento do GPS ou de outras fontes de localização."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"acessar localização precisa apenas em primeiro plano"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Este app pode ver sua localização exata a qualquer momento apenas quando está em primeiro plano. Esses serviços de localização precisam estar ativados e disponíveis no seu smartphone para que o app possa usá-los. Isso pode aumentar o consumo de bateria."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"acessar localização aproximada (baseada em rede) apenas em primeiro plano"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Esse app pode acessar sua localização com base em fontes de rede, como torres de celular e redes Wi-Fi, mas apenas quando está em primeiro plano. Esses serviços de localização precisam estar ativados e disponíveis no seu tablet para que o app possa usá-los."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Esse app pode acessar sua localização com base em fontes de rede, como torres de celular e redes Wi-Fi, mas apenas quando está em primeiro plano. Esses serviços de localização precisam estar ativados e disponíveis no seu dispositivo Android TV para que o app possa usá-los."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Esse app pode acessar sua localização com base em fontes de rede, como torres de celular e redes Wi-Fi, mas apenas quando está em primeiro plano. Esses serviços de localização precisam estar ativados e disponíveis no seu smartphone para que o app possa usá-los."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Este app pode ver seu local exato apenas quando está em primeiro plano. Os \"Serviços de localização\" precisam estar ativados e disponíveis no seu dispositivo para que o app possa usá-los. Isso pode aumentar o consumo de bateria."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"acessar local aproximado apenas em primeiro plano"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Este app poderá acessar seu local aproximado somente quando estiver em primeiro plano. Os \"Serviços de localização\" precisam estar ativados e disponíveis no dispositivo para que o app possa usá-los."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"acessar a localização em segundo plano"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Se essa permissão for concedida, além do acesso à localização precisa ou aproximada, o app poderá acessar a localização durante a execução em segundo plano."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Esse app pode acessar o local em segundo plano, além do acesso em primeiro plano."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas configurações de áudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que o app modifique configurações de áudio globais como volume e alto-falantes de saída."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite que o app acesse a configuração do Bluetooth no tablet, além de fazer e aceitar conexões com dispositivos pareados."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite que o app acesse a configuração do Bluetooth no dispositivo Android TV, além de fazer e aceitar conexões com dispositivos pareados."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite que o app acesse a configuração do Bluetooth no telefone, além de fazer e aceitar conexões com dispositivos pareados."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controlar a comunicação a curta distância"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permite que o app se comunique com leitores, cartões e etiqueta NFC (comunicação a curta distância)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"desativar o bloqueio de tela"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Conectado a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Toque para ver os arquivos"</string>
<string name="pin_target" msgid="8036028973110156895">"Fixar guia"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Liberar guia"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informações do app"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demonstração…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Atualizar estes itens em "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> e <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Salvar"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Não, obrigado"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Agora não"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nunca"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Atualizar"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuar"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"senha"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Ativar tela dividida"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Bloquear tela"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Capturar tela"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"App <xliff:g id="APP_NAME">%1$s</xliff:g> na janela pop-up."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de legendas do app <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 5c21c0d..0d2310d 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"A aplicação de administração do perfil de trabalho está em falta ou danificada. Consequentemente, o seu perfil de trabalho e os dados relacionados foram eliminados. Contacte o gestor para obter assistência."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"O seu perfil de trabalho já não está disponível neste dispositivo"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Demasiadas tentativas de introdução da palavra-passe"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador anulou o dispositivo para utilização pessoal."</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"O dispositivo é gerido"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"A sua entidade gere este dispositivo e pode monitorizar o tráfego de rede. Toque para obter mais detalhes."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"O seu dispositivo será apagado"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permite à aplicação enviar transmissões fixas que permanecem após o fim da transmissão. Uma utilização excessiva pode tornar o seu dispositivo Android TV lento ou instável, fazendo com que utilize demasiada memória."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permite que a aplicação envie difusões fixas, que permanecem após o fim da difusão. Uma utilização excessiva pode tornar o telemóvel lento ou instável, fazendo com que utilize demasiada memória."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ler os contactos"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permite que a aplicação leia dados acerca de contactos guardados no tablet, incluindo a frequência com que telefonou, enviou emails ou comunicou através de outras formas com determinadas pessoas. Esta autorização permite que a aplicação guarde dados de contactos e as aplicações maliciosas podem partilhar dados de contactos sem o seu conhecimento."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permite à aplicação ler dados acerca dos contactos armazenados no seu dispositivo Android TV, incluindo a frequência com que efetuou chamadas, enviou emails ou comunicou de outras formas com indivíduos específicos. Esta autorização permite às aplicações guardarem dados de contactos e as aplicações maliciosas podem partilhá-los sem o seu conhecimento."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permite que a aplicação leia dados acerca de contactos guardados no telemóvel, incluindo a frequência com que telefonou, enviou emails ou comunicou através de outras formas com determinadas pessoas. Esta autorização permite que a aplicação guarde dados de contactos e as aplicações maliciosas podem partilhar dados de contactos sem o seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite à aplicação ler dados acerca dos contactos armazenados no seu tablet. As aplicações também terão acesso às contas no tablet que criaram contactos. Pode incluir contas criadas pelas aplicações instaladas. Esta autorização permite às aplicações guardarem dados de contactos e as aplicações maliciosas podem partilhá-los sem o seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite à aplicação ler dados acerca dos contactos armazenados no seu dispositivo Android TV. As aplicações terão acesso às contas no dispositivo Android TV que criaram contactos. Pode incluir contas criadas pelas aplicações instaladas. Esta autorização permite às aplicações guardarem dados de contactos e as aplicações maliciosas podem partilhá-los sem o seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite à aplicação ler dados acerca dos contactos armazenados no seu telemóvel. As aplicações também terão acesso às contas no telemóvel que criaram contactos. Pode incluir contas criadas pelas aplicações instaladas. Esta autorização permite às aplicações guardarem dados de contactos e as aplicações maliciosas podem partilhá-los sem o seu conhecimento."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modificar os contactos"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permite que a aplicação modifique dados acerca dos contactos guardados no tablet, incluindo a frequência com que telefonou, enviou emails ou comunicou através de outras formas com determinadas pessoas. Esta autorização permite que as aplicações eliminem dados de contactos."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permite à aplicação modificar dados acerca dos contactos armazenados no seu dispositivo Android TV, incluindo a frequência com que efetuou chamadas, enviou emails ou comunicou de outras formas com pessoas específicas. Esta autorização permite às aplicações eliminarem dados de contactos."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permite que a aplicação modifique dados acerca dos contactos guardados no telemóvel, incluindo a frequência com que telefonou, enviou emails ou comunicou através de outras formas com determinadas pessoas. Esta autorização permite que as aplicações eliminem dados de contactos."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permite que a aplicação modifique dados acerca dos contactos armazenados no tablet. Esta autorização permite que as aplicações eliminem dados de contactos."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permite à aplicação modificar dados acerca dos contactos armazenados no seu dispositivo Android TV. Esta autorização permite que as aplicações eliminem dados de contactos."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permite que a aplicação modifique dados acerca dos contactos guardados no telemóvel. Esta autorização permite que as aplicações eliminem dados de contactos."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ler registo de chamadas"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Esta aplicação pode ler o seu histórico de chamadas."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"escrever registo de chamadas"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"aceder a comandos adicionais do fornecedor de localização"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permite que a aplicação aceda a comandos adicionais do fornecedor de localização. Esta opção pode permitir que a aplicação interfira com o funcionamento do GPS ou de outras fontes de localização."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"apenas aceder à localização exata em primeiro plano"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Esta aplicação apenas pode obter a sua localização exata quando estiver em primeiro plano. É necessário que estes Serviços de localização estejam ativados e disponíveis no seu telemóvel para que a aplicação os possa utilizar. Esta ação pode aumentar o consumo da bateria."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"aceder à localização aproximada (baseada na rede) apenas em primeiro plano"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Esta aplicação pode obter a sua localização com base em fontes de rede, tais como torres de redes móveis e redes Wi-Fi, mas apenas quando estiver em primeiro plano. É necessário que estes Serviços de localização estejam ativados e disponíveis no seu tablet para que a aplicação os possa utilizar."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Esta aplicação pode obter a sua localização com base em fontes de rede, tais como torres de redes móveis e redes Wi-Fi, mas apenas quando estiver em primeiro plano. É necessário que estes Serviços de localização estejam ativados e disponíveis no seu dispositivo Android TV para que a aplicação os possa utilizar."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Esta aplicação pode obter a sua localização com base em fontes de rede, tais como torres de redes móveis e redes Wi-Fi, mas apenas quando estiver em primeiro plano. É necessário que estes Serviços de localização estejam ativados e disponíveis no seu telemóvel para que a aplicação os possa utilizar."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Esta aplicação apenas pode obter a sua localização exata quando estiver em primeiro plano. É necessário que os Serviços de localização estejam ativados e disponíveis no seu dispositivo para que a aplicação os possa utilizar. Esta ação pode aumentar o consumo da bateria."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"apenas aceder à localização aproximada em primeiro plano"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Esta aplicação apenas pode obter a sua localização aproximada quando estiver em primeiro plano. É necessário que os Serviços de localização estejam ativados e disponíveis para que a aplicação os possa utilizar."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"aceder à localização em segundo plano"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Se tal for concedido em conjunto com o acesso à localização aproximada ou exata, a aplicação pode aceder à localização mesmo estando em segundo plano."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Esta aplicação pode aceder à localização mesmo estando em segundo plano, além do acesso à localização em primeiro plano."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas definições de áudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que a aplicação modifique definições de áudio globais, tais como o volume e qual o altifalante utilizado para a saída de som."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite que a aplicação visualize a configuração do Bluetooth no tablet e que estabeleça e aceite ligações com dispositivos emparelhados."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite que a aplicação visualize a configuração do Bluetooth no seu dispositivo Android TV e que estabeleça e aceite ligações com dispositivos sincronizados."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite que a aplicação visualize a configuração do Bluetooth no telemóvel e que estabeleça e aceite ligações com dispositivos emparelhados."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controlo Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permite que a aplicação comunique com etiquetas, cartões e leitores Near Field Communication (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"desativar o bloqueio do ecrã"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Ligado a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Tocar para ver ficheiros"</string>
<string name="pin_target" msgid="8036028973110156895">"Fixar"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Soltar"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Info. da aplicação"</string>
<string name="negative_duration" msgid="1938335096972945232">"-<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"A iniciar a demonstração…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Pretende atualizar estes itens em "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> e <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Guardar"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Não, obrigado"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Agora não"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nunca"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Atualizar"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuar"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"palavra-passe"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Ativar/desativar o ecrã dividido"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Ecrã de bloqueio"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Captura de ecrã"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplicação <xliff:g id="APP_NAME">%1$s</xliff:g> numa janela de pop-up."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de legendas da aplicação <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 1750d2a..108d187 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"O app para administrador do perfil de trabalho não foi encontrado ou está corrompido. Consequentemente, seu perfil de trabalho e os dados relacionados foram excluídos. Entre em contato com seu administrador para receber assistência."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Seu perfil de trabalho não está mais disponível neste dispositivo"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Muitas tentativas de senha"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"O administrador renunciou ao dispositivo para uso pessoal"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"O dispositivo é gerenciado"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Sua organização gerencia este dispositivo e pode monitorar o tráfego de rede. Toque para ver detalhes."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Seu dispositivo será limpo"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permite ao app enviar transmissões fixas, que permanecem após o fim da transmissão. O uso excessivo pode causar lentidão ou instabilidade no seu dispositivo Android TV, devido ao aumento do uso de memória."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permite que o app envie transmissões fixas, que permanecem depois que a transmissão termina. O uso excessivo pode deixar o telefone lento ou instável, fazendo com que ele use muita memória."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ler seus contatos"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permite que o app leia dados dos contatos armazenados no tablet, incluindo a frequência com que você chamou, enviou e-mails ou se comunicou de qualquer outra forma com indivíduos específicos. Esta permissão autoriza o app a salvar seus dados de contato, e apps maliciosos podem compartilhar esses dados de contato sem seu conhecimento."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permite que o app leia os dados sobre os contatos armazenados no seu dispositivo Android TV, incluindo a frequência de chamadas, e-mails e outras comunicações com pessoas específicas. Essa autorização permite que os apps salvem os dados dos seus contatos,. Tenha cuidado porque apps maliciosos podem compartilhar esses dados sem seu conhecimento."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permite que o app leia dados dos contatos armazenados no telefone, incluindo a frequência com que você chamou, enviou e-mails ou se comunicou de qualquer outra forma com indivíduos específicos. Esta permissão autoriza o app a salvar seus dados de contato, e apps maliciosos podem compartilhar esses dados de contato sem seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite que o app leia dados sobre seus contatos armazenados no tablet. Os apps também terão acesso às contas no tablet que criaram contatos. Isso pode incluir contas criadas pelos apps que você instalou. Essa permissão autoriza os apps a salvarem os dados dos seus contatos, e apps maliciosos podem compartilhar esses dados sem seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite que o app leia dados sobre seus contatos armazenados no dispositivo Android TV. Os apps também terão acesso às contas no dispositivo Android TV que criaram contatos. Isso pode incluir contas criadas pelos apps que você instalou. Essa permissão autoriza os apps a salvarem os dados dos seus contatos, e apps maliciosos podem compartilhar esses dados sem seu conhecimento."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite que o app leia dados sobre seus contatos armazenados no smartphone. Os apps também terão acesso às contas no smartphone que criaram contatos. Isso pode incluir contas criadas pelos apps que você instalou. Essa permissão autoriza os apps a salvarem os dados dos seus contatos, e apps maliciosos podem compartilhar esses dados sem seu conhecimento."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modificar seus contatos"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permite que o app modifique os dados sobre os contatos armazenados no tablet, incluindo a frequência com que você fez chamadas, enviou e-mails ou se comunicou de outras formas com contatos específicos. Esta permissão autoriza o app a excluir dados de contatos."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permite que o app modifique os dados dos contatos armazenados no seu dispositivo Android TV, incluindo a frequência de chamadas, e-mails e outras comunicações com contatos específicos. Essa permissão autoriza os apps a excluir dados dos contatos."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permite que o app modifique os dados dos contatos armazenados no telefone, incluindo a frequência com que você fez chamadas, enviou e-mails ou se comunicou de outras formas com contatos específicos. Esta permissão autoriza o app a excluir dados de contatos."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permite que o app modifique os dados sobre os contatos armazenados no tablet. Essa permissão autoriza os apps a excluírem dados de contato."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permite que o app modifique os dados sobre os contatos armazenados no dispositivo Android TV. Essa permissão autoriza os apps a excluírem dados de contato."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permite que o app modifique os dados sobre os contatos armazenados no smartphone. Essa permissão autoriza os apps a excluírem dados de contato."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ler registro de chamadas"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Este app pode ler seu histórico de chamadas."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"salvar no registo de chamadas"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"acessar comandos extras do provedor de localização"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permite que o app acesse comandos do provedor não relacionados à localização. Isso pode permitir que o app interfira no funcionamento do GPS ou de outras fontes de localização."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"acessar localização precisa apenas em primeiro plano"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Este app pode ver sua localização exata a qualquer momento apenas quando está em primeiro plano. Esses serviços de localização precisam estar ativados e disponíveis no seu smartphone para que o app possa usá-los. Isso pode aumentar o consumo de bateria."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"acessar localização aproximada (baseada em rede) apenas em primeiro plano"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Esse app pode acessar sua localização com base em fontes de rede, como torres de celular e redes Wi-Fi, mas apenas quando está em primeiro plano. Esses serviços de localização precisam estar ativados e disponíveis no seu tablet para que o app possa usá-los."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Esse app pode acessar sua localização com base em fontes de rede, como torres de celular e redes Wi-Fi, mas apenas quando está em primeiro plano. Esses serviços de localização precisam estar ativados e disponíveis no seu dispositivo Android TV para que o app possa usá-los."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Esse app pode acessar sua localização com base em fontes de rede, como torres de celular e redes Wi-Fi, mas apenas quando está em primeiro plano. Esses serviços de localização precisam estar ativados e disponíveis no seu smartphone para que o app possa usá-los."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Este app pode ver seu local exato apenas quando está em primeiro plano. Os \"Serviços de localização\" precisam estar ativados e disponíveis no seu dispositivo para que o app possa usá-los. Isso pode aumentar o consumo de bateria."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"acessar local aproximado apenas em primeiro plano"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Este app poderá acessar seu local aproximado somente quando estiver em primeiro plano. Os \"Serviços de localização\" precisam estar ativados e disponíveis no dispositivo para que o app possa usá-los."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"acessar a localização em segundo plano"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Se essa permissão for concedida, além do acesso à localização precisa ou aproximada, o app poderá acessar a localização durante a execução em segundo plano."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Esse app pode acessar o local em segundo plano, além do acesso em primeiro plano."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas configurações de áudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que o app modifique configurações de áudio globais como volume e alto-falantes de saída."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite que o app acesse a configuração do Bluetooth no tablet, além de fazer e aceitar conexões com dispositivos pareados."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite que o app acesse a configuração do Bluetooth no dispositivo Android TV, além de fazer e aceitar conexões com dispositivos pareados."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite que o app acesse a configuração do Bluetooth no telefone, além de fazer e aceitar conexões com dispositivos pareados."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controlar a comunicação a curta distância"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permite que o app se comunique com leitores, cartões e etiqueta NFC (comunicação a curta distância)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"desativar o bloqueio de tela"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Conectado a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Toque para ver os arquivos"</string>
<string name="pin_target" msgid="8036028973110156895">"Fixar guia"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Liberar guia"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informações do app"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demonstração…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Atualizar estes itens em "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> e <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Salvar"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Não, obrigado"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Agora não"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nunca"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Atualizar"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuar"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"senha"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Ativar tela dividida"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Bloquear tela"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Capturar tela"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"App <xliff:g id="APP_NAME">%1$s</xliff:g> na janela pop-up."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Barra de legendas do app <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 397525a..2725f6b 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -190,8 +190,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplicația de administrare a profilului de serviciu lipsește sau este deteriorată. Prin urmare, profilul de serviciu și datele asociate au fost șterse. Pentru asistență, contactați administratorul."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profilul de serviciu nu mai este disponibil pe acest dispozitiv"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Prea multe încercări de introducere a parolei"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratorul a retras dispozitivul pentru uz personal"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Dispozitivul este gestionat"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Organizația dvs. gestionează acest dispozitiv și poate monitoriza traficul în rețea. Atingeți pentru mai multe detalii."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Datele de pe dispozitiv vor fi șterse"</string>
@@ -384,13 +383,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Permite aplicației să trimită mesaje difuzate persistente, care se păstrează după terminarea difuzării mesajului. Utilizarea excesivă a acestei funcții poate să încetinească sau să destabilizeze dispozitivul Android TV, determinându-l să utilizeze prea multă memorie."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Permite aplicației să trimită mesaje difuzate persistente, care se păstrează după terminarea difuzării mesajului. Utilizarea excesivă a acestei funcții poate să încetinească sau să destabilizeze telefonul, determinându-l să utilizeze prea multă memorie."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"citește agenda"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Permite aplicației să citească datele despre persoanele din agenda stocată pe tabletă, inclusiv frecvența cu care ați apelat, ați trimis e-mailuri sau ați comunicat în alte moduri cu anumite persoane. Cu această permisiune aplicația salvează datele dvs. de contact, iar aplicațiile rău intenționate pot distribui datele de contact fără știrea dvs."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Permite aplicației să citească datele despre persoanele de contact salvate pe dispozitivul Android TV, inclusiv frecvența cu care ați apelat, ați trimis e-mailuri sau ați comunicat în alte moduri cu anumite persoane. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău-intenționate pot permite accesul la datele de contact fără cunoștința dvs."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Permite aplicației să citească datele despre persoanele din agenda stocată pe telefon, inclusiv frecvența cu care ați apelat, ați trimis e-mailuri sau ați comunicat în alte moduri cu anumite persoane. Cu această permisiune aplicația salvează datele dvs. de contact, iar aplicațiile rău intenționate pot distribui datele de contact fără știrea dvs."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Permite aplicației să citească datele despre persoanele din agenda stocată pe tabletă. Aplicațiile vor avea și acces la conturile de pe tabletă care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ați instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău-intenționate pot permite accesul la datele de contact fără cunoștința dvs."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Permite aplicației să citească datele despre persoanele de contact din agenda stocată pe dispozitivul Android TV. Aplicațiile vor avea și acces la conturile de pe dispozitivul Android TV care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ați instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău-intenționate pot permite accesul la datele de contact fără cunoștința dvs."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Permite aplicației să citească datele despre persoanele de contact salvate pe telefon. Aplicațiile vor avea și acces la conturile de pe telefon care au creat agenda. Aici pot fi incluse conturile create de aplicațiile pe care le-ați instalat. Cu această permisiune, aplicațiile pot salva datele de contact, iar aplicațiile rău-intenționate pot permite accesul la datele de contact fără cunoștința dvs."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modifică agenda"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Permite aplicației să modifice datele despre persoanele din agenda stocată pe tabletă, inclusiv frecvența cu care ați apelat, ați trimis e-mailuri sau ați comunicat în alte moduri cu anumite persoane din agendă. Cu această permisiune aplicația poate șterge datele de contact."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Permite aplicației să modifice datele despre persoanele de contact salvate pe dispozitivul Android TV, inclusiv frecvența cu care ați apelat, ați trimis e-mailuri sau ați comunicat în alte moduri cu anumite persoane de contact. Cu această permisiune, aplicația poate șterge datele de contact."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Permite aplicației să modifice datele despre persoanele din agenda stocată pe telefon, inclusiv frecvența cu care ați apelat, ați trimis e-mailuri sau ați comunicat în alte moduri cu anumite persoane din agendă. Cu această permisiune aplicația poate șterge datele de contact."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Permite aplicației să modifice datele despre persoanele din agenda stocată pe tabletă. Cu această permisiune, aplicația poate șterge datele de contact."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Permite aplicației să modifice datele despre persoanele din agenda stocată pe dispozitivul Android TV. Cu această permisiune, aplicația poate șterge datele de contact."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Permite aplicației să modifice datele despre persoanele din agenda stocată pe telefon. Cu această permisiune, aplicația poate șterge datele de contact."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"citește jurnalul de apeluri"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Această aplicație poate citi istoricul apelurilor."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"scrie jurnalul de apeluri"</string>
@@ -410,13 +409,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"accesare comenzi suplimentare ale furnizorului locației"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Permite aplicației să acceseze comenzi suplimentare pentru furnizorul locației. Aplicația ar putea să utilizeze această permisiune pentru a influența operațiile GPS sau ale altor surse de locații."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"să acceseze locația exactă în prim-plan"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Aplicația vă poate obține locația exactă numai când rulează în prim-plan. Serviciile de localizare trebuie să fie activate și disponibile pe telefon pentru ca aplicația să le poată folosi. Acest lucru poate accelera descărcarea bateriei."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"să acceseze locația aproximativă (bazată pe rețea) doar în prim-plan"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Aplicația vă poate determina locația folosind sursele din rețea, cum ar fi turnurile de telefonie mobilă și rețelele Wi-Fi, însă numai când rulează în prim-plan. Aceste servicii de localizare trebuie să fie activate și disponibile pe tabletă pentru ca aplicația să le poată folosi."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Aplicația vă poate determina locația folosind sursele din rețea, cum ar fi turnurile de telefonie mobilă și rețelele Wi-Fi, însă numai când rulează în prim-plan. Aceste servicii de localizare trebuie să fie activate și disponibile pe dispozitivul Android TV pentru ca aplicația să le poată folosi."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Aplicația vă poate determina locația folosind sursele din rețea, cum ar fi turnurile de telefonie mobilă și rețelele Wi-Fi, însă numai când rulează în prim-plan. Aceste servicii de localizare trebuie să fie activate și disponibile pe telefon pentru ca aplicația să le poată folosi."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Aplicația vă poate obține locația exactă numai când rulează în prim-plan. Serviciile de localizare trebuie să fie activate și disponibile pe dispozitiv pentru ca aplicația să le poată folosi. Acest lucru poate accelera descărcarea bateriei."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"să acceseze locația aproximativă numai în prim-plan."</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Aplicația vă poate determina locația aproximativă numai când rulează în prim-plan. Serviciile de localizare trebuie să fie activate și disponibile pe dispozitiv pentru ca aplicația să le poată folosi."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"accesați locația în fundal"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Dacă se acordă în plus față de accesul la locație aproximativă sau exactă, aplicația poate accesa locația în timp ce rulează în fundal."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Această aplicație poate accesa locația cât timp rulează în fundal, pe lângă accesul la locație din prim-plan."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modificare setări audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite aplicației să modifice setările audio globale, cum ar fi volumul și difuzorul care este utilizat pentru ieșire."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"înregistreze sunet"</string>
@@ -497,6 +494,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Permite aplicației să vadă configurația tabletei Bluetooth, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Permite aplicației să vadă configurația conexiunii prin Bluetooth a dispozitivului Android TV, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Permite aplicației să vadă configurația telefonului Bluetooth, să efectueze și să accepte conexiuni cu dispozitive împerecheate."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"controlare schimb de date prin Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Permite aplicației să comunice cu etichetele, cardurile și cititoarele NFC (Near Field Communication)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"dezactivează blocarea ecranului"</string>
@@ -1894,7 +1895,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Conectat la <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Atingeți pentru a vedea fișierele"</string>
<string name="pin_target" msgid="8036028973110156895">"Fixați"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Anulați fixarea"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informații despre aplicație"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Se pornește demonstrația…"</string>
@@ -1938,6 +1943,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Actualizați aceste articole în "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> și <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Salvați"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nu, mulțumesc"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Nu acum"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Niciodată"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Actualizați"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Continuați"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"parolă"</string>
@@ -2034,5 +2041,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Activați ecranul împărțit"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Ecran de blocare"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Captură de ecran"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplicația <xliff:g id="APP_NAME">%1$s</xliff:g> în fereastră pop-up."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Bară cu legenda pentru <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index a7abbff..3290591 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Приложение для администрирования рабочего профиля отсутствует или повреждено. Из-за этого рабочий профиль и связанные с ним данные были удалены. Если у вас возникли вопросы, обратитесь к администратору."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Ваш рабочий профиль больше не доступен на этом устройстве"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Слишком много попыток ввести пароль."</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Администратор освободил устройство для личного использования"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Это управляемое устройство"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Ваша организация управляет этим устройством и может отслеживать сетевой трафик. Подробнее…"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Все данные с устройства будут удалены"</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Приложение сможет делать бессрочные рассылки, которые не удаляются после отправки. Злоупотребление ими может замедлить работу устройства Android TV или сделать ее нестабильной из-за чрезмерной загрузки памяти."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Приложение сможет делать несрочные рассылки, которые не удаляются после их отправки. Злоупотребление этими рассылками может замедлить работу телефона или сделать ее нестабильной из-за чрезмерного использования памяти."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"Просмотр контактов"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Приложение сможет просматривать и сохранять все данные о контактах на устройстве, включая то, как часто вы звоните, отправляете письма и другими способами связываетесь с определенными людьми. Вредоносные программы смогут передавать данные о контактах без уведомления."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Приложение получит доступ к контактам, сохраненным на устройстве Android TV, а также к данным о частоте звонков, отправки сообщений электронной почты и других сеансов связи с определенными людьми. Приложения смогут сохранять данные о контактах. Вредоносные программы смогут публиковать их без вашего ведома."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Приложение сможет просматривать и сохранять все данные о контактах на устройстве, включая то, как часто вы звоните, отправляете письма и другими способами связываетесь с определенными людьми. Вредоносные программы смогут передавать данные о контактах без уведомления."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Приложение сможет считывать данные о контактах, сохраненных на вашем планшете. Кроме того, у приложения будет доступ к аккаунтам на планшете, в которых есть контакты. В их число могут входить аккаунты, созданные приложениями, которые вы установили. Получив возможность сохранять данные контактов, вредоносные программы смогут передавать их без вашего ведома."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Приложение сможет считывать данные о контактах, сохраненных на вашем устройстве Android TV. Кроме того, у приложения будет доступ к аккаунтам на устройстве Android TV, в которых есть контакты. В их число могут входить аккаунты, созданные приложениями, которые вы установили. Получив возможность сохранять данные контактов, вредоносные программы смогут передавать их без вашего ведома."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Приложение сможет считывать данные о контактах, сохраненных на вашем телефоне. Кроме того, у приложения будет доступ к аккаунтам на телефоне, в которых есть контакты. В их число могут входить аккаунты, созданные приложениями, которые вы установили. Получив возможность сохранять данные контактов, вредоносные программы смогут передавать их без вашего ведома."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"Изменение контактов"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Приложение сможет изменять сведения о контактах на вашем планшетном ПК, включая то, как часто вы звоните, отправляете письма и другими способами связываетесь с определенными людьми. С этим разрешением приложения смогут удалять данные о контактах."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Приложение сможет изменять сведения о контактах на вашем устройстве Android TV, включая то, как часто вы звоните, отправляете письма и другими способами связываетесь с определенными людьми. С этим разрешением приложения смогут удалять данные о контактах."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Приложение сможет изменять сведения о контактах на вашем телефоне, включая то, как часто вы звоните, отправляете письма и другими способами связываетесь с определенными людьми. С этим разрешением приложения смогут удалять данные о контактах."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Приложение сможет изменять сведения о контактах, сохраненных на вашем планшете. Это разрешение позволяет приложениям удалять данные о контактах."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Приложение сможет изменять сведения о контактах, сохраненных на вашем устройстве Android TV. Это разрешение позволяет приложениям удалять данные о контактах."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Приложение сможет изменять сведения о контактах, сохраненных на вашем телефоне. Это разрешение позволяет приложениям удалять данные о контактах."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"Просмотр журнала вызовов"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Приложение может считывать данные журнала звонков."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"Изменение журнала вызовов"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"Доступ к дополнительным командам управления источниками геоданных"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Доступ к дополнительным командам управления источниками геоданных и вмешательство в работу системы GPS или других источников геоданных."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"Доступ к точному местоположению только в фоновом режиме"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Приложение может получать сведения о вашем точном местоположении только в фоновом режиме. Для этого необходимо включить соответствующие параметры на телефоне и разрешить использовать геоданные. Может увеличиться расход заряда батареи."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"Доступ к примерному местоположению (по координатам сети) только в фоновом режиме"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Приложение может получать сведения о вашем местоположении от источников сетей, таких как вышки сотовой связи и точки доступа Wi-Fi, но только в фоновом режиме. Для этого необходимо включить соответствующие параметры на планшете и разрешить использовать геоданные."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Приложение может получать сведения о вашем местоположении от сетевых источников, таких как вышки сотовой связи и точки доступа Wi-Fi, но только в активном режиме. Для этого необходимо включить на устройстве Android TV соответствующие сервисы геолокации и разрешить приложению использовать их."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Приложение может получать сведения о вашем местоположении от источников сетей, таких как вышки сотовой связи и точки доступа Wi-Fi, но только в фоновом режиме. Для этого необходимо включить соответствующие параметры на телефоне и разрешить использовать геоданные."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Приложение сможет получать сведения о вашем точном местоположении только в активном режиме. Для этого нужно включить геолокацию на устройстве и разрешить приложению использовать геоданные. Расход заряда батареи может увеличиться."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"Доступ к приблизительному местоположению только в активном режиме"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Приложение сможет получать сведения о вашем приблизительном местоположении только в активном режиме. Для этого нужно включить геолокацию на устройстве и разрешить приложению использовать геоданные."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"доступ к геоданным в фоновом режиме"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Если помимо доступа к данным о точном или приблизительном местоположении вы предоставите это разрешение, приложение сможет получать доступ к геоданным в фоновом режиме."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Приложение сможет получать сведения о вашем местоположении как в фоновом, так и в активном режиме."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Изменение настроек аудио"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Приложение сможет изменять системные настройки звука, например уровень громкости и активный динамик."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"Запись аудио"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Приложение сможет просматривать конфигурацию Bluetooth на планшетном ПК, а также запрашивать и подтверждать соединение с другими устройствами."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Приложение сможет просматривать конфигурацию Bluetooth на устройстве Android TV, а также запрашивать и подтверждать соединение с другими устройствами."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Приложение сможет просматривать конфигурацию Bluetooth на телефоне, а также запрашивать и подтверждать соединение с другими устройствами."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"Управление NFC-модулем"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Приложение сможет обмениваться данными с NFC-метками, картами и устройствами считывания, используя NFC."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"Отключение функции блокировки экрана"</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Подключено к <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Нажмите, чтобы просмотреть файлы"</string>
<string name="pin_target" msgid="8036028973110156895">"Закрепить"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Открепить"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"О приложении"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Запуск деморежима…"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Обновить данные (<xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, <xliff:g id="TYPE_2">%3$s</xliff:g>) в сервисе "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Сохранить"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Нет, спасибо"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Не сейчас"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Никогда"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Обновить"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Продолжить"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"Пароль"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Включить или выключить разделение экрана"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Заблокированный экран"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Скриншот"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" в всплывающем окне."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Строка субтитров в приложении \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"."</string>
</resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 7d3f80d..eb633b5 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"කාර්යාල පැතිකඩ පාලක යෙදුම නොමැති හෝ දූෂණය වී ඇත. ප්රතිඵලයක් ලෙස ඔබගේ කාර්යාල පැතිකඩ සහ අදාළ දත්ත මකා දමා ඇත. සහය සඳහා ඔබගේ පරිපාලකයා සම්බන්ධ කර ගන්න."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ඔබේ කාර්යාල පැතිකඩ මෙම උපාංගය මත තවදුරටත් ලබා ගැනීමට නොහැකිය"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"මුරපද උත්සාහ කිරීම් ඉතා වැඩි ගණනකි"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"පරිපාලක පුද්ගලික භාවිතය සඳහා උපාංගය අත්හැර දමන ලදී"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"උපාංගය කළමනාකරණය කෙරේ"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"ඔබගේ ආයතනය මෙම උපාංගය කළමනාකරණය කරන අතර එය ජාල තදබදය නිරීක්ෂණය කළ හැක. විස්තර සඳහා තට්ටු කරන්න."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"ඔබගේ උපාංගය මකා දැමෙනු ඇත"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"යෙදුමට විකාශයෙන් පසුවද ඉතිරි වන, ඇලවෙන විකාශ යැවීමට ඉඩ දෙයි. වැඩියෙන් භාවිතය වැඩිපුර මතකය භාවිත කිරීම මගින් ඔබේ Android TV උපාංගය මන්දගාමී හෝ අස්ථායී බවට පත් කළ හැකිය."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ප්රචාරණයට පසුවද පවතින, ප්රචාරණයන් යැවීමට යෙදුමට අවසර දෙන්න. වැඩිපුර මතකය භාවිතය හේතු කොට, අධික භාවිතය මඟින් දුරකථනය පමා කිරීම හෝ අස්ථිර කළ හැක."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"ඔබගේ සම්බන්ධතා කියවීම"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"සඳහන් පුද්ගලයන් හට ඔබ ඇමතුම් ගත්, ඊ-තැපැල්, හෝ අනෙකුත් ආකාර වලින් සන්නිවේදනය කරගත් සංඛ්යතද ඇතුළුව, ඔබගේ ටැබ්ලටයේ ගබඩාවී ඇති සම්බන්ධතා පිළිබඳ දත්ත කියවීමට යෙදුමට අවසර දෙන්න. මෙම අවසරය මඟින් යෙදුම්වලට ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත සුරැකීමට ඉඩ ලබා දෙන අතර, අනිෂ්ට යෙදුම් විසින් ඔබ නොදැනුවත්වම සම්බන්ධතා දත්ත බෙදා ගැනීමට ඉඩ ඇත."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"ඔබ නිශ්චිත පුද්ගලයන් ඇමතූ, ඉ-තැපැල් කළ හෝ වෙනත් ආකාරයකින් සන්නිවේදනය කළ වාර ගණනද ඇතුළුව, ඔබගේ Android TV උපාංගයේ ගබඩා කර ඇති ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත කියවීමට යෙදුමට ඉඩ දෙයි. මෙම අවසරය යෙදුම්වලට ඔබගේ සම්බන්ධතා දත්ත සුරැකීමට ඉඩ දෙන අතර, අනිෂ්ට යෙදුම් ඔබගේ දැනුමෙන් තොරව සම්බන්ධතා දත්ත බෙදා ගත හැකිය."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"නියමිත පුද්ගලයන් සමග ඔබ ඇමතු, ඊ-තැපැල් කළ හෝ වෙනත් ආකාරයකින් සන්නිවේදනය කළ සංඛ්යාතය ඇතුලත් ඔබගේ දුරකථනයේ ආචයනය කරන ලද ඔබගේ සම්බන්ධතා ගැන දත්ත කියවීමට යෙදුමට අවසර දෙන්න. ඔබගේ සම්බන්ධතා දත්ත උපස්ථ කිරීමට මෙම අවසරය යෙදුමට අවසර දෙන අතර ඔබගේ දැනුමකින් තොරව අනිෂ්ට යෙදුම් සම්බන්ධතා දත්ත බෙදාගැනීම කළ හැක."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"ඔබගේ ටැබ්ලට් පරිගණකයේ ගබඩා කර ඇති ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත කියවීමට යෙදුමට ඉඩ දෙයි. යෙදුම්වලට සම්බන්ධතා තැනූ ඔබගේ ටැබ්ලට් පරිගණකයේ ගිණුම්වලට ප්රවේශය ද ලැබෙනු ඇත. මෙයට ඔබ ස්ථාපනය කර ඇති යෙදුම් මගින් තැනූ ගිණුම් ඇතුළත් විය හැකිය. මෙම අවසරය යෙදුම්වලට ඔබගේ සම්බන්ධතා දත්ත සුරැකීමට ඉඩ දෙන අතර, අනිෂ්ට යෙදුම් ඔබගේ දැනුමෙන් තොරව සම්බන්ධතා දත්ත බෙදා ගත හැකිය."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"ඔබගේ Android TV උපාංගයේ ගබඩා කර ඇති ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත කියවීමට යෙදුමට ඉඩ දෙයි. යෙදුම්වලට සම්බන්ධතා තැනූ ඔබගේ Android TV උපාංගයේ ගිණුම්වලට ප්රවේශය ද ලැබෙනු ඇත. මෙයට ඔබ ස්ථාපනය කර ඇති යෙදුම් මගින් තැනූ ගිණුම් ඇතුළත් විය හැකිය. මෙම අවසරය යෙදුම්වලට ඔබගේ සම්බන්ධතා දත්ත සුරැකීමට ඉඩ දෙන අතර, අනිෂ්ට යෙදුම් ඔබගේ දැනුමෙන් තොරව සම්බන්ධතා දත්ත බෙදා ගත හැකිය."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ඔබගේ දුරකථනයේ ගබඩා කර ඇති ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත කියවීමට යෙදුමට ඉඩ දෙයි. යෙදුම්වලට සම්බන්ධතා තැනූ ඔබගේ දුරකථනයේ ගිණුම්වලට ප්රවේශය ද ලැබෙනු ඇත. මෙයට ඔබ ස්ථාපනය කර ඇති යෙදුම් මගින් තැනූ ගිණුම් ඇතුළත් විය හැකිය. මෙම අවසරය යෙදුම්වලට ඔබගේ සම්බන්ධතා දත්ත සුරැකීමට ඉඩ දෙන අතර, අනිෂ්ට යෙදුම් ඔබගේ දැනුමෙන් තොරව සම්බන්ධතා දත්ත බෙදා ගත හැකිය."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ඔබගේ සම්බන්ධතා වෙනස් කිරීම"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"නියමිත පුද්ගලයන්ට ඔබ ඇමතූ, ඊ-තැපැල් කළ හෝ ඇමතුම් කළ සංඛ්යාත ඇතුලත් ඔබගේ ටැබ්ලටයේ ආචයනය කරන ලද සම්බන්ධතා (ලිපින) දත්ත වෙනස් කිරීමට යෙදුමට අවසර දෙන්න. මෙම අවසරයෙන් යෙදුමට සම්බන්ධතා දත්ත මැකීමට අවසර දෙයි."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"ඔබ නිශ්චිත සම්බන්ධතා ඇමතූ, ඉ-තැපැල් කළ හෝ වෙනත් ආකාරයකින් සන්නිවේදනය කළ වාර ගණනද ඇතුළුව, ඔබගේ Android TV උපාංගයේ ගබඩා කර ඇති ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත වෙනස් කිරීමට යෙදුමට ඉඩ දෙයි. මෙම අවසරය යෙදුම්වලට සම්බන්ධතා දත්ත මැකීමට ඉඩ දෙයි."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"සඳහන් පුද්ගලයන්ට ඔබ ඇමතූ, ඊ-තැපැල් කළ හෝ ඇමතුම් කළ සංඛ්යාන ඇතුලත් ඔබගේ දුරකථනයේ ආචයනය කරන ලද සම්බන්ධතා (ලිපින) දත්ත වෙනස් කිරීමට යෙදුමට අවසර දෙන්න. මෙම අවසරයෙන් යෙදුමට සම්බන්ධතා දත්ත මැකීමට අවසර දෙයි."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"ඔබගේ ටැබ්ලට් පරිගණකයේ ගබඩා කර ඇති ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත වෙනස් කිරීමට යෙදුමට ඉඩ දෙයි. මෙම අවසරය යෙදුම්වලට සම්බන්ධතා දත්ත මැකීමට ඉඩ දෙයි."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"ඔබගේ Android TV උපාංගයේ ගබඩා කර ඇති ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත වෙනස් කිරීමට යෙදුමට ඉඩ දෙයි. මෙම අවසරය යෙදුම්වලට සම්බන්ධතා දත්ත මැකීමට ඉඩ දෙයි."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ඔබගේ දුරකථනයේ ගබඩා කර ඇති ඔබගේ සම්බන්ධතා පිළිබඳ දත්ත වෙනස් කිරීමට යෙදුමට ඉඩ දෙයි. මෙම අවසරය යෙදුම්වලට සම්බන්ධතා දත්ත මැකීමට ඉඩ දෙයි."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ඇමතුම් ලොගය කියවන්න"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"මෙම යෙදුමට ඔබේ ඇමතුම් ඉතිහාසය කියවීමට හැකිය."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"ඇමතුම් ලොගය ලිවීම"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"අමතර ස්ථාන සැපයුම්කරු විධාන වෙත ප්රවේශ වීම"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ස්ථානය සපයන අමතර අණ වලට ප්රවේශය කිරීමට යෙදුමට අවසර දෙන්න. GPS ක්රියාවන් හෝ වෙනත් ස්ථාන මූලාශ්ර සමඟ මැදිහත් වීමට මෙයින් යෙදුමට ඉඩ ලැබේ."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"පෙරබිම තුළ පමණක් නිශ්චිත ස්ථානය වෙත පිවිසෙන්න"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"මෙම යෙදුම පෙරබිම තුළ ඇති විට පමණක් එයට ඔබේ නිශ්චිත ස්ථානය ලබා ගත හැකිය. යෙදුමට ඒවා භාවිත කිරීමට හැකි වීමට මෙම ස්ථාන සේවා ක්රියාත්මක කර සහ ඔබේ දුරකථනය මත ලබා ගත හැකිව තිබිය යුතුය. මෙය බැටරි පරිභෝජනය වැඩි කළ හැකිය."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"ඉදිරියේ ඇති ආසන්න ස්ථානය (ජාලය පදනම්වූ) පමණක් ප්ර වේශ වන්න"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"මෙම යෙදුමට ජංගම දුරකථන කුළුණු සහ Wi-Fi ජාල වැනි ජාල මූලාශ්ර පදනම්ව ඔබගේ ස්ථානය ලබා ගත හැකි නමුත් යෙදුම ඉදිරියේ ඇති විට පමණී. යෙදුමට ඒවා භාවිත කිරීමට හැකි වීමට මෙම ස්ථාන සේවා ක්රියාත්මක කර ඔබේ ටැබ්ලටය මත ලබා ගත හැකිව තිබිය යුතුය."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"මෙම යෙදුමට ජංගම දුරකථන කුළුණු සහ Wi-Fi ජාල වැනි ජාල මූලාශ්ර පදනම්ව ඔබගේ ස්ථානය ලබා ගත හැකි නමුත් යෙදුම ඉදිරියේ ඇති විට පමණී. යෙදුමට ඒවා භාවිත කිරීමට හැකි වීමට මෙම ස්ථාන සේවා ක්රියාත්මක කර ඔබේ ඔබගේ Android TV උපාංගය මත ලබා ගත හැකිව තිබිය යුතුය."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"මෙම යෙදුමට ජංගම දුරකථන කුළුණු සහ Wi-Fi ජාල වැනි ජාල මූලාශ්ර පදනම්ව ඔබගේ ස්ථානය ලබා ගත හැකි නමුත් යෙදුම ඉදිරියේ ඇති විට පමණී. යෙදුමට ඒවා භාවිත කිරීමට හැකි වීමට මෙම ස්ථාන සේවා ක්රියාත්මක කර ඔබේ දුරකථනය මත ලබා ගත හැකිව තිබිය යුතුය."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"මෙම යෙදුම පෙරබිම තුළ ඇති විට පමණක් එයට ඔබගේ නිශ්චිත ස්ථානය ලබා ගත හැකිය. යෙදුමට ඒවා භාවිත කිරීමට හැකියාව ලැබීමට ස්ථාන සේවා ක්රියාත්මක කර ඔබගේ දුරකථනයේ ලබා ගත හැකි විය යුතුය. මෙය බැටරි පරිභෝජනය වැඩි කළ හැකිය."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"පෙරබිම තුළ පමණක් ආසන්න ස්ථානයට ප්රවේශය"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"මෙම යෙදුම පෙරබිම තුළ ඇති විට පමණක් එයට ඔබගේ ආසන්න ස්ථානය ලබා ගත හැකිය. යෙදුමට ඒවා භාවිත කිරීමට හැකියාව ලැබීමට ස්ථාන සේවා ක්රියාත්මක කර ඔබගේ උපාංගයේ ලබා ගත හැකි විය යුතුය."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"පසුබිමේ ස්ථානය ප්රවේශ කිරීම"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"මෙය ආසන්න වශයෙන් හෝ නිශ්චිත ස්ථානයක ප්රවේශය ලබා දෙන්නේ නම් පසුබිම් ධාවන අතරතුරදී යෙදුමට ස්ථානය වෙත ප්රවේශය විය හැක."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"පෙරබිම ස්ථාන ප්රවේශයට අමතරව පසුබිමේ ධාවනය වන අතරතුර මෙම යෙදුමට ස්ථානය වෙත ප්රවේශ විය හැකිය."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ඔබගේ ශ්රව්ය සැකසීම් වෙනස් කරන්න"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ශබ්දය ආදී ගෝලීය ශබ්ද සැකසීම් වෙනස් කිරීමට සහ ප්රතිදානය සඳහා භාවිත කරන්නේ කුමන නාදකය දැයි තේරීමට යෙදුමට අවසර දෙන්න."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ශබ්ද පටිගත කරන්න"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ටැබ්ලටයේ බ්ලූටූත් වින්යාසය බැලිමට, සැකසීමට සහ යුගල කළ උපාංග සමඟ සම්බන්ධතාවන් පිළිගැනීමට යෙදුමට අවසර දෙන්න."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ඔබගේ Android TV උපාංගයේ බ්ලූටූත් හි වින්යාසකරණය බැලීමට, සහ යුගල කළ උපාංග සමඟ සම්බන්ධතා ඇති කර ගැනීමට සහ පිළිගැනීමට යෙදුමට ඉඩ දෙයි."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"දුරකථනයේ බ්ලූටූත් වින්යාසය දැකීමට, යුගල උපාංග සමඟ සම්බන්ධතාවන් සැකසීමට සහ භාරගැනීමට යෙදුමට අවසර දෙයි."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ආසන්න ක්ෂේත්ර සන්නිවේදනය පාලනය කරන්න"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ආසන්න ක්ෂේත්ර සන්නිවේදන (NFC) ටැග්, පත්, සහ කියවන්නන් සමඟ සන්නිවේදනය කිරීමට යෙදුමට අවසර දෙන්න."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ඔබගේ තිරයේ අගුල අබල කරන්න"</string>
@@ -1864,7 +1865,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> වෙත සම්බන්ධ විය"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ගොනු බැලීමට තට්ටු කරන්න"</string>
<string name="pin_target" msgid="8036028973110156895">"අමුණන්න"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"ගලවන්න"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"යෙදුම් තොරතුරු"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ආදර්ශනය ආරම්භ කරමින්..."</string>
@@ -1907,6 +1912,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" තුළ යාවත්කාලීන කරන්නද?: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, සහ <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"සුරකින්න"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"එපා ස්තූතියි"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"දැන් නොවේ"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"කිසිදා නැත"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"යාවත්කාලීන කරන්න"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ඉදිරියට යන්න"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"මුරපදය"</string>
@@ -2002,5 +2009,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"බෙදුම් තිරය ටොගල කරන්න"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"අගුලු තිරය"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"තිර රුව"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"උත්පතන කවුළුව තුළ <xliff:g id="APP_NAME">%1$s</xliff:g> යෙදුම."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> හි සිරස්තල තීරුව."</string>
</resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 3a81468c..769c1b07 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplikácia na správu pracovného profilu buď chýba, alebo je poškodená. Z toho dôvodu bol odstránený pracovný profil aj k nemu priradené dáta. Ak potrebujete pomoc, kontaktujte svojho správcu."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Váš pracovný profil už v tomto zariadení nie je k dispozícii"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Príliš veľa pokusov o zadanie hesla"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Správca uvoľnil toto zariadenie na osobné používanie"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Zariadenie je spravované"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Vaša organizácia spravuje toto zariadenie a môže sledovať sieťovú premávku. Klepnutím zobrazíte podrobnosti."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Vaše zariadenie bude vymazané"</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Umožňuje aplikácii odosielať trvalé vysielania, ktoré pretrvávajú aj po skončení vysielania. Nadmerné používanie môže zariadenie Android TV spomaliť alebo spôsobiť jeho nestabilitu, pretože bude používať príliš veľa pamäte."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Umožňuje aplikácii odosielať trvalé vysielania, ktoré pretrvávajú aj po skončení vysielania. Nadmerné používanie môže telefón spomaliť alebo spôsobiť jeho nestabilitu, pretože bude používať príliš veľa pamäte."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"čítať kontakty"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Umožňuje aplikácii čítať údaje o kontaktoch uložených v tablete vrátane informácií o frekvencii vašich telefonátov, odoslaných e-mailov alebo iných foriem komunikácie s konkrétnymi osobami. Toto povolenie umožňuje aplikáciám ukladať údaje o kontaktoch. Škodlivé aplikácie môžu zdieľať údaje o kontaktoch bez vášho vedomia."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Umožňuje aplikácii čítať údaje o kontaktoch, ktoré máte uložené v zariadení Android TV, vrátane informácií o frekvencii vašich volaní, odoslaných e‑mailov alebo iných foriem komunikácie s konkrétnymi osobami. Toto povolenie umožňuje aplikáciám ukladať údaje kontaktov. Škodlivé aplikácie môžu zdieľať údaje kontaktov bez vášho vedomia."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Umožňuje aplikácii čítať údaje o kontaktoch uložených v telefóne vrátane informácií o frekvencii vašich telefonátov, odoslaných e-mailov alebo iných foriem komunikácie s konkrétnymi osobami. Toto povolenie umožňuje aplikáciám ukladať údaje o kontaktoch. Škodlivé aplikácie môžu zdieľať údaje o kontaktoch bez vášho vedomia."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Umožňuje aplikácii čítať údaje o kontaktoch uložených v tablete. Aplikácie budú mať tiež prístup k účtom v tablete, ktoré vytvorili kontakty. Môže ísť o účty vytvorené aplikáciami, ktoré ste nainštalovali. Toto povolenie umožňuje aplikáciám ukladať údaje o kontaktoch. Škodlivé aplikácie môžu zdieľať údaje o kontaktoch bez vášho vedomia."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Umožňuje aplikácii čítať údaje o kontaktoch uložených v zariadení Android TV. Aplikácie budú mať tiež prístup k účtom v zariadení Android TV, ktoré vytvorili kontakty. Môže ísť o účty vytvorené aplikáciami, ktoré ste nainštalovali. Toto povolenie umožňuje aplikáciám ukladať údaje o kontaktoch. Škodlivé aplikácie môžu zdieľať údaje o kontaktoch bez vášho vedomia."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Umožňuje aplikácii čítať údaje o kontaktoch uložených v telefóne. Aplikácie budú mať tiež prístup k účtom v telefóne, ktoré vytvorili kontakty. Môže ísť o účty vytvorené aplikáciami, ktoré ste nainštalovali. Toto povolenie umožňuje aplikáciám ukladať údaje o kontaktoch. Škodlivé aplikácie môžu zdieľať údaje o kontaktoch bez vášho vedomia."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"upraviť kontakty"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Umožňuje aplikácii upraviť údaje o kontaktoch uložených v tablete vrátane informácií o frekvencii vašich telefonátov, odoslaných e-mailov alebo iných foriem komunikácie s konkrétnymi osobami. Toto povolenie umožňuje aplikáciám odstraňovať údaje o kontaktoch."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Umožňuje aplikácii upravovať údaje o kontaktoch, ktoré máte uložené v zariadení Android TV, vrátane informácií o frekvencii vašich volaní, odoslaných e‑mailov alebo iných foriem komunikácie s konkrétnymi kontaktmi. Toto povolenie umožňuje aplikáciám odstraňovať údaje kontaktov."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Umožňuje aplikácii upraviť údaje o kontaktoch uložených v telefóne vrátane informácií o frekvencii vašich telefonátov, odoslaných e-mailov alebo iných foriem komunikácie s konkrétnymi osobami. Toto povolenie umožňuje aplikáciám odstraňovať údaje o kontaktoch."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Umožňuje aplikácii upraviť údaje o kontaktoch uložených v tablete. Toto povolenie umožňuje aplikáciám odstraňovať údaje o kontaktoch."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Umožňuje aplikácii upraviť údaje o kontaktoch uložených v zariadení Android TV. Toto povolenie umožňuje aplikáciám odstraňovať údaje o kontaktoch."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Umožňuje aplikácii upraviť údaje o kontaktoch uložených v telefóne. Toto povolenie umožňuje aplikáciám odstraňovať údaje o kontaktoch."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"čítať denník hovorov"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Táto aplikácia môže čítať históriu vašich hovorov."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"zapisovať do denníka hovorov"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"prístup k ďalším príkazom poskytovateľa polohy"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Umožňuje aplikácii pristupovať k ďalším príkazom poskytovateľa informácií o polohe. Aplikácii to môže umožniť zasahovať do činnosti systému GPS alebo iných zdrojov informácií o polohe."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"prístup k presnej polohe iba v popredí"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Táto aplikácia dokáže získať vašu presnú polohu iba vtedy, keď je spustená v popredí. Na to, aby mohla používať služby určovania polohy, musia byť tieto služby zapnuté a k dispozícii v telefóne. Môže to zvýšiť spotebu batérie."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"prístup k približnej polohe (pomocou siete) iba v popredí"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Táto aplikácia môže získať údaje o vašej polohe na základe sieťových zdrojov, ako sú mobilné veže a siete Wi‑Fi, keď je spustená v popredí. Na to, aby aplikácia mohla používať služby určovania polohy, musia byť tieto služby zapnuté a k dispozícii v tablete."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Táto aplikácia môže získať údaje o vašej polohe na základe sieťových zdrojov, ako sú vysielače mobilnej siete a siete Wi‑Fi, keď je spustená v popredí. Tieto služby musia byť zapnuté a k dispozícii v zariadení Android TV, aby aplikácia mohla používať služby určovania polohy."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Táto aplikácia môže získať údaje o vašej polohe na základe sieťových zdrojov, ako sú mobilné veže a siete Wi‑Fi, ale iba keď je spustená v popredí. Na to, aby aplikácia mohla používať služby určovania polohy, musia byť tieto služby zapnuté a k dispozícii v telefóne."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Táto aplikácia dokáže získať vašu presnú polohu iba vtedy, keď je spustená v popredí. Služby určovania polohy musia byť zapnuté a k dispozícii v zariadení, aby ich mohla aplikácia používať. Môže to zvýšiť spotrebu batérie."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"prístup k približnej polohe iba v popredí"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Táto aplikácia dokáže získať vašu približnú polohu iba vtedy, keď je spustená v popredí. Služby určovania polohy musia byť zapnuté a k dispozícii v zariadení, aby ich mohla aplikácia používať."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"prístup k polohe na pozadí"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ak okrem prístupu k približnej alebo presnej polohe udelíte aj toto povolenie, aplikácia bude môcť používať polohu, keď bude spustená na pozadí."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Táto aplikácia môže používať polohu nielen vtedy, keď je spustená v popredí, ale aj na pozadí."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"meniť nastavenia zvuku"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Umožňuje aplikácii upraviť globálne nastavenia zvuku, ako je hlasitosť, alebo určiť, z ktorého reproduktora bude zvuk vychádzať."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"nahrávať zvuk"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Umožňuje aplikácii zobraziť informácie o konfigurácii Bluetooth na tablete. Taktiež jej umožňuje nadväzovať a akceptovať spojenia so spárovanými zariadeniami."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Umožňuje aplikácii zobraziť konfiguráciu rozhrania Bluetooth v zariadení Android TV, ako aj nadväzovať a prijímať pripojenia so spárovanými zariadeniami."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Umožňuje aplikácii zobraziť informácie o konfigurácii Bluetooth na telefóne. Taktiež jej umožňuje nadväzovať a akceptovať spojenia so spárovanými zariadeniami."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ovládať technológiu NFC"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Umožňuje aplikácii komunikovať so značkami, kartami a čítačkami s podporou technológie NFC."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"deaktivácia zámky obrazovky"</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Pripojené k zariadeniu <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Klepnutím zobrazíte súbory"</string>
<string name="pin_target" msgid="8036028973110156895">"Pripnúť"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Uvoľniť"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Info o aplikácii"</string>
<string name="negative_duration" msgid="1938335096972945232">"-<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Spúšťa sa ukážka…"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Chcete tieto položky aktualizovať v službe "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> a <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Uložiť"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nie, vďaka"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Teraz nie"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nikdy"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Aktualizovať"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Pokračovať"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"heslo"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Prepnúť rozdelenú obrazovku"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Uzamknúť obrazovku"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Snímka obrazovky"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> vo vyskakovacom okne."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Popis aplikácie <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index c28384c..be173dd 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Skrbniška aplikacija delovnega profila manjka ali pa je poškodovana, zaradi česar je bil delovni profil s povezanimi podatki izbrisan. Za pomoč se obrnite na skrbnika."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Vaš delovni profil ni več na voljo v tej napravi"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Preveč poskusov vnosa gesla"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Skrbnik je napravo prepustil osebni uporabi"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Naprava je upravljana"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Vaša organizacija upravlja to napravo in lahko nadzira omrežni promet. Dotaknite se za podrobnosti."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Podatki v napravi bodo izbrisani"</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Aplikaciji dovoljuje pošiljanje fiksnih oddaj, ki ostanejo po koncu oddajanja. Zaradi prekomerne uporabe je delovanje naprave Android TV lahko počasno ali nestabilno, ker porabi preveč pomnilnika."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Aplikaciji omogoča hitro pošiljanje fiksnih oddaj, ki ostanejo po koncu oddajanja. Zaradi prekomerne uporabe je delovanje telefona lahko počasno ali nestabilno, ker porabi preveč pomnilnika."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"branje stikov"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Aplikaciji omogoča branje podatkov o stikih v tabličnem računalniku, vključno s pogostostjo klicev, pošiljanja e-poštnih sporočil in druge komunikacije s posamezniki. S tem dovoljenjem lahko aplikacije shranjujejo podatke o stikih in zlonamerne aplikacije lahko te podatke razkrijejo brez vaše vednosti."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Aplikaciji dovoljuje branje podatkov o stikih, shranjenih v napravi Android TV, vključno s pogostostjo klicev, pošiljanja e-poštnih sporočil in druge komunikacije s posamezniki. S tem dovoljenjem lahko aplikacije shranjujejo podatke o stikih in zlonamerne aplikacije lahko te podatke razkrijejo brez vaše vednosti."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Aplikaciji omogoča branje podatkov o stikih v telefonu, vključno s pogostostjo klicev, pošiljanja e-poštnih sporočil in druge komunikacije s posamezniki. S tem dovoljenjem lahko aplikacije shranjujejo podatke o stikih in zlonamerne aplikacije lahko te podatke razkrijejo brez vaše vednosti."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Aplikaciji dovoljuje branje podatkov o stikih, shranjenih v tabličnem računalniku. Aplikacije bodo imele dostop tudi do računov v tabličnem računalniku, ki so ustvarili stike. To lahko vključuje račune, ki so jih ustvarile nameščene aplikacije. S tem dovoljenjem lahko aplikacije shranjujejo podatke o stikih in zlonamerne aplikacije lahko te podatke razkrijejo brez vaše vednosti."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Aplikaciji dovoljuje branje podatkov o stikih, shranjenih v napravi Android TV. Aplikacije bodo imele dostop tudi do računov v napravi Android TV, ki so ustvarili stike. To lahko vključuje račune, ki so jih ustvarile nameščene aplikacije. S tem dovoljenjem lahko aplikacije shranjujejo podatke o stikih in zlonamerne aplikacije lahko te podatke razkrijejo brez vaše vednosti."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Aplikaciji dovoljuje branje podatkov o stikih, shranjenih v telefonu. Aplikacije bodo imele dostop tudi do računov v telefonu, ki so ustvarili stike. To lahko vključuje račune, ki so jih ustvarile nameščene aplikacije. S tem dovoljenjem lahko aplikacije shranjujejo podatke o stikih in zlonamerne aplikacije lahko te podatke razkrijejo brez vaše vednosti."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"spreminjanje stikov"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Aplikaciji omogoča spreminjanje podatkov o stikih v tabličnem računalniku, vključno s pogostostjo klicev, pošiljanja e-poštnih sporočil in druge komunikacije z določenimi stiki. S tem dovoljenjem lahko aplikacije brišejo podatke o stikih."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Aplikaciji dovoljuje spreminjanje podatkov o stikih, shranjenih v napravi Android TV, vključno s pogostostjo klicev, pošiljanja e-poštnih sporočil in druge komunikacije z določenimi stiki. S tem dovoljenjem lahko aplikacije brišejo podatke o stikih."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Aplikaciji omogoča spreminjanje podatkov o stikih v telefonu, vključno s pogostostjo klicev, pošiljanja e-poštnih sporočil in druge komunikacije z določenimi stiki. S tem dovoljenjem lahko aplikacije brišejo podatke o stikih."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Aplikaciji dovoljuje spreminjanje podatkov o stikih, shranjenih v tabličnem računalniku. S tem dovoljenjem lahko aplikacije brišejo podatke o stikih."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Aplikaciji dovoljuje spreminjanje podatkov o stikih, shranjenih v napravi Android TV. S tem dovoljenjem lahko aplikacije brišejo podatke o stikih."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Aplikaciji dovoljuje spreminjanje podatkov o stikih, shranjenih v telefonu. S tem dovoljenjem lahko aplikacije brišejo podatke o stikih."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"branje dnevnika klicev"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Ta aplikacija lahko prebere zgodovino klicev."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"pisanje v dnevnik klicev"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"dostopanje do ukazov ponudnika dodatnih lokacij"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Aplikaciji omogoča dostop do dodatnih ukazov ponudnika lokacij. S tem lahko aplikacija moti delovanje sistema GPS ali drugih virov lokacije."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"dostop do točne lokacije samo, ko deluje v ospredju"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Ta aplikacija lahko pridobi vašo točno lokacijo samo, ko deluje v ospredju. Če želite aplikaciji omogočiti uporabo teh lokacijskih storitev, morajo biti te vklopljene in na voljo v telefonu. Poraba energije akumulatorja bo morda večja."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"dostop do približne lokacije (na podlagi omrežja) samo med delovanjem v ospredju"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ta aplikacija lahko pridobi vašo lokacijo na podlagi omrežnih virov, kot so bazne postaje in omrežja Wi-Fi, vendar samo, ko deluje v ospredju. Če želite aplikaciji omogočiti uporabo teh lokacijskih storitev, morajo biti te vklopljene in na voljo v tabličnem računalniku."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ta aplikacija lahko pridobi vašo lokacijo na podlagi omrežnih virov, kot so bazne postaje in omrežja Wi-Fi, vendar samo, ko deluje v ospredju. Če želite aplikaciji omogočiti uporabo teh lokacijskih storitev, morajo biti te vklopljene in na voljo v napravi Android TV."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ta aplikacija lahko pridobi vašo lokacijo na podlagi omrežnih virov, kot so bazne postaje in omrežja Wi-Fi, vendar samo, ko deluje v ospredju. Če želite aplikaciji omogočiti uporabo teh lokacijskih storitev, morajo biti te vklopljene in na voljo v telefonu."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ta aplikacija lahko pridobi vašo točno lokacijo samo, ko deluje v ospredju. Če želite aplikaciji omogočiti uporabo lokacijskih storitev, morajo biti te vklopljene in na voljo v napravi. Poraba energije baterije bo morda večja."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"dostop do približne lokacije samo, ko deluje v ospredju"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ta aplikacija lahko pridobi vašo približno lokacijo samo, ko deluje v ospredju. Če želite aplikaciji omogočiti uporabo lokacijskih storitev, morajo biti te vklopljene in na voljo v napravi."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"dostop do lokacije med izvajanjem v ozadju"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Če to dovolite poleg dostopa do približne ali natančne lokacije, lahko aplikacija dostopa do lokacije, medtem ko se izvaja v ozadju."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ta aplikacija lahko dostopa do lokacije, ko deluje v ospredju ali ozadju."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"spreminjanje nastavitev zvoka"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Aplikaciji omogoča spreminjanje splošnih zvočnih nastavitev, na primer glasnost in kateri zvočnik se uporablja."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"snemanje zvoka"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Aplikaciji omogoča ogled konfiguracije Bluetootha tabličnega računalnika ter vzpostavljanje in sprejemanje povezave s seznanjenimi napravami."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Aplikaciji dovoljuje ogled konfiguracije vmesnika Bluetooth v napravi Android TV ter ustvarjanje in sprejemanje povezav s seznanjenimi napravami."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Aplikaciji omogoča ogled konfiguracije Bluetootha telefona ter ustvarjanje in sprejemanje povezave s seznanjenimi napravami."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"nadzor nad komunikacijo s tehnologijo bližnjega polja"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Podpira komunikacijo med računalnikom in oznakami, karticami in bralniki komunikacije s tehnologijo bližnjega polja."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"onemogočanje zaklepanja zaslona"</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Vzpostavljena povezava z napravo <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Dotaknite se, če si želite ogledati datoteke"</string>
<string name="pin_target" msgid="8036028973110156895">"Pripenjanje"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Odpenjanje"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Podatki o aplikacijah"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Začenjanje predstavitve …"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Želite posodobiti te elemente v aplikaciji "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> in <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Shrani"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ne, hvala"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Ne zdaj"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Nikoli"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Posodobi"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Naprej"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"geslo"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Preklop razdeljenega zaslona"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Zaklenjen zaslon"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Posnetek zaslona"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v pojavnem oknu."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Vrstica s podnapisi aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 39f7377..4c02bc7 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Aplikacioni i administratorit të profilit të punës mungon ose është dëmtuar. Si rezultat i kësaj, profili yt i punës dhe të dhënat përkatëse janë fshirë. Kontakto me administratorin për ndihmë."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Profili yt i punës nuk është më i disponueshëm në këtë pajisje"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Shumë përpjekje për fjalëkalimin"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratori e refuzoi pajisjen për përdorim personal"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Pajisja është e menaxhuar"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Organizata jote e menaxhon këtë pajisje dhe mund të monitorojë trafikun e rrjetit. Trokit për detaje."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Pajisja do të spastrohet"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Lejon aplikacionin të dërgojë transmetime ngjitëse të cilat mbeten pas përfundimit të transmetimit. Përdorimi i tepërt mund ta bëjë pajisjen tënde Android TV të ngadaltë ose të paqëndrueshme, duke e detyruar atë të përdorë më shumë memorie."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Lejon aplikacionin të dërgojë transmetime ngjitëse që qëndrojnë derisa të përfundojë transmetimi. Përdorimi i tepruar mund ta bëjë telefonin të ngadaltë ose të paqëndrueshëm duke e detyruar atë të përdorë shumë kujtesë."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"lexo kontaktet e tua"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Lejon aplikacionin të lexojë të dhëna rreth kontakteve të ruajtura në tabletin tënd, përfshirë shpeshtësinë me të cilën ke telefonuar, ke dërguar mail-a ose komunikuar në mënyra të tjera me individë të caktuar. Kjo leje u mundëson aplikacioneve të ruajnë të dhënat e tua të kontakteve, ndaj aplikacionet keqdashëse mund të ndajnë të dhëna të kontakteve pa dijeninë tënde."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Lejon aplikacionin të lexojë të dhënat rreth kontakteve të tua të ruajtura në pajisjen tënde Android TV, përfshi shpeshtësinë me të cilën ke telefonuar, ke dërguar email-e apo komunikuar në rrugë të tjera me kontakte të veçanta. Ky autorizim i lejon aplikacionet të ruajnë të dhënat e tua të kontakteve, dhe aplikacionet keqdashëse mund të ndajnë të dhëna të kontakteve pa dijeninë tënde."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Lejon aplikacionin të lexojë të dhëna rreth kontakteve të ruajtura në telefonin tënd, përfshirë shpeshtësinë me të cilën ke telefonuar, ke dërguar mail-a ose komunikuar në mënyra të tjera me individë të caktuar. Kjo leje u mundëson aplikacioneve të ruajnë të dhënat e tua të kontakteve, ndaj aplikacionet keqdashëse mund të ndajnë të dhëna të kontakteve pa dijeninë tënde."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Lejon aplikacionin të lexojë të dhëna për kontaktet e ruajtura në tabletin tënd. Aplikacionet do të kenë po ashtu qasje te llogaritë në tabletin tënd, të cilat i kanë krijuar kontaktet. Kjo mund të përfshijë llogaritë e krijuara nga aplikacionet që ke instaluar. Kjo leje lejon që aplikacionet të ruajnë të dhënat e tua të kontakteve dhe aplikacionet keqdashëse mund të ndajnë të dhëna të kontakteve pa dijeninë tënde."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Lejon aplikacionin të lexojë të dhëna për kontaktet e ruajtura në pajisjen tënde Android TV. Aplikacionet do të kenë po ashtu qasje te llogaritë në pajisjen tënde të Android TV, të cilat i kanë krijuar kontaktet. Kjo mund të përfshijë llogaritë e krijuara nga aplikacionet që ke instaluar. Kjo leje lejon që aplikacionet të ruajnë të dhënat e tua të kontakteve dhe aplikacionet keqdashëse mund të ndajnë të dhëna të kontakteve pa dijeninë tënde."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Lejon aplikacionin të lexojë të dhëna për kontaktet e ruajtura në telefonin tënd. Aplikacionet do të kenë po ashtu qasje te llogaritë në telefonin tënd, të cilat i kanë krijuar kontaktet. Kjo mund të përfshijë llogaritë e krijuara nga aplikacionet që ke instaluar. Kjo leje lejon që aplikacionet të ruajnë të dhënat e tua të kontakteve dhe aplikacionet keqdashëse mund të ndajnë të dhëna të kontakteve pa dijeninë tënde."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modifiko kontaktet"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Lejon aplikacionin të modifikojë të dhënat rreth kontakteve të tua të ruajtura në tabletin tënd, përfshi shpeshtësinë me të cilën ke telefonuar, ke dërguar mail-a apo ke komunikuar në rrugë të tjera me kontakte të veçanta. Kjo leje u mundëson aplikacioneve të fshijnë të dhënat e kontaktit."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Lejon aplikacionin të modifikojë të dhënat rreth kontakteve të tua të ruajtura në pajisjen tënde Android TV, përfshi shpeshtësinë me të cilën ke telefonuar, ke dërguar email-e apo komunikuar në rrugë të tjera me kontakte specifike. Ky autorizim i lejon aplikacionet të fshijnë të dhënat e kontaktit."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Lejon aplikacionin të modifikojë të dhënat e kontakteve të tua të ruajtura në telefon, përfshirë shpeshtësinë me të cilën ke telefonuar, ke dërguar mail-a apo ke komunikuar në rrugë të tjera me kontakte të veçanta. Kjo leje iu mundëson aplikacioneve të fshijnë të dhënat e kontakteve."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Lejon aplikacionin të modifikojë të dhënat rreth kontakteve të tua të ruajtura në tabletin tënd. Kjo leje lejon që aplikacionet të fshijnë të dhënat e kontakteve."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Lejon aplikacionin të modifikojë të dhënat rreth kontakteve të tua të ruajtura në pajisjen tënde Android TV. Kjo leje lejon që aplikacionet të fshijnë të dhënat e kontakteve."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Lejon aplikacionin të modifikojë të dhënat rreth kontakteve të tua të ruajtura në telefonin tënd. Kjo leje lejon që aplikacionet të fshijnë të dhënat e kontakteve."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"lexo ditarin e telefonatave"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Ky aplikacion mund të lexojë të gjithë historikun e telefonatave."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"shkruaj ditarin e telefonatave"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"qasje në komandat shtesë të ofruesit të vendndodhjes"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Lejon aplikacionin të ketë qasje në komandat shtesë të ofruesit për vendndodhjen. Kjo mund ta lejojë aplikacionin të ndërhyjë në operacionin e GPS-së apo të burimeve të tjera për vendndodhjen."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"qasu në vendndodhjen e saktë vetëm në plan të parë"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Ky aplikacion mund të marrë vendndodhjen tënde të saktë në çdo kohë kur është në plan të parë. Këto shërbime të vendndodhjes duhet të jenë të aktivizuara dhe në dispozicion në telefonin tënd që aplikacioni të mund t\'i përdorë. Kjo gjë mund të rritë konsumin e baterisë."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"qasu te vendndodhja e përafërt (bazuar te rrjeti) vetëm në plan të parë"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ky aplikacion mund të marrë vendndodhjen tënde bazuar në burimet e rrjetit si antenat e operatorëve celulare dhe rrjetet Wi-Fi, por vetëm kur aplikacioni është në plan të parë. Këto shërbime të vendndodhjes duhet të jenë të aktivizuara dhe të ofrohen në tabletin tënd që aplikacioni të mund t\'i përdorë."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ky aplikacion mund të marrë vendndodhjen tënde bazuar në burimet e rrjetit si antenat e operatorëve celulare dhe rrjetet Wi-Fi, por vetëm kur aplikacioni është në plan të parë. Këto shërbime të vendndodhjes duhet të jenë të aktivizuara dhe të ofrohen në pajisjen tënde Android TV që aplikacioni të mund t\'i përdorë."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ky aplikacion mund të marrë vendndodhjen tënde bazuar në burimet e rrjetit si antenat e operatorëve celulare dhe rrjetet Wi-Fi, por vetëm kur aplikacioni është në plan të parë. Këto shërbime të vendndodhjes duhet të jenë të aktivizuara dhe të ofrohen në telefonin tënd që aplikacioni të mund t\'i përdorë."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ky aplikacion mund të marrë vendndodhjen tënde të saktë vetëm kur është në plan të parë. Shërbimet e vendndodhjes duhet të jenë të aktivizuara dhe në dispozicion në pajisjen tënde që aplikacioni të mund t\'i përdorë. Kjo gjë mund të rrisë konsumin e baterisë."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"qasu në vendndodhjen e përafërt vetëm në plan të parë"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ky aplikacion mund të marrë vendndodhjen tënde të përafërt vetëm kur është në plan të parë. Shërbimet e vendndodhjes duhet të jenë të aktivizuara dhe të ofrohen në pajisjen tënde që aplikacioni të mund t\'i përdorë."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"qasje te vendndodhja në sfond"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Nëse kjo jepet përveç qasjes te vendndodhja e përafërt ose të saktë, aplikacioni mund të qaset te vendndodhja ndërkohë që ekzekutohet në sfond."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ky aplikacion mund të ketë qasje te vendndodhja ndërkohë që funksionon në sfond, përveç qasjes në plan të parë te vendndodhja."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ndrysho cilësimet e audios"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lejon aplikacionin të modifikojë cilësimet globale të audios siç është volumi dhe se cili altoparlant përdoret për daljen."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"regjistro audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Lejon aplikacionin të shikojë konfigurimin e \"bluetooth-it\" në tablet, të kryejë dhe të pranojë lidhje me pajisjet e çiftuara."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Lejon aplikacionin të shikojë konfigurimin e Bluetooth-it në pajisjen tënde Android TV dhe të kryejë dhe të pranojë lidhje me pajisjet e çiftuara."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Lejon aplikacionin të shohë konfigurimin e \"bluetooth-it\" në telefon dhe të kryejë e pranojë lidhje me pajisjet e çiftuara."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontrollo \"Komunikimin e fushës në afërsi\" NFC"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Lejon aplikacionin të komunikojë me etiketimet e \"Komunikimit të fushës së afërt (NFC)\", kartat dhe lexuesit."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"çaktivizo kyçjen e ekranit"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"U lidh me <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Trokit për të parë skedarët"</string>
<string name="pin_target" msgid="8036028973110156895">"Gozhdo"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Zhgozhdo"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Informacioni mbi aplikacionin"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Po nis demonstrimin..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Të përditësohen këta artikuj në "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> dhe <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Ruaj"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Jo, faleminderit"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Jo tani"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Asnjëherë"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Përditëso"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Vazhdo"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"fjalëkalimi"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Kalo tek ekrani i ndarë"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Ekrani i kyçjes"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Pamja e ekranit"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Aplikacioni <xliff:g id="APP_NAME">%1$s</xliff:g> në dritaren kërcyese."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Shiriti i nëntitullit të <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index e93c852..890d91e 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -190,8 +190,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Апликација за администраторе на профилу за Work недостаје или је оштећена. Због тога су профил за Work и повезани подаци избрисани. Обратите се администратору за помоћ."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Профил за Work више није доступан на овом уређају"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Превише покушаја уноса лозинке"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Администратор је уступио уређај за личну употребу"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Уређајем се управља"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Организација управља овим уређајем и може да надгледа мрежни саобраћај. Додирните за детаље."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Уређај ће бити обрисан"</string>
@@ -384,13 +383,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Дозвољава апликацији да шаље лепљива емитовања која остају по завршетку емитовања. Прекомерна употреба може да успори или дестабилизује Android TV уређај тако што ће га приморати да троши превише меморије."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Дозвољава апликацији да шаље пријемчива емитовања, која остају по завршетку емитовања. Прекомерна употреба може да успори или дестабилизује телефон тако што ће га приморати да троши превише меморије."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"читање контаката"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Дозвољава апликацији да чита податке о контактима ускладиштене на таблету, укључујући податке о томе колико често зовете одређене особе, шаљете им поруке е-поште или на други начин комуницирате са њима. Ова дозвола омогућава апликацијама да чувају податке о контактима, а злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Дозвољава апликацији да чита податке о контактима које чувате на Android TV уређају, укључујући учесталост позива, слања имејлова или других начина комуникације са одређеним појединцима. Ова дозвола омогућава апликацијама да чувају податке о контактима и злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Дозвољава апликацији да чита податке о контактима ускладиштене на телефону, укључујући податке о томе колико често зовете одређене особе, шаљете им поруке е-поште или на други начин комуницирате са њима. Ова дозвола омогућава апликацијама да чувају податке о контактима, а злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Дозвољава апликацији да чита податке о контактима које чувате на таблету. Апликације ће имати приступ и налозима на вашем таблету на којима су направљени контакти. Ту могу да спадају налози које су отвориле апликације које сте инсталирали. Ова дозвола омогућава апликацијама да чувају податке о контактима и злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Дозвољава апликацији да чита податке о контактима које чувате на Android TV уређају. Апликације ће имати приступ и налозима на вашем Android TV уређају на којима су направљени контакти. Ту могу да спадају налози које су отвориле апликације које сте инсталирали. Ова дозвола омогућава апликацијама да чувају податке о контактима и злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Дозвољава апликацији да чита податке о контактима које чувате на телефону. Апликације ће имати приступ и налозима на вашем телефону на којима су направљени контакти. Ту могу да спадају налози које су отвориле апликације које сте инсталирали. Ова дозвола омогућава апликацијама да чувају податке о контактима и злонамерне апликације могу да деле податке о контактима без вашег знања."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"измена контаката"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Дозвољава апликацији да мења податке о контактима ускладиштене на таблету, укључујући податке о томе колико често зовете одређене контакте, шаљете им поруке е-поште или на други начин комуницирате са њима. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Дозвољава апликацији да мења податке о контактима које чувате на Android TV уређају, укључујући учесталост позива, слања имејлова или других начина комуникације са одређеним контактима. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Дозвољава апликацији да мења податке о контактима ускладиштене на телефону, укључујући податке о томе колико често зовете одређене контакте, шаљете им поруке е-поште или на други начин комуницирате са њима. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Дозвољава апликацији да мења податке о контактима које чувате на таблету. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Дозвољава апликацији да мења податке о контактима које чувате на Android TV уређају. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Дозвољава апликацији да мења податке о контактима које чувате на телефону. Ова дозвола омогућава апликацијама да бришу податке о контактима."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"читање евиденције позива"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Ова апликација може да чита историју позива."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"писање евиденције позива"</string>
@@ -410,13 +409,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"приступ додатним командама добављача локације"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Омогућава апликацији да приступа додатним командама даваоца услуга локације. То може да омогући апликацији да утиче на рад GPS-а или других извора локације."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"приступ прецизној локацији само у првом плану"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Ова апликација може да одреди вашу тачну локацију само када ради у првом плану. Ове услуге локације морају да буду укључене и доступне на телефону да би апликација могла да их користи. То може да повећа потрошњу батерије."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"приступ приближној локацији (утврђеној преко мреже) само у првом плану"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Ова апликација може да приступи вашој локацији помоћу извора мреже, као што су мобилни предајници и Wi-Fi мреже, али само када апликација ради у првом плану. Ове услуге локације морају да буду укључене и доступне на таблету да би апликација могла да их користи"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Ова апликација може да приступи вашој локацији помоћу извора мреже, као што су мобилни предајници и Wi-Fi мреже, али само када апликација ради у првом плану. Ове услуге локације морају да буду укључене и доступне на Android TV уређају да би апликација могла да их користи."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Ова апликација може да приступи вашој локацији помоћу извора мреже, као што су мобилни предајници и Wi-Fi мреже, али само када апликација ради у првом плану. Ове услуге локације морају да буду укључене и доступне на телефону да би апликација могла да их користи."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ова апликација може да одреди вашу тачну локацију само када ради у првом плану. Услуге локације морају да буду укључене и доступне на уређају да би апликација могла да их користи. То може да повећа потрошњу батерије."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"приступ приближној локацији само у првом плану"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ова апликација може да одреди вашу приближну локацију само када ради у првом плану. Услуге локације морају да буду укључене и доступне на уређају да би апликација могла да их користи."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"приступ локацији у позадини"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Ако се поред приближног или прецизног приступа локација одобри и овај, апликација може да приступа локацији док је покренута у позадини."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ова апликација може да приступа локацији док ради у позадини, као и када ради у првом плану."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"промена аудио подешавања"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дозвољава апликацији да мења глобална аудио подешавања као што су јачина звука и избор звучника који се користи као излаз."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"снимање аудио записа"</string>
@@ -497,6 +494,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Дозвољава апликацији да прегледа конфигурацију Bluetooth-а на таблету, као и да успоставља и прихвата везе са упареним уређајима."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Дозвољава апликацији да прегледа конфигурацију Bluetooth-а на Android TV уређају и да успоставља и прихвата везе са упареним уређајима."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Дозвољава апликацији да прегледа конфигурацију Bluetooth-а на телефону, као и да успоставља и прихвата везе са упареним уређајима."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"контрола комуникације у ужем пољу (Near Field Communication)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Дозвољава апликацији да комуницира са ознакама, картицама и читачима комуникације кратког домета (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"онемогућавање закључавања екрана"</string>
@@ -1894,7 +1895,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Повезано је са производом <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Додирните за преглед датотека"</string>
<string name="pin_target" msgid="8036028973110156895">"Закачи"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Откачи"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Информације о апликацији"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Покрећемо демонстрацију..."</string>
@@ -1938,6 +1943,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Желите ли да ажурирате ове ставке у услузи "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> и <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Сачувај"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Не, хвала"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Не сада"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Никада"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Ажурирај"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Настави"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"лозинка"</string>
@@ -2034,5 +2041,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Укључите/искључите подељени екран"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Закључани екран"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Снимак екрана"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> у искачућем прозору."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Трака са насловима апликације <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index cf0e0bb..eabcd8e 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Administratörsappen för jobbprofilen saknas eller är skadad. Det innebär att jobbprofilen och all relaterad data har raderats. Kontakta administratören om du vill ha hjälp."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Jobbprofilen är inte längre tillgänglig på enheten"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"För många försök med lösenord"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administratören tillåter inte längre privat bruk av enheten"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Enheten hanteras"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Organisationen hanterar den här enheten och kan övervaka nätverkstrafiken. Tryck om du vill veta mer."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Enheten kommer att rensas"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Tillåter att appen skickar sticky-sändningar som finns kvar när sändningen är slut. Vid intensiv användning kan Android TV-enheten bli långsam eller instabil eftersom för mycket minne används."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Tillåter att appen skickar sticky broadcast, som finns kvar när sändningen är slut. Vid intensiv användning kan mobilen bli långsam eller instabil eftersom minnet överbelastas."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"läsa dina kontakter"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Tillåter att appen läser kontaktuppgifter som sparats på surfplattan, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med specifika personer. Med den här behörigheten tillåts appen att spara kontaktuppgifter. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Tillåter att appen läser kontaktuppgifter som sparats på Android TV-enheten, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med enskilda personer. Med den här behörigheten tillåts appen att spara kontaktuppgifter. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Tillåter att appen läser kontaktuppgifter som sparats på mobilen, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med specifika personer. Med den här behörigheten tillåts appen att spara kontaktuppgifter. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tillåter att appen läser kontaktuppgifter som sparats på surfplattan. Appar har även åtkomst till de konton på surfplattan som har skapade kontakter. Detta kan inkludera konton som har skapats av appar som du har installerat. Med den här behörigheten tillåts appen att spara kontaktuppgifter. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Tillåter att appen läser kontaktuppgifter som sparats på Android TV-enheten. Appar har även åtkomst till de konton på Android TV-enheten som har skapade kontakter. Detta kan inkludera konton som har skapats av appar som du har installerat. Med den här behörigheten tillåts appen att spara kontaktuppgifter. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Tillåter att appen läser kontaktuppgifter som sparats på telefonen. Appar har även åtkomst till de konton på telefonen som har skapade kontakter. Detta kan inkludera konton som har skapats av appar som du har installerat. Med den här behörigheten tillåts appen att spara kontaktuppgifter. Skadliga appar kan dela uppgifterna med andra utan din vetskap."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"ändra kontakterna"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Tillåter att appen ändrar kontaktuppgifter som sparats på surfplattan, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med specifika personer. Med den här behörigheten tillåts appar att ta bort kontaktuppgifter."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Tillåter att appen ändrar kontaktuppgifter som sparats på Android TV-enheten, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med enskilda kontakter. Med den här behörigheten tillåts appar att ta bort kontaktuppgifter."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Tillåter att appen ändrar kontaktuppgifter som sparats på mobilen, inklusive information om hur ofta du har ringt, skickat e-post till eller på andra sätt kommunicerat med specifika personer. Med den här behörigheten tillåts appar att ta bort kontaktuppgifter."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tillåter att appen ändrar kontaktuppgifter som sparats på surfplattan. Med den här behörigheten får appar även radera kontaktuppgifter."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Tillåter att appen ändrar kontaktuppgifter som sparats på Android TV-enheten. Med den här behörigheten får appar även radera kontaktuppgifter."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Tillåter att appen ändrar kontaktuppgifter som sparats på telefonen. Med den här behörigheten får appar även radera kontaktuppgifter."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"läs samtalslogg"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Appen kan läsa din samtalshistorik."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"skriv samtalslogg"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"få åtkomst till extra kommandon för platsleverantör"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Tillåter att appen får åtkomst till extra kommandon för platsleverantör. Detta kan innebära att appen tillåts störa funktionen för GPS eller andra platskällor."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"endast åtkomst till exakt plats i förgrunden"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Den här appen kan endast få information om din exakta plats när den körs i förgrunden. Platstjänsterna måste ha aktiverats och finnas på mobilen om appen ska kunna använda dem. Detta kan leda till ökad batteriförbrukning."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"endast åtkomst till beräknad plats (nätverksbaserad) i förgrunden"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Appen kan endast få information om din plats från källor i nätverket, som mobilmaster och Wi-Fi-nätverk, när den körs i förgrunden. Platstjänsterna måste ha aktiverats och finnas på surfplattan om appen ska kunna använda dem."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Appen kan få information om din plats från olika nätverkskällor, som mobilmaster och Wi-Fi-nätverk, men endast när den körs i förgrunden. Platstjänsterna måste ha aktiverats och finnas på Android TV-enheten om appen ska kunna använda dem."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Appen kan endast få information om din plats från källor i nätverket, som mobilmaster och Wi-Fi-nätverk, när den körs i förgrunden. Platstjänsterna måste ha aktiverats och finnas på mobilen om appen ska kunna använda dem."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Den här appen kan endast få information om din exakta plats när den körs i förgrunden. Platstjänsterna måste ha aktiverats och vara tillgängliga på enheten om appen ska kunna använda dem. Detta kan leda till ökad batteriförbrukning."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"endast åtkomst till ungefärlig plats i förgrunden"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Den här appen kan endast få information om din ungefärliga plats när den körs i förgrunden. Platstjänsterna måste ha aktiverats och vara tillgängliga på enheten om appen ska kunna använda dem."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"få åtkomst till platsinformation i bakgrunden"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Om denna behörighet ges utöver ungefärlig eller exakt platsåtkomst får appen åtkomst till platsinformation när den körs i bakgrunden."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Appen får åtkomst till platsinformation när appen körs i bakgrunden, förutom att den har åtkomst när den körs i förgrunden."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ändra dina ljudinställningar"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tillåter att appen ändrar globala ljudinställningar som volym och vilken högtalarutgång som används."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"spela in ljud"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Tillåter att appen kommer åt pekdatorns Bluetooth-konfiguration och upprättar och godkänner anslutningar till parkopplade enheter."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Tillåter att appen läser Android TV-enhetens Bluetooth-konfiguration och upprättar och godkänner anslutningar till parkopplade enheter."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Tillåter att appen kommer åt mobilens Bluetooth-konfiguration och upprättar och godkänner anslutningar till parkopplade enheter."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontrollera närfältskommunikationen"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Tillåter att appen kommunicerar med etiketter, kort och läsare för närfältskommunikation (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"inaktivera skärmlåset"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Ansluten till <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Filerna visas om du trycker här"</string>
<string name="pin_target" msgid="8036028973110156895">"Fäst"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Lossa"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Info om appen"</string>
<string name="negative_duration" msgid="1938335096972945232">"-<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Demo startas …"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Vill du uppdatera dessa objekt i "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> och <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Spara"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Nej tack"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Inte nu"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Aldrig"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Uppdatera"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Fortsätt"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"lösenordet"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Aktivera och inaktivera delad skärm"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Låsskärm"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Skärmdump"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g>-app i popup-fönster."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Textningsfält för <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index f8cd19c..2abcaff 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Programu ya msimamizi wa wasifu wa kazini imepotea au ina hitilafu. Kwa sababu hiyo, wasifu wako wa kazini na data husika imefutwa. Wasiliana na msimamizi wako kwa usaidizi."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Wasifu wako wa kazini haupatikani tena kwenye kifaa hiki"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Umejaribu kuweka nenosiri mara nyingi mno"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Msimamizi aliacha kutumia kifaa kwa matumizi ya binafsi"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Kifaa kinadhibitiwa"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Shirika lako linadhibiti kifaa hiki na huenda likafuatilia shughuli kwenye mtandao. Gusa ili upate maelezo zaidi."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Data iliyomo kwenye kifaa chako itafutwa"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Huruhusu programu itume matangazo yanayonata, ambayo hubaki baada ya matangazo kuisha. Huenda matumizi zaidi yakapunguza kasi ya kifaa chako cha Android TV au kukifanya kisiwe thabiti kwa kukifanya kitumie hifadhi kubwa zaidi."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Inaruhusu programu kutuma matangazo nata, ambayo hubakia baada ya matangazo kuisha. Matumizi zaidi yanaweza kufanya simu kufanya kazi polepole au kuifanya isiwe thabiti kwa kuisababisha itumie kumbukumbu kubwa zaidi."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"kusoma anwani zako"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Inaruhusu programu kusoma data kuhusu anwani zako zilizohifadhiwa kwenye kompyuta kibao yako, ikiwa ni pamoja na mara ngapi umepiga simu, kutuma barua pepe au kuwasiliana kwa njia zingine na watu fulani. Idhini hii inaruhusu programu kuhifadhi data yako ya anwani, na programu hasidi zinaweza kushiriki data ya anwani bila ya kujua kwako."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Huruhusu programu isome data kuhusu anwani zako zilizohifadhiwa kwenye kifaa chako cha Android TV, ikiwa ni pamoja na mara ambazo umepiga simu, umetuma barua pepe au umewasiliana kwa njia zingine na watu mahususi. Idhini hii huruhusu programu kuhifadhi data yako ya anwani na huenda programu hasidi zikashiriki data ya anwani bila idhini yako."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Inaruhusu programu kusoma data kuhusu anwani zako zilizohifadhiwa kwenye simu yako, ikiwa ni pamoja na mara ngapi umepiga simu, kutuma barua pepe au kuwasiliana kwa njia zingine na watu fulani. Idhini hii inaruhusu programu kuhifadhi data yako ya anwani, na programu hasidi zinaweza kushiriki data ya anwani bila ya kujua kwako."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Huruhusu programu kusoma data kuhusu anwani zako zilizohifadhiwa kwenye kompyuta yako kibao. Programu pia zitafikia akaunti zilizo kwenye kompyuta yako kibao zilizounda anwani. Hii inaweza kujumuisha akaunti zilizoundwa na programu ambazo umesakinisha. Idhini hii huruhusu programu kuhifadhi data yako ya anwani na programu hasidi zinaweza kushiriki data ya anwani bila wewe kujua."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Huruhusu programu kusoma data kuhusu anwani zako zilizohifadhiwa kwenye kifaa chako cha Android TV. Programu pia zitafikia akaunti zilizo kwenye kifaa chako cha Android TV zilizounda anwani. Hii inaweza kujumuisha akaunti zilizoundwa na programu ambazo umesakinisha. Idhini hii huruhusu programu kuhifadhi data yako ya anwani na programu hasidi zinaweza kushiriki data ya anwani bila wewe kujua."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Huruhusu programu kusoma data kuhusu anwani zako zilizohifadhiwa kwenye simu yako. Programu pia zitafikia akaunti zilizo kwenye simu yako zilizounda anwani. Hii inaweza kujumuisha akaunti zilizoundwa na programu ambazo umesakinisha. Idhini hii huruhusu programu kuhifadhi data yako ya anwani na programu hasidi zinaweza kushiriki data ya anwani bila wewe kujua."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"kurekebisha anwani zako"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Huruhusu programu kurekebisha data kuhusu anwani ulizohifadhi kwenye kompyuta kibao yako, ikiwa ni pamoja na mara ambazo umepiga simu, kutuma barua pepe au kuwasiliana kwa njia nyingine na anwani maalum. Idhini hii inaruhusu programu kufuta data ya anwani."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Huruhusu programu ibadilishe data kuhusu anwani zako zilizohifadhiwa kwenye kifaa chako cha Android TV, ikiwa ni pamoja na mara ambazo umepiga simu, umetuma barua pepe au kuwasiliana kwa njia nyingine na anwani mahususi. Idhini hii huruhusu programu ifute data ya anwani."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Huruhusu programu kurekebisha data kuhusu anwani ulizohifadhi kwenye simu yako, ikiwa ni pamoja na mara ambazo umepiga simu, kutuma barua pepe au kuwasiliana kwa njia nyingine na anwani maalum. Idhini hii inaruhusu programu kufuta data ya anwani."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Huruhusu programu kubadilisha data kuhusu anwani zako zilizohifadhiwa kwenye kompyuta yako kibao. Idhini hii huruhusu programu kufuta data ya anwani."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Huruhusu programu kubadilisha data kuhusu anwani zako zilizohifadhiwa kwenye kifaa chako cha Android TV. Idhini hii huruhusu programu kufuta data ya anwani."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Huruhusu programu kurekebisha data kuhusu anwani zako zilizohifadhiwa kwenye simu yako. Idhini hii huruhusu programu kufuta data ya anwani."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"kusoma rekodi ya simu"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Programu hii inaweza kusoma rekodi yako ya simu zilizopigwa."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"kuandika rekodi ya simu"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"fikia amri za ziada za mtoa huduma ya mahali"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Ruhusu programu kufikia amri za ziada za mtoa huduma za mahali. Hii huenda ikaruhusu programu ikatize matumizi ya GPS au vyanzo vingine vya eneo."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"kufikia mahali mahususi ikiwa tu programu imefunguliwa kwenye skrini"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Programu hii inaweza kupata mahali halisi ikiwa tu umeifungua kwenye skrini. Ni lazima uwashe huduma hizi za mahali na zipatikane kwenye simu yako ili programu iweze kuzitumia. Hatua hii inaweza kuongeza utumiaji wa betri."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"fikia eneo linalokadiriwa (kulingana na mtandao) wakati tu programu inatumika kwenye skrini"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Programu hii inaweza kupata eneo lako kulingana na vyanzo vya mtandao kama vile minara ya simu na mitandao ya Wi-Fi, lakini hili hufanyika tu wakati programu inatumika kwenye skrini. Ni lazima uwashe huduma hizi za mahali na zipatikane kwenye simu kibao yako ili programu iweze kuzitumia."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Programu hii inaweza kupata maelezo ya mahali ulipo kulingana na vyanzo vya mtandao kama vile minara ya simu na mitandao ya Wi-Fi, lakini hili hufanyika tu wakati programu inatumika. Ni lazima huduma hizi za mahali ziwashwe na kupatikana kwenye kifaa chako cha Android TV ili programu iweze kuzitumia."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Programu hii inaweza kupata eneo lako kulingana na vyanzo vya mtandao kama vile minara ya simu na mitandao ya Wi-Fi, lakini hili hufanyika tu wakati programu inatumika kwenye skrini. Ni lazima uwashe huduma hizi za mahali na zipatikane kwenye simu yako ili programu iweze kuzitumia."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Programu hii inaweza kupata mahali halisi ikiwa tu umeifungua kwenye skrini. Ni lazima uwashe huduma za mahali na zipatikane kwenye kifaa chako ili programu iweze kuzitumia. Hatua hii inaweza kuongeza matumizi ya betri."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"kufikia mahali palipokadiriwa ikiwa tu programu imefunguliwa kwenye skrini"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Programu hii inaweza kukadiria mahali ulipo ikiwa tu umeifungua kwenye skrini. Ni lazima huduma za mahali ziwashwe na zipatikane kwenye kifaa chako ili programu iweze kuzitumia."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"ifikie mahali inapotumika chinichini"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Kama ruhusa hii itatolewa, mbali na idhini ya kufikia mahali panapokadiriwa au mahali mahususi, programu inaweza kufikia mahali wakati inatumika chinichini."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Programu hii inaweza kufikia maelezo ya mahali inapotumika chinichini, pamoja na ufikiaji wa mahali wakati umeifungua kwenye skrini."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"badilisha mipangilio yako ya sauti"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Inaruhusu programu kurekebisha mipangilio ya sauti kila mahali kama vile sauti na ni kipaza sauti kipi ambacho kinatumika kwa kutoa."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"kurekodi sauti"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Huruhusu programu kuona usanidi wa Bluetooth kwenye kompyuta kibao, na kutuma na kukubali miunganisho kwa vifaa vilivyooanishwa."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Huruhusu programu iangalie mipangilio iliyowekwa ya Bluetooth kwenye kifaa chako cha Android TV na kufanya na kukubali miunganisho na vifaa vilivyooanishwa."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Huruhusu programu kuona usanidi wa Bluetooth kwenye simu, na kutuma na kukubali miunganisho kwa vifaa vilivyooanishwa."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kudhibiti Mawasiliano ya Vifaa Vilivyokaribu (NFC)"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Inaruhusu programu kuwasiliana na lebo, kadi na wasomaji wa Near Field Communication (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"zima kufuli la skrini yako"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Imeunganishwa na <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Gusa ili uangalie faili"</string>
<string name="pin_target" msgid="8036028973110156895">"Bandika"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Bandua"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Maelezo ya programu"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Inaanzisha onyesho..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Ungependa kusasisha vipengee hivi katika "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> na <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Hifadhi"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Hapana, asante"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Si sasa"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Kamwe"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Sasisha"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Endelea"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"nenosiri"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Geuza Skrini Iliyogawanywa"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Skrini Iliyofungwa"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Picha ya skrini"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> katika dirisha Ibukizi."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Upau wa manukuu wa <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-sw600dp/bools.xml b/core/res/res/values-sw600dp/bools.xml
index 00f45c1..b339717 100644
--- a/core/res/res/values-sw600dp/bools.xml
+++ b/core/res/res/values-sw600dp/bools.xml
@@ -15,7 +15,6 @@
-->
<resources>
- <bool name="target_honeycomb_needs_options_menu">false</bool>
<bool name="show_ongoing_ime_switcher">true</bool>
<bool name="kg_share_status_area">false</bool>
<bool name="kg_sim_puk_account_full_screen">false</bool>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index ac284d3..56a44dd 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"பணிக் கணக்கு நிர்வாகி ஆப்ஸ் இல்லை அல்லது அது சிதைந்துள்ளது. இதன் விளைவாக, உங்கள் பணிக் கணக்குமும் அதனுடன் தொடர்புடைய தரவும் நீக்கப்பட்டன. உதவிக்கு, நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"இந்தச் சாதனத்தில் இனி பணிக் கணக்கு கிடைக்காது"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"கடவுச்சொல்லை அதிக முறை தவறாக முயற்சித்துவிட்டீர்கள்"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"நிர்வாகியால் தனிப்பட்ட உபயோகத்திற்காக ஒதுக்கப்பட்ட சாதனம்"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"சாதனம் நிர்வகிக்கப்படுகிறது"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"உங்கள் நிறுவனம் இந்தச் சாதனத்தை நிர்வகிக்கும், அத்துடன் அது நெட்வொர்க் ட்ராஃபிக்கைக் கண்காணிக்கலாம். விவரங்களுக்கு, தட்டவும்."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"சாதனத் தரவு அழிக்கப்படும்"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"வலைபரப்பு முடிந்த பின்னரும் தங்கிவிடும் ஸ்டிக்கி வலைபரப்புகளை அனுப்ப ஆப்ஸை அனுமதிக்கும். அளவுக்கதிகமான உபயோகம் Android TVயின் வேகத்தைக் குறைக்கவோ நிலையற்றதாகவோ ஆக்கக்கூடும். இதனால் அதிகமான நினைவகம் பயன்படுத்தப்படும்."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"அலைபரப்பு முடிந்த பின்னும் இருக்கும், தொடர்ந்து அணுகத்தக்க அலைபரப்பை அனுப்பப் ஆப்ஸை அனுமதிக்கிறது. அதிகமாகப் பயன்படுத்தினால், மொபைலானது நினைவகத்தை மிக அதிகமாகப் பயன்படுத்துவதால் வேகம் குறைந்ததாகவும், நிலையற்றதாகவும் ஆகலாம்."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"உங்கள் தொடர்புகளைப் படித்தல்"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உட்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் ஆப்ஸ் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"உங்கள் Android TVயில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவைத் தெரிந்துகொள்ள ஆப்ஸை அனுமதிக்கும். குறிப்பிட்ட தனிநபரை எத்தனை முறை அழைத்தீர்கள், பிறவழிகளில் தொடர்புகொண்டீர்கள் அல்லது அவருக்கு எத்தனை முறை மின்னஞ்சல் அனுப்பினீர்கள் என்பதும் இதில் அடங்கும். இது உங்கள் தொடர்புத் தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கும், அத்துடன் தீங்குவிளைவிக்கும் ஆப்ஸ் உங்களுக்குத் தெரியாமல் தொடர்புத் தரவைப் பகிரக்கூடும்."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்ட எண்ணிக்கை உட்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் ஆப்ஸ் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"உங்கள் டேப்லெட்டில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை அணுக ஆப்ஸை அனுமதிக்கும். உங்கள் டேப்லெட்டில் உள்ள தொடர்புகளை உருவாக்கிய கணக்குகளுக்கான அணுகலும் ஆப்ஸிற்கு இருக்கும். நீங்கள் நிறுவிய ஆப்ஸ் மூலம் உருவாக்கப்பட்ட அனைத்துக் கணக்குகளும் இதில் உள்ளடங்கக்கூடும். இந்த அனுமதி உங்கள் தொடர்புத் தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் ஆப்ஸ் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரக்கூடும்."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"உங்கள் Android TVயில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை அணுக ஆப்ஸை அனுமதிக்கும். உங்கள் Android TVயில் உள்ள தொடர்புகளை உருவாக்கிய கணக்குகளுக்கான அணுகலும் ஆப்ஸிற்கு இருக்கும். நீங்கள் நிறுவிய ஆப்ஸ் மூலம் உருவாக்கப்பட்ட அனைத்துக் கணக்குகளும் இதில் உள்ளடங்கக்கூடும். இந்த அனுமதி உங்கள் தொடர்புத் தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் ஆப்ஸ் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரக்கூடும்."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"உங்கள் மொபைலில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை அணுக ஆப்ஸை அனுமதிக்கும். உங்கள் மொபைலில் உள்ள தொடர்புகளை உருவாக்கிய கணக்குகளுக்கான அணுகலும் ஆப்ஸிற்கு இருக்கும். நீங்கள் நிறுவிய ஆப்ஸ் மூலம் உருவாக்கப்பட்ட அனைத்துக் கணக்குகளும் இதில் உள்ளடங்கக்கூடும். இந்த அனுமதி உங்கள் தொடர்புத் தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் ஆப்ஸ் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரக்கூடும்."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"உங்கள் தொடர்புகளை மாற்றுதல்"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்க ஆப்ஸை அனுமதிக்கிறது."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"உங்கள் Android TVயில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை மாற்ற ஆப்ஸை அனுமதிக்கும். குறிப்பிட்ட தொடர்பை எத்தனை முறை அழைத்தீர்கள், பிறவழிகளில் தொடர்புகொண்டீர்கள் அல்லது அவருக்கு எத்தனை முறை மின்னஞ்சல் அனுப்பினீர்கள் என்பதும் இதில் அடங்கும். தொடர்புத் தரவை நீக்க ஆப்ஸை இது அனுமதிக்கும்."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்க ஆப்ஸை அனுமதிக்கிறது."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"உங்கள் டேப்லெட்டில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை மாற்ற ஆப்ஸை அனுமதிக்கும். இது தொடர்புத் தரவை நீக்கவும் ஆப்ஸை அனுமதிக்கும்."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"உங்கள் Android TVயில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை மாற்ற ஆப்ஸை அனுமதிக்கும். இது தொடர்புத் தரவை நீக்கவும் ஆப்ஸை அனுமதிக்கும்."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"உங்கள் மொபைலில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை மாற்ற ஆப்ஸை அனுமதிக்கும். இது தொடர்புத் தரவை நீக்கவும் ஆப்ஸை அனுமதிக்கும்."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"அழைப்புப் பதிவைப் படித்தல்"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"இந்த ஆப்ஸ் உங்கள் அழைப்பு வரலாற்றைப் படிக்கலாம்."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"அழைப்புப் பதிவை எழுதுதல்"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"கூடுதல் இட வழங்குநரின் கட்டளைகளின் அணுகல்"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"கூடுதல் இட வழங்குநர் கட்டளைகளை அணுகப் ஆப்ஸை அனுமதிக்கிறது. இது, GPS அல்லது பிற இருப்பிட மூலங்களின் செயல்பாட்டை இடைமறிக்க ஆப்ஸை அனுமதிக்கலாம்."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"முன்புலத்தில் இயங்கும்போது மட்டும் துல்லியமான இருப்பிடத்தைக் கண்டறிதல்"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"இந்த ஆப்ஸ் முன்புலத்தில் இயங்கும்போது மட்டுமே நீங்கள் இருக்கும் இடத்தைத் துல்லியமாகக் கண்டறியும். உங்கள் மொபைலில், இருப்பிடச் சேவைகளை ஆப்ஸ் பயன்படுத்துவதற்கு வசதியாக, அவை ஆன் செய்யப்பட்டிருக்க வேண்டும். இதனால் பேட்டரி அதிகம் பயன்படுத்தப்படலாம்."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"முன்புலத்தில் இயங்கும்போது மட்டும் தோராயமான இருப்பிடத்தைக் கண்டறிதல் (நெட்வொர்க் அடிப்படையில்)"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ஆப்ஸ் முன்புலத்தில் இயங்கும்போது மட்டுமே மொபைல் டவர்கள், வைஃபை நெட்வொர்க்குகள் போன்ற நெட்வொர்க் மூலங்கள் மூலம் ஆப்ஸால் உங்கள் இருப்பிடத்தைப் பெற முடியும். உங்கள் டேப்லெட்டில் \'இருப்பிடச் சேவைகளை\' ஆப்ஸ் பயன்படுத்துவதற்கு வசதியாக அவை ஆன் செய்யப்பட்டிருக்க வேண்டும்."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"மொபைல் டவர்கள், வைஃபை நெட்வொர்க்குகள் போன்ற நெட்வொர்க் ஆதாரங்களின் உதவியுடன் ஆப்ஸால் உங்கள் இருப்பிடத்தைப் பெற முடியும். அதற்கு ஆப்ஸ் முன்புலத்தில் இயங்க வேண்டும். இதனை ஆப்ஸ் பயன்படுத்துவதற்கேற்ப உங்கள் Android TVயில் இந்த இருப்பிடச் சேவைகள் இயக்கப்பட்டு கிடைக்கும்படி இருக்க வேண்டும்."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ஆப்ஸ் முன்புலத்தில் இயங்கும்போது மட்டுமே மொபைல் டவர்கள், வைஃபை நெட்வொர்க்குகள் போன்ற நெட்வொர்க் மூலங்கள் மூலம் ஆப்ஸால் உங்கள் இருப்பிடத்தைப் பெற முடியும். உங்கள் மொபைலில் \'இருப்பிடச் சேவைகளை\' ஆப்ஸ் பயன்படுத்துவதற்கு வசதியாக அவை ஆன் செய்யப்பட்டிருக்க வேண்டும்."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"இந்த ஆப்ஸ் முன்புலத்தில் இயங்கும்போது மட்டுமே நீங்கள் இருக்கும் இடத்தைத் துல்லியமாகக் கண்டறியும். உங்கள் சாதனத்தில், இருப்பிடச் சேவைகளை ஆப்ஸ் பயன்படுத்துவதற்கு வசதியாக, அவை ஆன் செய்யப்பட்டிருக்க வேண்டும். இதனால் பேட்டரி அதிகம் பயன்படுத்தப்படக்கூடும்."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"முன்புலத்தில் இயங்கும்போது மட்டும் தோராயமான இருப்பிடத்தைக் கண்டறிதல்"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"முன்புலத்தில் இருக்கும்போது மட்டுமே இந்த ஆப்ஸால் தோராயமான இடத்தைக் கண்டறிய முடியும். இருப்பிட சேவைகள் ஆன் செய்யப்பட்டு சாதனத்தில் இருக்க வேண்டும், அப்போதுதான் அவற்றை ஆப்ஸால் பயன்படுத்த முடியும்."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"பின்புலத்தில் இருப்பிடத்தை அணுகுதல்"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"தோராயமான அல்லது துல்லியமான \'இருப்பிட அணுகலுடன்\' சேர்ந்து இதற்கும் அனுமதி வழங்கப்பட்டால், ஆப்ஸ் பின்புலத்தில் இயங்கினாலும் இருப்பிடத்தை அணுக இயலும்."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"முன்புல இருப்பிட அணுகலுடன் பின்புலத்தில் இயங்கும்போதும் இந்த ஆப்ஸால் இருப்பிடத்தை அணுக முடியும்."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"எனது ஆடியோ அமைப்புகளை மாற்றுதல்"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ஒலியளவு மற்றும் வெளியீட்டிற்கு ஸ்பீக்கர்கள் பயன்படுத்தப்படுவது போன்ற ஒட்டுமொத்த ஆடியோ அமைப்புகளைக் கட்டுப்படுத்தப் ஆப்ஸை அனுமதிக்கிறது."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ஆடியோவைப் பதிவுசெய்தல்"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"டேப்லெட்டில் புளூடூத் இன் உள்ளமைவைப் பார்க்க மற்றும் இணைந்த சாதனங்களுடன் இணைப்புகளை ஏற்படுத்த மற்றும் ஏற்க ஆப்ஸை அனுமதிக்கிறது."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Android TVயில் புளூடூத்தின் உள்ளமைவைப் பார்க்கவும் இணைக்கப்பட்ட சாதனங்களுடன் இணைப்புகளை உருவாக்கவும் ஏற்கவும் ஆப்ஸை அனுமதிக்கும்."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"மொபைலில் புளூடூத் இன் உள்ளமைவைப் பார்க்க மற்றும் இணைந்த சாதனங்களுடன் இணைப்புகளை ஏற்படுத்த மற்றும் ஏற்க ஆப்ஸை அனுமதிக்கிறது."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"குறுகிய இடைவெளி தகவல்பரிமாற்றத்தைக் கட்டுப்படுத்துதல்"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"குறுகிய இடைவெளி தகவல்பரிமாற்றம் (NFC), குறிகள், கார்டுகள் மற்றும் ரீடர்கள் ஆகியவற்றுடன் தொடர்புகொள்ள, ஆப்ஸை அனுமதிக்கிறது."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"உங்கள் திரைப் பூட்டை முடக்குதல்"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> உடன் இணைக்கப்பட்டது"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"கோப்புகளைப் பார்க்க, தட்டவும்"</string>
<string name="pin_target" msgid="8036028973110156895">"பின் செய்"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"பின்னை அகற்று"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ஆப்ஸ் தகவல்"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"டெமோவைத் தொடங்குகிறது…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" இல் <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> மற்றும் <xliff:g id="TYPE_2">%3$s</xliff:g>ஐ மாற்றவா?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"சேமி"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"வேண்டாம்"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"இப்போது வேண்டாம்"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ஒருபோதும் வேண்டாம்"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"புதுப்பி"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"தொடர்க"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"கடவுச்சொல்"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"திரைப் பிரிப்பை நிலைமாற்று"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"பூட்டுத் திரை"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ஸ்கிரீன்ஷாட்"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸில் உள்ள பாப் அப் சாளரம்."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸின் தலைப்புப் பட்டி."</string>
</resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 6641c41..47dcb2b 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"కార్యాలయ ప్రొఫైల్ నిర్వాహక యాప్ లేదు లేదా పాడైంది. తత్ఫలితంగా, మీ కార్యాలయ ప్రొఫైల్ మరియు సంబంధిత డేటా తొలగించబడ్డాయి. సహాయం కోసం మీ నిర్వాహకులను సంప్రదించండి."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ఈ పరికరంలో మీ కార్యాలయ ప్రొఫైల్ ఇప్పుడు అందుబాటులో లేదు"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"చాలా ఎక్కువ పాస్వర్డ్ ప్రయత్నాలు చేసారు"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"వ్యక్తిగత వినియోగం కోసం నిర్వాహకులు పరికరాన్ని తీసి వేశారు"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"పరికరం నిర్వహించబడింది"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"మీ సంస్థ ఈ పరికరాన్ని నిర్వహిస్తుంది మరియు నెట్వర్క్ ట్రాఫిక్ని పర్యవేక్షించవచ్చు. వివరాల కోసం నొక్కండి."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"మీ పరికరంలోని డేటా తొలగించబడుతుంది"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ప్రసారం ముగిసిన తర్వాత భద్రపరచబడే ప్రసారాలను పంపడానికి యాప్ని అనుమతిస్తుంది. ఎక్కువగా వినియోగిస్తే అధిక పరిమాణంలో మెమరీని ఉపయోగించడం వలన టీవీ నెమ్మదిగా పని చేయవచ్చు లేదా అస్థిరంగా మారవచ్చు."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ప్రసారం ముగిసిన తర్వాత భద్రపరచబడే ప్రసారాలను పంపడానికి యాప్ను అనుమతిస్తుంది. అత్యధిక వినియోగం వలన ఫోన్ నెమ్మదిగా పని చేయవచ్చు లేదా అధిక పరిమాణంలో మెమరీని ఉపయోగించడం వలన అస్థిరంగా మారవచ్చు."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"మీ పరిచయాలను చదవడం"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"మీరు నిర్దిష్ట వ్యక్తులకు కాల్ చేసిన, ఇమెయిల్ చేసిన లేదా ఇతర మార్గాల్లో కమ్యూనికేట్ చేసిన తరచుదనంతో సహా మీ టాబ్లెట్లో నిల్వ చేయబడిన మీ పరిచయాల గురించి డేటాను చదవడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి మీ పరిచయ డేటాను సేవ్ చేయడానికి యాప్లను అనుమతిస్తుంది మరియు హానికరమైన యాప్లు మీకు తెలియకుండానే పరిచయ డేటాను షేర్ చేయవచ్చు."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"మీరు నిర్దిష్ట వ్యక్తులకు కాల్ చేసిన, ఇమెయిల్ చేసిన లేదా ఇతర మార్గాల్లో కమ్యూనికేట్ చేసిన ఫ్రీక్వెన్సీతో సహా మీ Android TV పరికరంలో నిల్వ చేసిన మీ పరిచయాలకు సంబంధించిన డేటాను చదవడానికి యాప్ని అనుమతిస్తుంది. ఈ అనుమతి మీ పరిచయ డేటాను సేవ్ చేయడానికి యాప్లను అనుమతిస్తుంది మరియు హానికరమైన యాప్లు మీకు తెలియకుండానే పరిచయ డేటాను షేర్ చేయవచ్చు."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"మీరు నిర్దిష్ట వ్యక్తులకు కాల్ చేసిన, ఇమెయిల్ చేసిన లేదా ఇతర మార్గాల్లో కమ్యూనికేట్ చేసిన తరచుదనంతో సహా మీ ఫోన్లో నిల్వ చేయబడిన మీ పరిచయాల గురించి డేటాను చదవడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి మీ పరిచయ డేటాను సేవ్ చేయడానికి యాప్లను అనుమతిస్తుంది మరియు హానికరమైన యాప్లు మీకు తెలియకుండానే పరిచయ డేటాను షేర్ చేయవచ్చు."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"టాబ్లెట్లో నిల్వ చేసిన మీ కాంటాక్ట్లకు సంబంధించిన డేటాను చదవడానికి యాప్ను అనుమతిస్తుంది. కాంటాక్ట్లను సృష్టించిన మీ టాబ్లెట్లోని ఖాతాలకు కూడా యాప్లకు యాక్సెస్ ఉంటుంది. ఇందులో మీరు ఇన్స్టాల్ చేసిన యాప్ల ద్వారా సృష్టించబడిన ఖాతాలు ఉండవచ్చు. ఈ అనుమతి, మీ కాంటాక్ట్ డేటాను సేవ్ చేయడానికి యాప్లను అనుమతిస్తుంది, హానికరమైన యాప్లు మీకు తెలియకుండానే కాంటాక్ట్ డేటాను షేర్ చేయవచ్చు."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"మీ Android TV పరికరంలో నిల్వ చేసిన కాంటాక్ట్లకు సంబంధించిన డేటాను చదవడానికి యాప్ను అనుమతిస్తుంది. కాంటాక్ట్లను సృష్టించిన మీ Android TV పరికరంలోని ఖాతాలకు కూడా యాప్లకు యాక్సెస్ ఉంటుంది. ఇందులో మీరు ఇన్స్టాల్ చేసిన యాప్ల ద్వారా సృష్టించబడిన ఖాతాలు ఉండవచ్చు. ఈ అనుమతి, మీ కాంటాక్ట్ డేటాను సేవ్ చేయడానికి యాప్లను అనుమతిస్తుంది, హానికరమైన యాప్లు మీకు తెలియకుండానే కాంటాక్ట్ డేటాను షేర్ చేయవచ్చు."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ఫోన్లో నిల్వ చేసిన మీ కాంటాక్ట్లకు సంబంధించిన డేటాను చదవడానికి యాప్ను అనుమతిస్తుంది. కాంటాక్ట్లను సృష్టించిన మీ ఫోన్లోని ఖాతాలను కూడా యాప్లు యాక్సెస్ చేయగలవు. ఇందులో మీరు ఇన్స్టాల్ చేసిన యాప్ల ద్వారా సృష్టించబడిన ఖాతాలు ఉండవచ్చు. ఈ అనుమతి, మీ కాంటాక్ట్ డేటాను సేవ్ చేయడానికి యాప్లను అనుమతిస్తుంది, హానికరమైన యాప్లు మీకు తెలియకుండానే కాంటాక్ట్ డేటాను షేర్ చేయవచ్చు."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"మీ పరిచయాలను సవరించడం"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"మీరు నిర్దిష్ట పరిచయాలకు కాల్ చేసిన, ఇమెయిల్ చేసిన లేదా ఇతర మార్గాల్లో కమ్యూనికేట్ చేసిన తరచుదనంతో సహా మీ టాబ్లెట్లో నిల్వ చేయబడిన మీ పరిచయాల గురించి డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి పరిచయ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"మీరు నిర్దిష్ట పరిచయాలకు కాల్ చేసిన, ఇమెయిల్ చేసిన లేదా ఇతర మార్గాల్లో కమ్యూనికేట్ చేసిన ఫ్రీక్వెన్సీతో సహా మీ Android TV పరికరంలో నిల్వ చేసిన మీ పరిచయాలకు సంబంధించిన డేటాను సవరించడానికి యాప్ని అనుమతిస్తుంది. ఈ అనుమతి పరిచయ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"మీరు నిర్దిష్ట పరిచయాలకు కాల్ చేసిన, ఇమెయిల్ చేసిన లేదా ఇతర మార్గాల్లో కమ్యూనికేట్ చేసిన తరచుదనంతో సహా మీ ఫోన్లో నిల్వ చేయబడిన మీ పరిచయాల గురించి డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి పరిచయ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"మీ టాబ్లెట్లో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"మీ Android TV పరికరంలో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"మీ ఫోన్లో నిల్వ చేసి ఉన్న కాంటాక్ట్లకు సంబంధించిన డేటాను సవరించడానికి యాప్ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్లను అనుమతిస్తుంది."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"కాల్ లాగ్ను చదవడం"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"ఈ యాప్ మీ కాల్ చరిత్రను చదవగలదు."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"కాల్ లాగ్ను వ్రాయడం"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"అదనపు స్థాన ప్రదాత ఆదేశాలను యాక్సెస్ చేయడం"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"అదనపు స్థాన ప్రదాత ఆదేశాలను యాక్సెస్ చేయడానికి యాప్ను అనుమతిస్తుంది. ఇది GPS లేదా ఇతర స్థాన మూలాల నిర్వహణలో యాప్ ప్రమేయం ఉండేలా అనుమతించవచ్చు."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"స్క్రీన్పై ఉన్నప్పుడు మాత్రమే ఖచ్చితమైన స్థానాన్ని యాక్సెస్ చేయండి"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"ఈ యాప్ స్క్రీన్పై ఉన్నప్పుడు మాత్రమే అది మీ ఖచ్చితమైన స్థానాన్ని తెలుసుకోగలదు. యాప్ ఉపయోగించడానికి మీ ఫోన్లో ఈ స్థాన సేవలను తప్పనిసరిగా ఆన్ చేయాలి మరియు అందుబాటులో ఉండాలి. ఇది బ్యాటరీ వినియోగాన్ని పెంచవచ్చు."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"స్క్రీన్పై ఉన్నప్పుడు మాత్రమే సమీప స్థానాన్ని (నెట్వర్క్-ఆధారిత) యాక్సెస్ చేయండి"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"ఈ యాప్ సెల్ టవర్లు మరియు Wi-Fi నెట్వర్క్ల వంటి నెట్వర్క్ మూలాధారాల ఆధారంగా మీ స్థానాన్ని తెలుసుకోగలదు, కానీ యాప్ తెరపై ఉన్నప్పుడు మాత్రమే. యాప్ ఉపయోగించడానికి మీ టాబ్లెట్లో ఈ స్థాన సేవలను తప్పనిసరిగా ఆన్ చేయాలి మరియు అందుబాటులో ఉండాలి."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"ఈ యాప్ సెల్ టవర్లు మరియు Wi-Fi నెట్వర్క్ల వంటి నెట్వర్క్ మూలాధారాల ఆధారంగా మీ స్థానాన్ని తెలుసుకోగలదు, కానీ యాప్ తెరపై ఉన్నప్పుడు మాత్రమే. యాప్ ఉపయోగించడానికి మీ Android TV పరికరంలో ఈ స్థాన సేవలను తప్పనిసరిగా ఆన్ చేయాలి మరియు అవి అందుబాటులో ఉండాలి."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"ఈ యాప్ సెల్ టవర్లు మరియు Wi-Fi నెట్వర్క్ల వంటి నెట్వర్క్ మూలాధారాల ఆధారంగా మీ స్థానాన్ని తెలుసుకోగలదు, కానీ యాప్ తెరపై ఉన్నప్పుడు మాత్రమే. యాప్ ఉపయోగించడానికి మీ ఫోన్లో ఈ స్థాన సేవలను తప్పనిసరిగా ఆన్ చేయాలి మరియు అందుబాటులో ఉండాలి."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"ఈ యాప్ స్క్రీన్పై ఉన్నప్పుడు మాత్రమే మీ ఖచ్చితమైన లొకేషన్ను తెలుసుకోగలదు. మీ పరికరంలో లొకేషన్ సర్వీస్లు అందుబాటులో ఉండి, అవి తప్పనిసరిగా ఆన్ చేసి ఉంటేనే, యాప్ వాటిని ఉపయోగించగలదు. దీని వలన బ్యాటరీ వినియోగం పెరగవచ్చు."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"స్క్రీన్పై ఉన్నప్పుడు మాత్రమే సుమారు లొకేషన్ను యాక్సెస్ చేయండి"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"ఈ యాప్ స్క్రీన్పై ఉన్నప్పుడు మాత్రమే మీ సుమారు లొకేషన్ను తెలుసుకోగలదు. మీ పరికరంలో లొకేషన్ సర్వీస్లు అందుబాటులో ఉండి, అవి తప్పనిసరిగా ఆన్ చేసి ఉంటేనే, యాప్ వాటిని ఉపయోగించగలదు."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"నేపథ్యంలో స్థానాన్ని యాక్సెస్ చేయి"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"సుమారుగా లేదా ఖచ్చితమైన స్థాన యాక్సెస్తో పాటు అదనందా ఇది మంజూరు చేయబడితే, యాప్ నేపథ్యంలో నడుస్తున్నప్పుడు స్థానాన్ని యాక్సెస్ చేయగలదు."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"స్క్రీన్పై ఉన్నప్పుడు లొకేషన్ను యాక్సెస్ చేయడంతో పాటు ఈ యాప్ బ్యాక్గ్రౌండ్లో నడుస్తున్నప్పుడు కూడా లొకేషన్ను యాక్సెస్ చేయగలదు."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"మీ ఆడియో సెట్టింగ్లను మార్చడం"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"వాల్యూమ్ మరియు అవుట్పుట్ కోసం ఉపయోగించాల్సిన స్పీకర్ వంటి సార్వజనీన ఆడియో సెట్టింగ్లను సవరించడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ఆడియోను రికార్డ్ చేయడం"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"టాబ్లెట్లో బ్లూటూత్ యొక్క కాన్ఫిగరేషన్ను వీక్షించడానికి మరియు జత చేయబడిన పరికరాలతో కనెక్షన్లను ఏర్పాటు చేయడానికి మరియు ఆమోదించడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"మీ Android TV పరికరం బ్లూటూత్ యొక్క కాన్ఫిగరేషన్ను చూడడానికి, జత చేయబడిన పరికరాలతో కనెక్షన్లను ఏర్పాటు చేయడానికి మరియు ఆమోదించడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ఫోన్లో బ్లూటూత్ యొక్క కాన్ఫిగరేషన్ను వీక్షించడానికి మరియు జత చేయబడిన పరికరాలతో కనెక్షన్లను ఏర్పాటు చేయడానికి మరియు ఆమోదించడానికి యాప్ను అనుమతిస్తుంది."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"సమీప క్షేత్ర కమ్యూనికేషన్ను నియంత్రించడం"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"సమీప ఫీల్డ్ కమ్యూనికేషన్ (NFC) ట్యాగ్లు, కార్డులు మరియు రీడర్లతో కమ్యూనికేట్ చేయడానికి యాప్ను అనుమతిస్తుంది."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"మీ స్క్రీన్ లాక్ను నిలిపివేయడం"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g>కి కనెక్ట్ చేయబడింది"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"ఫైల్లను వీక్షించడానికి నొక్కండి"</string>
<string name="pin_target" msgid="8036028973110156895">"పిన్ చేయి"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"అన్పిన్ చేయి"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"యాప్ సమాచారం"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"డెమోను ప్రారంభిస్తోంది..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"ఈ అంశాలను "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"లో అప్డేట్ చేయాలా: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> మరియు <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"సేవ్ చేయి"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"వద్దు, ధన్యవాదాలు"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ఇప్పుడు కాదు"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ఎప్పుడూ వద్దు"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"అప్డేట్ చేయి"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"కొనసాగించు"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"పాస్వర్డ్"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"స్క్రీన్ విభజనను టోగుల్ చేయి"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"స్క్రీన్ను లాక్ చేయి"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"స్క్రీన్షాట్"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"పాప్-అప్ విండోలో <xliff:g id="APP_NAME">%1$s</xliff:g> యాప్ ఉంది."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> క్యాప్షన్ బార్."</string>
</resources>
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 655d4dd..3ecb1dd 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -42,8 +42,4 @@
<!-- Allow SystemUI to show the shutdown dialog -->
<bool name="config_showSysuiShutdown">true</bool>
-
- <!-- The time in milliseconds of prolonged user inactivity after which device goes to sleep,
- even if wakelocks are held. On TVs, this defaults to 4 hours. -->
- <integer name="config_attentiveTimeout">14400000</integer>
</resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index bc4b79e..cfdf4b5 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"แอปผู้ดูแลระบบโปรไฟล์งานไม่มีอยู่หรือเสียหาย ระบบจึงทำการลบโปรไฟล์งานและข้อมูลที่เกี่ยวข้องของคุณออก โปรดติดต่อผู้ดูแลระบบเพื่อรับความช่วยเหลือ"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"โปรไฟล์งานของคุณไม่สามารถใช้ในอุปกรณ์นี้อีกต่อไป"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ลองป้อนรหัสผ่านหลายครั้งเกินไป"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"ผู้ดูแลระบบปล่อยอุปกรณ์ให้คุณใช้งานส่วนตัว"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"อุปกรณ์มีการจัดการ"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"องค์กรของคุณจัดการอุปกรณ์นี้และอาจตรวจสอบการจราจรของข้อมูลในเครือข่าย แตะเพื่อดูรายละเอียด"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"ระบบจะลบข้อมูลในอุปกรณ์ของคุณ"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"อนุญาตให้แอปส่งการแพร่ภาพแบบติดหนึบ ซึ่งจะยังคงอยู่หลังจากการแพร่ภาพจบไปแล้ว การใช้งานมากเกินไปอาจทำให้อุปกรณ์ Android TV ทำงานช้าลงหรือไม่เสถียรเนื่องจากมีการใช้หน่วยความจำมากเกิน"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"อนุญาตให้แอปพลิเคชันส่งการกระจายข้อมูลที่ติดหนึบ ซึ่งจะยังคงอยู่หลังจากการกระจายข้อมูลจบไปแล้ว การใช้งานมากเกินไปอาจทำให้โทรศัพท์ทำงานช้าลงหรือไม่เสถียรโดยการใช้หน่วยความจำมากเกินไป"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"อ่านผู้ติดต่อของคุณ"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"อนุญาตให้แอปพลิเคชันอ่านข้อมูลผู้ติดต่อที่จัดเก็บไว้ในแท็บเล็ต ซึ่งรวมถึงความถี่ในการโทร ส่งอีเมล หรือการติดต่อด้วยวิธีอื่นๆ กับบุคคลใดบุคคลหนึ่ง การอนุญาตนี้ทำให้แอปพลิเคชันสามารถบันทึกข้อมูลผู้ติดต่อของคุณ และแอปพลิเคชันที่เป็นอันตรายอาจแชร์ข้อมูลผู้ติดต่อโดยไม่แจ้งให้คุณทราบ"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"อนุญาตให้แอปอ่านข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในอุปกรณ์ Android TV รวมถึงความถี่ที่คุณโทรหา ส่งอีเมล หรือสื่อสารในรูปแบบอื่นกับรายชื่อติดต่อแต่ละคน สิทธิ์นี้ทำให้แอปบันทึกข้อมูลรายชื่อติดต่อได้และแอปที่เป็นอันตรายอาจแชร์ข้อมูลรายชื่อติดต่อโดยไม่แจ้งให้คุณทราบ"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"อนุญาตให้แอปพลิเคชันอ่านข้อมูลผู้ติดต่อที่จัดเก็บไว้ในโทรศัพท์ ซึ่งรวมถึงความถี่ในการโทร ส่งอีเมล หรือการติดต่อด้วยวิธีอื่นๆ กับบุคคลใดบุคคลหนึ่ง การอนุญาตนี้ทำให้แอปพลิเคชันสามารถบันทึกข้อมูลผู้ติดต่อของคุณ และแอปพลิเคชันที่เป็นอันตรายอาจแชร์ข้อมูลผู้ติดต่อโดยไม่แจ้งให้คุณทราบ"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"อนุญาตให้แอปอ่านข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในแท็บเล็ต แอปจะมีสิทธิ์เข้าถึงบัญชีในแท็บเล็ตที่มีการสร้างรายชื่อติดต่อด้วย โดยอาจรวมถึงบัญชีซึ่งแอปที่คุณได้ติดตั้งไว้สร้างขึ้น สิทธิ์นี้ทำให้แอปบันทึกข้อมูลรายชื่อติดต่อได้และแอปที่เป็นอันตรายอาจแชร์ข้อมูลรายชื่อติดต่อโดยไม่แจ้งให้คุณทราบ"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"อนุญาตให้แอปอ่านข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในอุปกรณ์ Android TV แอปจะมีสิทธิ์เข้าถึงบัญชีในอุปกรณ์ Android TV ที่มีการสร้างรายชื่อติดต่อด้วย โดยอาจรวมถึงบัญชีซึ่งแอปที่คุณได้ติดตั้งไว้สร้างขึ้น สิทธิ์นี้ทำให้แอปบันทึกข้อมูลรายชื่อติดต่อได้และแอปที่เป็นอันตรายอาจแชร์ข้อมูลรายชื่อติดต่อโดยไม่แจ้งให้คุณทราบ"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"อนุญาตให้แอปอ่านข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในโทรศัพท์ แอปจะมีสิทธิ์เข้าถึงบัญชีในโทรศัพท์ที่มีการสร้างรายชื่อติดต่อด้วย โดยอาจรวมถึงบัญชีซึ่งแอปที่คุณได้ติดตั้งไว้สร้างขึ้น สิทธิ์นี้ทำให้แอปบันทึกข้อมูลรายชื่อติดต่อได้และแอปที่เป็นอันตรายอาจแชร์ข้อมูลรายชื่อติดต่อโดยไม่แจ้งให้คุณทราบ"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"แก้ไขผู้ติดต่อของคุณ"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"อนุญาตให้แอปพลิเคชันเปลี่ยนแปลงข้อมูลผู้ติดต่อที่จัดเก็บไว้ในแท็บเล็ต ซึ่งรวมถึงความถี่ในการโทร ส่งอีเมล หรือการติดต่อด้วยวิธีอื่นๆ กับบุคคลใดบุคคลหนึ่ง การอนุญาตนี้ทำให้แอปพลิเคชันสามารถลบข้อมูลผู้ติดต่อได้"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"อนุญาตให้แอปแก้ไขข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในอุปกรณ์ Android TV รวมถึงความถี่ที่คุณโทรหา ส่งอีเมล หรือสื่อสารในรูปแบบอื่นกับรายชื่อติดต่อแต่ละคน สิทธิ์นี้ทำให้แอปลบข้อมูลรายชื่อติดต่อได้"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"อนุญาตให้แอปพลิเคชันเปลี่ยนแปลงข้อมูลผู้ติดต่อที่จัดเก็บไว้ในโทรศัพท์ ซึ่งรวมถึงความถี่ในการโทร ส่งอีเมล หรือการติดต่อด้วยวิธีอื่นๆ กับบุคคลใดบุคคลหนึ่ง การอนุญาตนี้ทำให้แอปพลิเคชันสามารถลบข้อมูลผู้ติดต่อได้"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"อนุญาตให้แอปแก้ไขข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในแท็บเล็ต สิทธิ์นี้ทำให้แอปลบข้อมูลรายชื่อติดต่อได้"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"อนุญาตให้แอปแก้ไขข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในอุปกรณ์ Android TV สิทธิ์นี้ทำให้แอปลบข้อมูลรายชื่อติดต่อได้"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"อนุญาตให้แอปแก้ไขข้อมูลเกี่ยวกับรายชื่อติดต่อที่จัดเก็บไว้ในโทรศัพท์ สิทธิ์นี้ทำให้แอปลบข้อมูลรายชื่อติดต่อได้"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"อ่านประวัติการโทร"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"แอปนี้สามารถอ่านประวัติการโทรของคุณได้"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"เขียนประวัติการโทร"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"เข้าถึงคำสั่งของโปรแกรมแจ้งตำแหน่งพิเศษ"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"อนุญาตให้แอปเข้าถึงคำสั่งของผู้ให้บริการตำแหน่งเพิ่มเติม ซึ่งอาจทำให้แอปสามารถแทรกแซงการทำงานของ GPS หรือต้นทางของตำแหน่งอื่นๆ ได้"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"เข้าถึงตำแหน่งที่แม่นยำเมื่ออยู่เบื้องหน้าเท่านั้น"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"แอปนี้รับตำแหน่งที่แม่นยำของคุณได้เมื่อทำงานอยู่เบื้องหน้าเท่านั้น แอปจะใช้บริการตำแหน่งเหล่านี้ได้ต่อเมื่อคุณเปิดบริการและบริการพร้อมใช้งานในโทรศัพท์ของคุณ ซึ่งอาจทำให้มีการใช้แบตเตอรี่มากขึ้น"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"เข้าถึงตำแหน่งโดยประมาณ (อิงตามเครือข่าย) เมื่อทำงานอยู่เบื้องหน้าเท่านั้น"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"แอปนี้รับตำแหน่งของคุณโดยอิงตามแหล่งที่มาของเครือข่าย เช่น เสาส่งสัญญาณมือถือและเครือข่าย Wi-Fi เมื่อทำงานอยู่เบื้องหน้าเท่านั้น แอปจะใช้บริการตำแหน่งเหล่านี้ได้ต่อเมื่อคุณเปิดบริการและบริการพร้อมใช้งานในแท็บเล็ตของคุณ"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"แอปนี้รับตำแหน่งของคุณได้โดยอิงตามแหล่งที่มาของเครือข่าย เช่น เสาส่งสัญญาณมือถือและเครือข่าย Wi-Fi เมื่อแอปทำงานอยู่เบื้องหน้าเท่านั้น แอปจะใช้บริการตำแหน่งเหล่านี้ได้ต่อเมื่อคุณเปิดใช้งานและบริการพร้อมใช้งานในอุปกรณ์ Android TV ของคุณ"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"แอปนี้รับตำแหน่งของคุณโดยอิงตามแหล่งที่มาของเครือข่าย เช่น เสาส่งสัญญาณมือถือและเครือข่าย Wi-Fi เมื่อทำงานอยู่เบื้องหน้าเท่านั้น แอปจะใช้บริการตำแหน่งเหล่านี้ได้ต่อเมื่อคุณเปิดบริการและบริการพร้อมใช้งานในโทรศัพท์ของคุณ"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"แอปนี้เข้าถึงตำแหน่งที่แม่นยำของคุณได้เมื่อทำงานอยู่เบื้องหน้าเท่านั้น แอปจะใช้บริการตำแหน่งได้ต่อเมื่อคุณเปิดบริการและบริการพร้อมใช้งานในอุปกรณ์ของคุณ ซึ่งอาจทำให้มีการใช้แบตเตอรี่มากขึ้น"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"เข้าถึงตำแหน่งโดยประมาณเมื่ออยู่เบื้องหน้าเท่านั้น"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"แอปนี้เข้าถึงตำแหน่งโดยประมาณของคุณได้เมื่อทำงานอยู่เบื้องหน้าเท่านั้น แอปจะใช้บริการตำแหน่งได้ต่อเมื่อคุณเปิดบริการและบริการพร้อมใช้งานในอุปกรณ์ของคุณ"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"เข้าถึงตำแหน่งในเบื้องหลัง"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"หากได้รับสิทธิ์นี้เพิ่มจากการเข้าถึงตำแหน่งโดยประมาณหรือตำแหน่งที่แม่นยำ แอปจะมีสิทธิ์เข้าถึงตำแหน่งระหว่างที่ทำงานในเบื้องหลังได้"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"แอปนี้เข้าถึงตำแหน่งในขณะที่ทำงานอยู่เบื้องหลังได้ด้วย นอกเหนือจากการเข้าถึงตำแหน่งเมื่อทำงานอยู่เบื้องหน้า"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"เปลี่ยนการตั้งค่าเสียงของคุณ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"อนุญาตให้แอปพลิเคชันปรับเปลี่ยนการตั้งค่าเสียงทั้งหมดได้ เช่น ระดับเสียงและลำโพงที่จะใช้งาน"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"บันทึกเสียง"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"อนุญาตให้แอปพลิเคชันดูการกำหนดค่าบลูทูธของแท็บเล็ต ตลอดจนเชื่อมต่อและยอมรับการเชื่อมต่อกับอุปกรณ์ที่จับคู่ไว้"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"อนุญาตให้แอปดูการกำหนดค่าบลูทูธในอุปกรณ์ Android TV ตลอดจนเชื่อมต่อและยอมรับการเชื่อมต่อกับอุปกรณ์ที่จับคู่ไว้"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"อนุญาตให้แอปพลิเคชันดูการกำหนดค่าบลูทูธของโทรศัพท์ ตลอดจนเชื่อมต่อและยอมรับการเชื่อมต่อกับอุปกรณ์ที่จับคู่ไว้"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"ควบคุม Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"อนุญาตให้แอปพลิเคชันสื่อสารกับแท็ก Near Field Communication (NFC) การ์ด และโปรแกรมอ่าน"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ปิดใช้งานการล็อกหน้าจอของคุณ"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"เชื่อมต่อ <xliff:g id="PRODUCT_NAME">%1$s</xliff:g> แล้ว"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"แตะเพื่อดูไฟล์"</string>
<string name="pin_target" msgid="8036028973110156895">"ปักหมุด"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"เลิกปักหมุด"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ข้อมูลแอป"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"กำลังเริ่มการสาธิต…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"อัปเดตข้อมูล<xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> และ<xliff:g id="TYPE_2">%3$s</xliff:g> ใน "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ไหม"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"บันทึก"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"ไม่เป็นไร"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ไว้ทีหลัง"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"ไม่เลย"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"อัปเดต"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"ทำต่อ"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"รหัสผ่าน"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"เปิด/ปิดการแบ่งหน้าจอ"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"หน้าจอล็อก"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"ภาพหน้าจอ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"แอป <xliff:g id="APP_NAME">%1$s</xliff:g> ในหน้าต่างป๊อปอัป"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"แถบคำบรรยาย <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
</resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index b24e95a..cf4e33d 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Nawawala o nasira ang admin app ng profile sa trabaho. Dahil dito, na-delete ang profile mo sa trabaho at nauugnay na data. Makipag-ugnayan sa iyong admin para sa tulong."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Hindi na available sa device na ito ang iyong profile sa trabaho"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Masyadong maraming pagsubok sa password"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Inalis ng admin ang device para sa personal na paggamit"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Pinamamahalaan ang device"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Pinamamahalaan ng iyong organisasyon ang device na ito, at maaari nitong subaybayan ang trapiko sa network. I-tap para sa mga detalye."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Buburahin ang iyong device"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Nagbibigay-daan sa app na magpadala ng mga sticky broadcast, na nananatili pagkatapos ng broadcast. Posibleng bumagal o maging hindi stable ang iyong Android TV device dahil sa sobra-sobrang paggamit sa pamamagitan ng pagtulak dito na gumamit ng masyadong maraming memory."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Pinapayagan ang app na magpadala ng mga sticky na pag-broadcast, na nananatili pagkatapos ng pag-broadcast. Maaaring pabagalin o gawing hindi matatag ng labis na paggamit ang telepono sa pamamagitan ng pagdulot dito na gumamit ng masyadong maraming memory."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"basahin ang iyong mga contact"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Pinapayagan ang app na magbasa ng data tungkol sa mga contact na naka-imbak sa iyong tablet, kabilang ang dalas kung kailan ka tumawag, nag-email, o nakipag-ugnayan sa iba pang mga paraan sa mga tukoy na indibidwal. Pinapayagan ng pahintulot na ito ang apps na i-save ang data ng iyong contact, at maaaring magbahagi ang nakakahamak na apps ng data ng contact nang hindi mo nalalaman."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Nagbibigay-daan sa app na basahin ang data tungkol sa iyong mga contact na naka-store sa Android TV device mo, kasama ang dalas ng iyong pagtawag, pag-email, o pakikipag-ugnayan sa iba pang paraan sa mga partikular na indibidwal. Nagbibigay-daan sa mga app ang pahintulot na ito na i-save ang iyong data ng contact at posibleng magbahagi ng data ng contact ang mga nakakapinsalang app nang hindi mo nalalaman."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Pinapayagan ang app na magbasa ng data tungkol sa mga contact na naka-imbak sa iyong telepono, kabilang ang dalas kung kailan ka tumawag, nag-email, o nakipag-ugnayan sa iba pang mga paraan sa mga tukoy na indibidwal. Pinapayagan ng pahintulot na ito ang apps na i-save ang data ng iyong contact, at maaaring magbahagi ang nakakahamak na apps ng data ng contact nang hindi mo nalalaman."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Nagbibigay-daan sa app na magbasa ng data tungkol sa iyong mga contact na naka-store sa tablet mo. Magkakaroon din ang mga app ng access sa mga account sa iyong tablet na gumawa ng mga contact. Puwedeng kasama rito ang mga account na ginawa ng mga na-install mong app. Nagbibigay-daan ang pahintulot na ito sa mga app na i-save ang iyong data ng contact, at puwedeng magbahagi ng data ng contact ang mga nakakahamak na app nang hindi mo alam."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Nagbibigay-daan sa app na magbasa ng data tungkol sa iyong mga contact na naka-store sa Android TV device mo. Magkakaroon din ang mga app ng access sa mga account sa iyong Android TV device na gumawa ng mga contact. Puwedeng kasama rito ang mga account na ginawa ng mga na-install mong app. Nagbibigay-daan ang pahintulot na ito sa mga app na i-save ang iyong data ng contact, at puwedeng magbahagi ng data ng contact ang mga nakakahamak na app nang hindi mo alam."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Nagbibigay-daan sa app na magbasa ng data tungkol sa iyong mga contact na naka-store sa telepono mo. Magkakaroon din ang mga app ng access sa mga account sa iyong telepono na gumawa ng mga contact. Puwedeng kasama rito ang mga account na ginawa ng mga na-install mong app. Nagbibigay-daan ang pahintulot na ito sa mga app na i-save ang iyong data ng contact, at puwedeng magbahagi ng data ng contact ang mga nakakahamak na app nang hindi mo alam."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"baguhin ang iyong mga contact"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Pinapayagan ang app na baguhin ang data tungkol sa iyong mga contact na naka-imbak sa iyong tablet, kabilang ang dalas kung kailan ka tumawag, nag-email, o nakipag-ugnayan sa iba pang mga paraan sa mga tukoy na contact. Pinapayagan ng pahintulot na ito ang apps na magtanggal ng data ng contact."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Nagbibigay-daan sa app na baguhin ang data tungkol sa iyong mga contact na naka-store sa iyong Android TV device, kasama ang dalas ng iyong pagtawag, pag-email, o pakikipag-ugnayan sa iba pang paraan sa mga partikular na contact. Nagbibigay-daan ang pahintulot na ito sa mga app na mag-delete ng data ng contact."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Pinapayagan ang app na baguhin ang data tungkol sa iyong mga contact na naka-imbak sa iyong telepono, kabilang ang dalas kung kailan ka tumawag, nag-email, o nakipag-ugnayan sa iba pang mga paraan sa mga tukoy na contact. Pinapayagan ng pahintulot na ito ang apps na magtanggal ng data ng contact."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Nagbibigay-daan sa app na baguhin ang data tungkol sa iyong mga contact na naka-store sa tablet mo. Nagbibigay-daan ang pahintulot na ito sa mga app na mag-delete ng data ng contact."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Nagbibigay-daan sa app na baguhin ang data tungkol sa iyong mga contact na naka-store sa Android TV device mo. Nagbibigay-daan ang pahintulot na ito sa mga app na mag-delete ng data ng contact."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Nagbibigay-daan sa app na baguhin ang data tungkol sa iyong mga contact na naka-store sa telepono mo. Nagbibigay-daan ang pahintulot na ito sa mga app na mag-delete ng data ng contact."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"basahin ang log ng tawag"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Mababasa ng app na ito ang iyong history ng tawag."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"isulat ang log ng tawag"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"i-access ang mga dagdag na command ng provider ng lokasyon"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Nagbibigay-daan sa app na mag-access ng mga karagdagang command ng provider ng lokasyon. Maaari nitong bigyang-daan ang app na gambalain ang pagpapatakbo ng GPS o ng iba pang mga pinagmulan ng lokasyon."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"i-access lang ang tumpak na lokasyon sa foreground"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Makukuha lang ng app na ito ang iyong eksaktong lokasyon kapag nasa foreground ito. Ang mga serbisyo ng lokasyon na ito ay dapat naka-on at available sa iyong telepono para magamit ng app ang mga ito. Maaaring lumakas ang pagkonsumo ng baterya dahil dito."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"i-access lang ang tinatantiyang lokasyon (batay sa network) sa foreground"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Makukuha ng app na ito ang iyong lokasyon batay sa mga source ng network gaya ng mga cell tower at Wi-Fi network ngunit magagawa lang ito kapag nasa foreground ang app. Ang mga serbisyo ng lokasyon na ito ay dapat naka-on at available sa iyong tablet para magamit ng app ang mga ito."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Makukuha ng app na ito ang iyong lokasyon batay sa mga source ng network gaya ng mga cell site at Wi-Fi network, pero magagawa lang ito kapag nasa foreground ang app. Naka-on at available dapat ang mga serbisyo ng lokasyon na ito sa iyong Android TV device para magamit ng app ang mga ito."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Makukuha ng app na ito ang iyong lokasyon batay sa mga source ng network gaya ng mga cell tower at Wi-Fi network, ngunit magagawa lang ito kapag nasa foreground ang app. Ang mga serbisyo ng lokasyon na ito ay dapat naka-on at available sa iyong telepono para magamit ng app ang mga ito."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Makukuha lang ng app na ito ang iyong eksaktong lokasyon kapag nasa foreground ito. Naka-on at available dapat ang mga serbisyo ng lokasyon sa iyong device para magamit ng app ang mga ito. Posibleng lumakas ang pagkonsumo ng baterya dahil dito."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"i-access lang ang tinatantyang lokasyon sa foreground"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Makukuha lang ng app na ito ang iyong tinatantyang lokasyon kapag nasa foreground ito. Naka-on at available dapat ang mga serbisyo ng lokasyon sa iyong device para magamit ng app ang mga ito."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"i-access ang lokasyon sa background"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Kung papahintulutan ito bukod pa sa pag-access sa tinataya o tumpak na lokasyon, maaaring i-access ng app ang lokasyon habang tumatakbo sa background."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Puwedeng i-access ng app na ito ang lokasyon habang tumatakbo sa background, bilang karagdagan sa access sa lokasyon sa foreground."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"baguhin ang mga setting ng iyong audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Pinapayagan ang app na baguhin ang mga pandaigdigang setting ng audio gaya ng volume at kung aling speaker ang ginagamit para sa output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"mag-record ng audio"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Pinapayagan ang app na tingnan ang configuration ng Bluetooth sa tablet, at na gumawa at tumanggap ng mga koneksyong may mga nakapares na device."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Nagbibigay-daan sa app na tingnan ang configuration ng Bluetooth sa iyong Android TV device, and at gumawa at tumanggap ng mga koneksyon sa mga nakapares na device."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Pinapayagan ang app na tingnan ang configuration ng Bluetooth sa telepono, at na gumawa at tumanggap ng mga koneksyong may mga nakapares na device."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kontrolin ang Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Pinapayagan ang app na makipag-ugnay sa Near Field Communication (NFC) na mga tag, card, at reader."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"i-disable ang iyong screen lock"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Nakakonekta sa <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"I-tap upang makita ang mga file"</string>
<string name="pin_target" msgid="8036028973110156895">"I-pin"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"I-unpin"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Impormasyon ng app"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Sinisimulan ang demo…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"I-update ang mga item na ito sa "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, at <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"I-save"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Hindi, salamat na lang"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Hindi ngayon"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Hindi Kailanman"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"I-update"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Magpatuloy"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"password"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"I-toggle ang Split Screen"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> app sa Pop-up na window."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar ng <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 326ace9..55cdc8d 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"İş profili yönetici uygulaması eksik ya da bozuk. Bunun sonucunda iş profiliniz ve ilgili veriler silindi. Yardım almak için yöneticiniz ile iletişim kurun."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"İş profiliniz arık bu cihazda kullanılamıyor"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Çok fazla şifre denemesi yapıldı"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Yönetici, cihazı kişisel kullanım için serbest bıraktı"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Cihaz yönetiliyor"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Kuruluşunuz bu cihazı yönetmekte olup ağ trafiğini izleyebilir. Ayrıntılar için dokunun."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Cihazınız silinecek"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Uygulamaya, yayın bittikten sonra da kalan sabit yayınlar gönderme izni verir. Aşırı kullanılması çok fazla bellek harcanmasına neden olarak Android TV cihazınızı yavaşlatabilir veya dengesiz hale getirebilir."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Uygulamaya, yayın bittikten sonra da kalan sabit yayınlar gönderme izni verir. Aşırı kullanılması çok fazla bellek harcanmasına neden olarak telefonunu yavaşlatabilir veya dengesiz hale getirebilir."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"kişilerinizi okuma"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Uygulamaya tabletinizde depolanan kişilerinizle ilgili verileri okuma izni verir. Bu verilere belirli kişilerle ne sıklıkta çağrı, e-posta veya diğer yöntemlerle iletişim kurduğunuz bilgisi dahildir. Bu izin, uygulamanın kişi verilerinizi kaydetmesine olanak sağlar ve kötü amaçlı uygulamalar kişi verilerini haberiniz olmadan paylaşabilir."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Uygulamaya Android TV cihazınızda depolanan kişilerinizle ilgili verileri okuma izni verir. Bu verilere belirli kişilerle ne sıklıkta çağrı, e-posta veya diğer yöntemlerle iletişim kurduğunuz bilgisi dahildir. Bu izin, uygulamanın kişi verilerinizi kaydetmesine olanak sağlar ve kötü amaçlı uygulamalar kişi verilerini sizden habersiz paylaşabilir."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Uygulamaya telefonunuzda depolanan kişilerinizle ilgili verileri okuma izni verir. Bu verilere belirli kişilerle ne sıklıkta çağrı, e-posta veya diğer yöntemlerle iletişim kurduğunuz bilgisi dahildir. Bu izin, uygulamanın kişi verilerinizi kaydetmesine olanak sağlar ve kötü amaçlı uygulamalar kişi verilerini sizden habersiz paylaşabilir."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Uygulamaya tabletinizde kayıtlı kişileriniz ile ilgili verileri okuma izni verir. Uygulamalar ayrıca tabletinizde kişi oluşturan hesaplara da erişebilir. Bu, yüklediğiniz uygulamalar tarafından oluşturulan hesapları içerebilir. Bu izin, uygulamanın kişi verilerinizi kaydetmesine olanak sağlar ve kötü amaçlı uygulamalar kişi verilerini sizden habersiz paylaşabilir."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Uygulamaya Android TV cihazınızda depolanan kişilerinizle ilgili verileri okuma izni verir. Uygulamalar ayrıca Android TV cihazınızda kişi oluşturan hesaplara da erişebilir. Bu, yüklediğiniz uygulamalar tarafından oluşturulan hesapları içerebilir. Bu izin, uygulamanın kişi verilerinizi kaydetmesine olanak sağlar ve kötü amaçlı uygulamalar kişi verilerini sizden habersiz paylaşabilir."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Uygulamaya telefonunuzda depolanan kişilerinizle ilgili verileri okuma izni verir. Uygulamalar ayrıca telefonunuzda kişi oluşturan hesaplara da erişebilir. Bu, yüklediğiniz uygulamalar tarafından oluşturulan hesapları içerebilir. Bu izin, uygulamanın kişi verilerinizi kaydetmesine olanak sağlar ve kötü amaçlı uygulamalar kişi verilerini sizden habersiz paylaşabilir."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"kişilerinizi değiştirme"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Uygulamaya tabletinizde depolanan kişilerinizle ilgili verileri değiştirme izni verir. Bu verilere belirli kişilerle ne sıklıkta çağrı, e-posta veya diğer yöntemlerle iletişim kurduğunuz bilgisi dahildir. Bu izin, uygulamanın kişi verilerinizi silmesine olanak sağlar."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Uygulamaya Android TV cihazınızda depolanan kişilerinizle ilgili verileri değiştirme izni verir. Bu verilere belirli kişilerle ne sıklıkta çağrı, e-posta veya diğer yöntemlerle iletişim kurduğunuz bilgisi dahildir. Bu izin, uygulamanın kişi verilerinizi silmesine olanak sağlar."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Uygulamaya telefonunuzda depolanan kişilerinizle ilgili verileri değiştirme izni verir. Bu verilere belirli kişilerle ne sıklıkta çağrı, e-posta veya diğer yöntemlerle iletişim kurduğunuz bilgisi dahildir. Bu izin, uygulamanın kişi verilerinizi silmesine olanak sağlar."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Uygulamaya tabletinizde kayıtlı kişileriniz ile ilgili verileri değiştirme izni verir. Bu izin, uygulamanın kişi verilerinizi silmesine olanak sağlar."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Uygulamaya Android TV cihazınızda kayıtlı kişileriniz ile ilgili verileri değiştirme izni verir. Bu izin, uygulamanın kişi verilerinizi silmesine olanak sağlar."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Uygulamaya telefonunuzda kayıtlı kişileriniz ile ilgili verileri değiştirme izni verir. Bu izin, uygulamanın kişi verilerinizi silmesine olanak sağlar."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"çağrı günlüğünü oku"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Bu uygulama, çağrı geçmişinizi okuyabilir."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"çağrı günlüğüne yaz"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ek konum sağlayıcı komutlarına eriş"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Uygulamanın, ekstra konum sağlayıcı komutlarına erişmesine izin verir. Bu izin, uygulamanın GPS veya diğer konum kaynaklarının çalışmasını kesmesine olanak sağlayabilir."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"yalnızca ön planda kesin konuma erişme"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Bu uygulama yalnızca ön plandayken kesin konumunuzu alabilir. Uygulamanın bu hizmetleri kullanabilmesi için telefonunuzda bu konum hizmetleri açık ve kullanılabilir olmalıdır. Bu, pil tüketimini artırabilir."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"yalnızca ön planda yaklaşık konuma (ağa dayalı) erişme"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Bu uygulama baz istasyonları ve kablosuz ağlar gibi ağ kaynaklarını dikkate alarak konumunuzu bulabilir, ancak bunu yalnızca ön plandayken yapabilir. Uygulamanın bu hizmetleri kullanabilmesi için tabletinizde bu konum hizmetleri açık ve kullanılabilir olmalıdır."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Bu uygulama baz istasyonları ve kablosuz ağlar gibi ağ kaynaklarını dikkate alarak konumunuzu bulabilir, ancak bunu yalnızca ön plandayken yapabilir. Uygulamanın bu hizmetleri kullanabilmesi için Android TV cihazınızda bu konum hizmetleri açık ve kullanılabilir olmalıdır."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Bu uygulama baz istasyonları ve kablosuz ağlar gibi ağ kaynaklarını dikkate alarak konumunuzu bulabilir, ancak bunu yalnızca ön plandayken yapabilir. Uygulamanın bu hizmetleri kullanabilmesi için telefonunuzda bu konum hizmetleri açık ve kullanılabilir olmalıdır."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Bu uygulama yalnızca ön plandayken kesin konumunuzu alabilir. Uygulamanın bu hizmetleri kullanabilmesi için telefonunuzda bu konum hizmetleri açık ve kullanılabilir olmalıdır. Bu, pil tüketimini artırabilir."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"yalnızca ön planda yaklaşık konuma erişme"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Bu uygulama, yalnızca ön planda çalışırken yaklaşık konum bilginizi alabilir. Uygulamanın kullanabilmesi için konum hizmetlerinin açılması ve cihazınızda kullanılabilir olması gerekir."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"konum bilgisine arka planda eriş"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Bu izin, yaklaşık veya tam konum erişimine ek olarak verilirse uygulama, konum bilgisine arka planda çalışırken erişebilir."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Bu uygulama ön plan konum erişimine ek olarak konum bilgisine arka planda çalışırken erişebilir."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ses ayarlarınızı değiştirin"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Uygulamaya ses düzeyi ve ses çıkışı için kullanılan hoparlör gibi genel ses ayarlarını değiştirme izni verir."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ses kaydet"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Uygulamaya, tabletteki Bluetooth yapılandırmasını görüntüleme, eşlenmiş cihazlarla bağlantı yapma ve bu tür bağlantıları kabul etme izni verir."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Uygulamaya, Android TV cihazınızdaki Bluetooth yapılandırmasını görüntüleme, eşleştirilmiş cihazlarla bağlantı yapma ve bu tür bağlantıları kabul etme izni verir."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Uygulamaya, telefondaki Bluetooth yapılandırmasını görüntüleme, eşlenmiş cihazlarla bağlantı yapma ve bu tür bağlantıları kabul etme izni verir."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"Yakın Alan İletişimini denetle"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Uygulamaya, Near Field Communication (NFC) etiketleri, kartlar ve okuyucular ile iletişim kurma izni verir."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ekran kilidimi devre dışı bırak"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> cihazına bağlandı"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Dosyaları görüntülemek için dokunun"</string>
<string name="pin_target" msgid="8036028973110156895">"Sabitle"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Sabitlemeyi kaldır"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Uygulama bilgileri"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Demo başlatılıyor…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Şu öğeler "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" hizmetinde güncellensin mi: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> ve <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Kaydet"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Hayır, teşekkürler"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Şimdi değil"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Hiçbir zaman"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Güncelle"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Devam"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"şifre"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Bölünmüş Ekranı aç/kapat"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Kilit Ekranı"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Ekran görüntüsü"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Pop-up pencerede <xliff:g id="APP_NAME">%1$s</xliff:g> uygulaması."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasının başlık çubuğu."</string>
</resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 8007f9e..00feb9e 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -192,8 +192,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Додаток адміністратора в робочому профілі відсутній або пошкоджений. У результаті ваш робочий профіль і пов’язані з ним дані видалено. Зверніться до свого адміністратора по допомогу."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Робочий профіль більше не доступний на цьому пристрої"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Забагато спроб ввести пароль"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Адміністратор не дозволив використовувати пристрій для особистих потреб"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Пристрій контролюється"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Адміністратор вашої організації контролює цей пристрій і відстежує мережевий трафік. Торкніться, щоб дізнатися більше."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"З вашого пристрою буде стерто всі дані"</string>
@@ -236,7 +235,7 @@
<string name="global_action_bug_report" msgid="5127867163044170003">"Звіт про помилки"</string>
<string name="global_action_logout" msgid="6093581310002476511">"Завершити сеанс"</string>
<string name="global_action_screenshot" msgid="2610053466156478564">"Знімок екрана"</string>
- <string name="bugreport_title" msgid="8549990811777373050">"Повідомлення про помилку"</string>
+ <string name="bugreport_title" msgid="8549990811777373050">"Звіт про помилку"</string>
<string name="bugreport_message" msgid="5212529146119624326">"Інформація про поточний стан вашого пристрою буде зібрана й надіслана електронною поштою. Підготовка звіту триватиме певний час."</string>
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Інтерактивний звіт"</string>
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Підходить для більшості випадків. Можна відстежувати, як створюється звіт, вводити більше деталей про проблему та робити знімки екрана. Можуть опускатися деякі розділи, які рідко використовуються, але довго створюються."</string>
@@ -387,13 +386,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Дозволяє додатку надсилати закріплені широкомовні повідомлення, які залишаються після їх відтворення. Надмірне використання може сповільнювати роботу пристрою Android TV або порушувати її стабільність, спричиняючи завелике використання пам\'яті."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Дозволяє програмі надсилати закріплені широкомовні повідомлення, які залишаються після відтворення широкомовного повідомлення. Надмірне використання може сповільнювати роботу телефону або порушувати її стабільність, спричиняючи завелике використання пам’яті."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"читати контакти"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Дозволяє програмі читати дані про контакти, які зберігаються у вашому планшетному ПК, зокрема частоту здійснення викликів, надсилання електронних листів або інших способів спілкування з окремими особами. Такий дозвіл дає програмам змогу зберігати ваші контактні дані. Шкідливі програми можуть надсилати контактні дані без вашого відома."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Дозволяє додатку зчитувати дані про контакти, які зберігаються на вашому пристрої Android TV, зокрема частоту здійснення викликів, надсилання електронних листів або спілкування з певними особами іншими способами. Додатки з цим дозволом можуть зберігати контактні дані. Шкідливі додатки можуть надсилати контактні дані без вашого відома."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Дозволяє програмі читати дані про контакти, які зберігаються у вашому телефоні, зокрема частоту здійснення викликів, надсилання електронних листів або інших способів спілкування з окремими особами. Такий дозвіл дає програмам змогу змогу зберігати ваші контактні дані. Шкідливі програми можуть надсилати контактні дані без вашого відома."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Дозволяє додатку переглядати дані про контакти, які зберігаються на вашому планшеті. Додатки також отримають доступ до облікових записів на вашому планшеті (зокрема до створених іншими встановленими додатками), у яких було створено контакти. Додатки з цим дозволом можуть зберігати ваші контактні дані. Шкідливі додатки можуть надсилати контактні дані без вашого відома."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Дозволяє додатку переглядати дані про контакти, які зберігаються на вашому пристрої Android TV. Додатки також отримають доступ до облікових записів на вашому пристрої Android TV (зокрема до створених іншими встановленими додатками), у яких було створено контакти. Додатки з цим дозволом можуть зберігати ваші контактні дані. Шкідливі додатки можуть надсилати контактні дані без вашого відома."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Дозволяє додатку переглядати дані про контакти, які зберігаються на вашому телефоні. Додатки також отримають доступ до облікових записів на вашому телефоні (зокрема до створених іншими встановленими додатками), у яких було створено контакти. Додатки з цим дозволом можуть зберігати ваші контактні дані. Шкідливі додатки можуть надсилати контактні дані без вашого відома."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"змінювати контакти"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Дозволяє програмі змінювати дані про контакти, які зберігаються у вашому планшетному ПК, зокрема частоту здійснення дзвінків, надсилання електронних листів або інших способів спілкування з окремими особами. Такий дозвіл дає програмам змогу видаляти контактні дані."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Дозволяє додатку змінювати дані про контакти, які зберігаються на вашому пристрої Android TV, зокрема частоту здійснення викликів, надсилання електронних листів або спілкування з певними особами іншими способами. Додатки з цим дозволом можуть видаляти контактні дані."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Дозволяє програмі змінювати дані про контакти, які зберігаються у вашому телефоні, зокрема частоту здійснення дзвінків, надсилання електронних листів або інших способів спілкування з окремими особами. Такий дозвіл дає програмам змогу видаляти контактні дані."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Дозволяє додатку змінювати дані про контакти, які зберігаються на вашому планшеті, а також видаляти їх."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Дозволяє додатку змінювати дані про контакти, які зберігаються на вашому пристрої Android TV, а також видаляти їх."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Дозволяє додатку змінювати дані про контакти, які зберігаються на вашому телефоні, а також видаляти їх."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"читати журнал викликів"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Цей додаток може переглядати історію викликів."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"записувати в журнал викликів"</string>
@@ -413,13 +412,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"отр. дост. до додат. команд пров. місцезн."</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Додаток отримуватиме доступ до додаткових команд постачальника геоданих. Можливе втручання додатка в роботу GPS чи інших джерел геоданих."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"отримувати доступ до даних про точне місцезнаходження лише в активному режимі"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Цей додаток може отримувати дані про ваше точне місцезнаходження лише в активному режимі. Щоб додаток користувався службами локації, вони мають бути наявні й увімкнені на вашому телефоні. Через це може швидше розряджатись акумулятор."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"отримувати доступ до даних про приблизне місцезнаходження (на основі мережі) лише в активному режимі"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Цей додаток може отримувати дані про ваше місцезнаходження на основі джерел мережі, як-от антен мобільного зв’язку та мереж Wi-Fi, лише в активному режимі. Щоб додаток міг користуватися цими службами локації, вони мають бути доступними й увімкненими на вашому планшеті."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Додаток може отримувати дані про ваше місцезнаходження на основі джерел мережі, як-от антен мобільного зв’язку та мереж Wi-Fi, лише в активному режимі. Щоб додаток міг користуватися цими службами локації, вони мають бути доступними й увімкненими на вашому пристрої Android TV."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Цей додаток може отримувати дані про ваше місцезнаходження на основі джерел мережі, як-от антен мобільного зв’язку та мереж Wi-Fi, лише в активному режимі. Щоб додаток міг користуватися цими службами локації, вони мають бути доступними й увімкненими на вашому телефоні."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Цей додаток може отримувати дані про ваше точне місцезнаходження лише в активному режимі. Щоб додаток міг використовувати Служби локації, вони мають бути доступні й увімкнені на вашому пристрої. Це може пришвидшити розряджання акумулятора."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"отримувати доступ до даних про приблизне місцезнаходження лише в активному режимі"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Цей додаток може отримувати дані про ваше приблизне місцезнаходження лише в активному режимі. Щоб додаток міг використовувати Служби локації, вони мають бути доступні й увімкнені на вашому пристрої."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"доступ до геоданих у фоновому режимі"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Якщо ви надасте цей дозвіл і доступ до приблизного або точного місцезнаходження, додаток зможе отримувати геодані у фоновому режимі."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Цей додаток може отримувати дані про місцезнаходження, коли його запущено не лише в активному, а й у фоновому режимі."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"змінювати налаштув-ня звуку"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дозволяє програмі змінювати загальні налаштування звуку, як-от гучність і динамік, який використовується для виводу сигналу."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"запис-ти аудіо"</string>
@@ -500,6 +497,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Дозволяє програмі переглядати конфігурацію Bluetooth на планшетному ПК, а також створювати та приймати з’єднання зі спареними пристроями."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Дозволяє додатку зчитувати конфігурацію Bluetooth на вашому пристрої Android TV, а також створювати та приймати з\'єднання зі спареними пристроями."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Дозволяє програмі переглядати конфігурацію Bluetooth на телефоні, а також створювати та приймати з’єднання зі спареними пристроями."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"контрол. Near Field Communication"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Дозволяє програмі обмінюватися даними з тегами, картками та читачами екрана Near Field Communication (NFC)."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"вимикати блокування екрана"</string>
@@ -906,7 +907,7 @@
<string name="factorytest_no_action" msgid="339252838115675515">"Не було знайдено жодного пакета, який надає дію FACTORY_TEST."</string>
<string name="factorytest_reboot" msgid="2050147445567257365">"Перезав."</string>
<string name="js_dialog_title" msgid="7464775045615023241">"На сторінці за адресою \"<xliff:g id="TITLE">%s</xliff:g>\" написано:"</string>
- <string name="js_dialog_title_default" msgid="3769524569903332476">"Javascript"</string>
+ <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
<string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Підтвердити перехід"</string>
<string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"Полишити цю сторінку"</string>
<string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Залишитися на цій сторінці"</string>
@@ -1356,7 +1357,7 @@
<string name="usb_contaminant_detected_message" msgid="7346100585390795743">"USB-порт автоматично вимкнено. Торкніться, щоб дізнатися більше."</string>
<string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"Можна використовувати USB-порт"</string>
<string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"Телефон уже не виявляє рідини або сміття."</string>
- <string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"Створюється повідомлення про помилку…"</string>
+ <string name="taking_remote_bugreport_notification_title" msgid="1582531382166919850">"Створення звіту про помилку…"</string>
<string name="share_remote_bugreport_notification_title" msgid="6708897723753334999">"Надіслати звіт про помилку?"</string>
<string name="sharing_remote_bugreport_notification_title" msgid="3077385149217638550">"Надсилається звіт про помилку…"</string>
<string name="share_remote_bugreport_notification_message_finished" msgid="7325635795739260135">"Ваш адміністратор просить надіслати повідомлення про помилку, щоб вирішити проблему з пристроєм. Він може отримати доступ до ваших додатків і даних."</string>
@@ -1926,7 +1927,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Під’єднано до пристрою <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Торкніться, щоб переглянути файли"</string>
<string name="pin_target" msgid="8036028973110156895">"Закріпити"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Відкріпити"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Про додатки"</string>
<string name="negative_duration" msgid="1938335096972945232">"-<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Запуск демонстрації…"</string>
@@ -1971,6 +1976,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Оновити в сервісі "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" такі дані: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> і <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Зберегти"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Ні, дякую"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Не зараз"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Ніколи"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Оновити"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Продовжити"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"пароль"</string>
@@ -2068,5 +2075,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Розділити екран"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Заблокувати екран"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Знімок екрана"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> у спливаючому вікні."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Смуга із субтитрами для додатка <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 312aed7d..04480d9 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"دفتری پروفائل کی منتظم ایپ یا تو غائب ہے یا خراب ہے۔ اس کی وجہ سے، آپ کا دفتری پروفائل اور متعلقہ ڈیٹا حذف کر دیے گئے ہیں۔ مدد کیلئے اپنے منتظم سے رابطہ کریں۔"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"آپ کا دفتری پروفائل اس آلہ پر مزید دستیاب نہیں ہے"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"پاس ورڈ کی بہت ساری کوششیں"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"منتظم نے ذاتی استعمال کے لیے آلہ کو دستبردار کیا ہے"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"آلہ زیر انتظام ہے"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"آپ کی تنظیم اس آلے کا نظم کرتی ہے اور وہ نیٹ ورک ٹریفک کی نگرانی کر سکتی ہے۔ تفاصیل کیلئے تھپتھپائیں۔"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"آپ کا آلہ صاف کر دیا جائے گا"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ایپ کو چپکنے والا براڈکاسٹس بھیجنے کی اجازت دیتا ہے، جو براڈکاسٹ ختم ہونے کے بعد بھی باقی رہتے ہیں۔ حد سے زیادہ استعمال میموری کے کافی زیادہ استعمال کی وجہ سے آپ کے Android TV کو سُست یا غیر مستحکم بنا سکتا ہے۔"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"ایپ کو اسٹیکی براڈکاسٹس بھیجنے کی اجازت دیتا ہے، جو براڈکاسٹ ختم ہونے کے بعد بھی باقی رہتے ہیں۔ حد سے زیادہ استعمال فون کو سست یا غیر مستحکم بنا سکتا ہے جس کی وجہ سے یہ میموری کا کافی زیادہ استعمال کر سکتا ہے۔"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"اپنے رابطوں کو پڑھیں"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"ایپ کو آپ کے ٹیبلٹ پر اسٹور کردہ آپ کے رابطوں، بشمول مخصوص افراد کو دوسرے طریقوں سے جس تعدد سے آپ نے کال، ای میل کیا ہے یا ان کے ساتھ مواصلت کی ہے اس کے بارے میں ڈیٹا کو پڑھنے کی اجازت دیتا ہے۔ یہ اجازت ایپس کو آپ کے رابطے کا ڈیٹا محفوظ کرنے کی اجازت دیتی ہے اور نقصان دہ ایپس آپ کے علم کے بغیر رابطے کے ڈیٹا کا اشتراک کرسکتی ہیں۔"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"ایپ کو آپ کے Android TV آلہ پر اسٹور کردہ آپ کے رابطے، بشمول مخصوص افراد کو دوسرے طریقوں سے جس تعدد سے آپ نے کال، ای میل کیا ہے یا ان کے ساتھ مواصلت کی ہے ان کے بارے میں ڈیٹا پڑھنے کی اجازت دیتا ہے۔ یہ اجازت ایپس کو آپ کے رابطے کا ڈیٹا محفوظ کرنے کی اجازت دیتی ہے اور نقصان دہ ایپس آپ کی جانکاری کے بغیر رابطے کے ڈیٹا کا اشتراک کر سکتی ہے۔"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"ایپ کو آپ کے فون پر اسٹور کردہ آپ کے رابطوں، بشمول مخصوص افراد کو دوسرے طریقوں سے جس تعدد سے آپ نے کال، ای میل کیا ہے یا ان کے ساتھ مواصلت کی ہے اس کے بارے میں ڈیٹا کو پڑھنے کی اجازت دیتا ہے۔ یہ اجازت ایپس کو آپ کے رابطے کا ڈیٹا محفوظ کرنے کی اجازت دیتی ہے اور نقصان دہ ایپس آپ کے علم کے بغیر رابطے کے ڈیٹا کا اشتراک کرسکتی ہیں۔"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"ایپ کو آپ کے ٹیبلیٹ پر اسٹور کردہ آپ کے رابطوں کے بارے میں ڈیٹا کو پڑھنے کی اجازت دیتی ہے۔ ایپس کو آپ کے ٹیبلیٹ پر ان اکاؤنٹس تک رسائی حاصل ہو گی جنہوں نے رابطے تخلیق کیے ہیں۔ اس میں آپ کی انسٹال کردہ ایپس کے ذریعے تخلیق کردہ اکاؤنٹس بھی شامل ہو سکتے ہیں۔ یہ اجازت ایپس کو آپ کے رابطے کا ڈیٹا محفوظ کرنے کی اجازت دیتی ہے اور نقصان دہ ایپس آپ کی جانکاری کے بغیر رابطے کے ڈیٹا کا اشتراک کر سکتی ہیں۔"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"ایپ کو آپ کے Android TV آلہ پر اسٹور کردہ آپ کے رابطوں کے بارے میں ڈیٹا کو پڑھنے کی اجازت دیتی ہے۔ ایپس کو آپ کے Android TV آلات پر ان اکاؤنٹس تک رسائی حاصل ہو گی جنہوں نے رابطے تخلیق کیے ہیں۔ اس میں آپ کی انسٹال کردہ ایپس کے ذریعے تخلیق کردہ اکاؤنٹس بھی شامل ہو سکتے ہیں۔ یہ اجازت ایپس کو آپ کے رابطے کا ڈیٹا محفوظ کرنے کی اجازت دیتی ہے اور نقصان دہ ایپس آپ کی جانکاری کے بغیر رابطے کے ڈیٹا کا اشتراک کر سکتی ہیں۔"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"ایپ کو آپ کے فون پر اسٹور کردہ آپ کے رابطوں کے بارے میں ڈیٹا پڑھنے کی اجازت دیتی ہے۔ ایپس کو آپ کے فون پر ان اکاؤنٹس تک رسائی حاصل ہو گی جنہوں نے رابطے تخلیق کیے ہیں۔ اس میں آپ کی انسٹال کردہ ایپس کے ذریعے تخلیق کردہ اکاؤنٹس بھی شامل ہو سکتے ہیں۔ یہ اجازت ایپس کو آپ کے رابطے کا ڈیٹا محفوظ کرنے کی اجازت دیتی ہے اور نقصان دہ ایپس آپ کی جانکاری کے بغیر رابطے کے ڈیٹا کا اشتراک کر سکتی ہیں۔"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"اپنے رابطوں میں ترمیم کریں"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"ایپ کو آپ کے ٹیبلٹ پر اسٹور کردہ آپ کے رابطوں، بشمول مخصوص رابطوں کو جس تعدد سے آپ نے کال، ای میل کیا ہے یا دوسرے طریقوں سے ان کے ساتھ مواصلت کی ہے اس کے بارے میں ڈیٹا میں ترمیم کی اجازت دیتا ہے۔ یہ اجازت ایپس کو رابطے کا ڈیٹا حذف کرنے کی اجازت دیتی ہے۔"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"ایپ کو آپ کے Android TV آلات پر اسٹور کردہ آپ کے رابطوں، بشمول مخصوص رابطوں کو جس تعدد سے آپ نے کال، ای میل کیا ہے یا دوسرے طریقوں سے ان کے ساتھ مواصلت کی ہے ان کے بارے میں ڈیٹا میں ترمیم کی اجازت دیتا ہے۔ یہ اجازت ایپس کو رابطے کا ڈیٹا حذف کرنے کی اجازت دیتی ہے۔"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"ایپ کو آپ کے فون پر اسٹور کردہ آپ کے رابطوں، بشمول مخصوص رابطوں کو جس تعدد سے آپ نے کال، ای میل کیا ہے یا دوسرے طریقوں سے ان کے ساتھ مواصلت کی ہے اس کے بارے میں ڈیٹا میں ترمیم کی اجازت دیتا ہے۔ یہ اجازت ایپس کو رابطے کا ڈیٹا حذف کرنے کی اجازت دیتی ہے۔"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"ایپ کو آپ کے ٹیبلیٹ پر اسٹور کردہ آپ کے رابطوں کے بارے میں ڈیٹا کو ترمیم کرنے کی اجازت دیتی ہے۔ یہ اجازت ایپس کو رابطے کا ڈیٹا حذف کرنے کی اجازت دیتی ہے۔"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"ایپ کو آپ کے Android TV آلہ پر اسٹور کردہ آپ کے رابطوں کے بارے میں ڈیٹا کو ترمیم کرنے کی اجازت دیتی ہے۔ یہ اجازت ایپس کو رابطے کا ڈیٹا حذف کرنے کی اجازت دیتی ہے۔"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ایپ کو آپ کے فون پر اسٹور کردہ آپ کے رابطوں کے بارے میں ڈیٹا کو ترمیم کرنے کی اجازت دیتی ہے۔ یہ اجازت ایپس کو رابطے کا ڈیٹا حذف کرنے کی اجازت دیتی ہے۔"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"کال لاگ پڑھیں"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"یہ ایپ آپ کی کال کی سرگزشت پڑھ سکتی ہے۔"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"کال لاگ لکھیں"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"اضافی مقام فراہم کنندہ کی کمانڈز تک رسائی حاصل کریں"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ایپ کو اضافی مقام فراہم کنندہ کی کمانڈز تک رسائی حاصل کرنے کی اجازت دیتی ہے۔ یہ ایپ کو GPS یا دوسرے مقام کے مآخذ کے عمل کے ساتھ مداخلت کرنے کی اجازت دے سکتی ہے۔"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"صرف پیش منظر میں درست مقام تک رسائی حاصل کریں"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"یہ ایپ جب پس منظر میں ہوتی ہے تبھی یہ آپ کا صحیح مقام حاصل کر سکتی ہے۔ ایپ کو ان مقام کی سروسز کو استعمال کر سکنے کیلئے ان کا آن ہونا اور آپ کے فون پر دستیاب ہونا ضروری ہے۔"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"صرف پیش منظر میں (نیٹ ورک پر مبنی) تخمینی مقام تک رسائی حاصل کریں"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"نیٹ ورک ماخذات جیسے کہ سیل ٹاورز اور Wi-Fi نیٹ ورکس کی بنیاد پر یہ ایپ آپ کا مقام حاصل کر سکتی ہے لیکن صرف اس وقت جب ایپ پیش منظر میں ہو۔ ایپ کو ان مقام کی سروسز کو استعمال کرنے کے لیے ان کا آن ہونا اور آپ کے ٹیبلیٹ پر دستیاب ہونا ضروری ہے۔"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"نیٹ ورک ماخذات جیسے کہ سیل ٹاورز اور Wi-Fi نیٹ ورکس کی بنیاد پر یہ ایپ آپ کا مقام حاصل کر سکتی ہے لیکن صرف اس وقت جب ایپ پیش منظر میں ہو۔ ایپ کو ان مقام کی سروسز کو استعمال کرنے کے لیے ان کا آن ہونا اور آپ کے Android TV آلہ پر دستیاب ہونا ضروری ہے۔"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"نیٹ ورک ماخذات جیسے کہ سیل ٹاورز اور Wi-Fi نیٹ ورکس کی بنیاد پر یہ ایپ آپ کا مقام حاصل کر سکتی ہے لیکن صرف اس وقت جب ایپ پیش منظر میں ہو۔ ایپ کو ان مقام کی سروسز کو استعمال کرنے کے لیے ان کا آن ہونا اور آپ کے فون پر دستیاب ہونا ضروری ہے۔"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"یہ ایپ جب پس منظر میں ہوتی ہے تبھی یہ آپ کا صحیح مقام حاصل کر سکتی ہے۔ ایپ کو مقام کی سروسز کو استعمال کرنے کیلئے اِن کا آپ کے آلہ پر دستیاب ہونا اور آن ہونا ضروری ہے۔ اس سے بیٹری کی کھپت میں اضافہ ہو سکتا ہے۔"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"صرف پیش منظر میں تخمینی مقام تک رسائی"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"یہ ایپ جب پس منظر میں ہوتی ہے تبھی یہ آپ کا تخمینی مقام حاصل کر سکتی ہے۔ ایپ کو مقام کی سروسز کو استعمال کرنے کیلئے ان کا آپ کے آلہ پر دستیاب ہونا اور آن ہونا ضروری ہے۔"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"پس منظر میں مقام کی رسائی حاصل کریں"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"اگر اضافی طور پر اسے تخمینی یا درست مقام تک رسائی کی منظوری دی جاتی ہے تو پس منظر میں چلنے کے دوران ایپ اس مقام تک رسائی حاصل کر سکتی ہے۔"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"یہ ایپ پیش منظر کے مقام تک رسائی کے ساتھ ساتھ، پس منظر میں چلتے ہوئے مقام تک رسائی حاصل کر سکتی ہے۔"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"اپنے آڈیو کی ترتیبات کو تبدیل کریں"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ایپ کو مجموعی آڈیو ترتیبات جیسے والیوم اور آؤٹ پٹ کیلئے جو اسپیکر استعمال ہوتا ہے اس میں ترمیم کرنے کی اجازت دیتا ہے۔"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"آڈیو ریکارڈ کریں"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ایپ کو ٹیبلیٹ پر بلوٹوتھ کی ترتیب دیکھنے اور جوڑا بنائے ہوئے آلات کے ساتھ کنکشنز بنانے اور قبول کرنے کی اجازت دیتا ہے۔"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ایپ کو آپ کے Android TV آلہ پر بلوٹوتھ کنفیگریشن دیکھنے، اور جوڑا بنائے ہوئے آلات کے ساتھ کنکشنز بنانے اور قبول کرنے کی اجازت دیتا ہے۔"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"ایپ کو فون پر بلوٹوتھ کی ترتیب دیکھنے اور جوڑا بنائے ہوئے آلات کے ساتھ کنکشنز بنانے اور قبول کرنے کی اجازت دیتا ہے۔"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"Near Field کمیونیکیشن کنٹرول کریں"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"ایپ کو Near Field Communication (NFC) ٹیگز، کارڈز اور ریڈرز کے ساتھ مواصلت کرنے کی اجازت دیٹا ہے۔"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"اپنے اسکرین لاک کو غیر فعال کریں"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> سے منسلک"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"فائلوں کو دیکھنے کیلئے تھپتھپائیں"</string>
<string name="pin_target" msgid="8036028973110156895">"پن کریں"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"پن ہٹائیں"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"ایپ کی معلومات"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"ڈیمو شروع ہو رہا ہے…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"ان آئٹمز کو "<b>"<xliff:g id="LABEL">%4$s</xliff:g> "</b>" میں اپ ڈیٹ کریں: <xliff:g id="TYPE_0">%1$s</xliff:g>، <xliff:g id="TYPE_1">%2$s</xliff:g> اور <xliff:g id="TYPE_2">%3$s</xliff:g> ؟"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"محفوظ کریں"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"نہیں، شکریہ"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"ابھی نہیں"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"کبھی نہیں"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"اپ ڈیٹ کریں"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"جاری رکھیں"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"پاس ورڈ"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"اسپلٹ اسکرین ٹوگل کریں"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"مقفل اسکرین"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"اسکرین شاٹ"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"پوپ-اپ ونڈو میں <xliff:g id="APP_NAME">%1$s</xliff:g> ایپ۔"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> کی کیپشن بار۔"</string>
</resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index c7987d1..8872dac 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Ishchi profilning administrator ilovasi yo‘q yoki buzilgan. Shuning uchun, ishchi profilingiz va unga aloqador ma’lumotlar o‘chirib tashlandi. Yordam olish uchun administratoringizga murojaat qiling."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Bu qurilmada endi ishchi profilingiz mavjud emas"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Parol ko‘p marta xato kiritildi"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Administrator shaxsiy foydalanishga qoldirilgan qurilmani rad etdi"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Bu – boshqariladigan qurilma"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Tashkilotingiz bu qurilmani boshqaradi va tarmoq trafigini nazorat qilishi mumkin. Tafsilotlar uchun bosing."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Qurilmangizdagi ma’lumotlar o‘chirib tashlanadi"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Ilova yuborilganidan keyin oʻchib ketmaydigan muddatsiz tarqatma xabarlarni yuborishi mumkin. Ulardan notoʻgʻri maqsadda foydalanish qurilmaning ishlashini sekinlatishi yoki xotiraga haddan ziyod yuklanish tushishi oqibatida qurilma ishdan chiqishi mumkin."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Ilova yuborilganidan keyin o‘chib ketmaydigan muddatsiz tarqatma xabarlarni yuborishi mumkin. Ulardan noto‘g‘ri maqsadda foydalanish qurilmaning ishlashini sekinlatishi yoki xotiraga haddan ziyod yuklanish tushishi oqibatida qurilma ishdan chiqishi mumkin."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"kontaktlaringizni ko‘rish"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Ilovaga planshetingizda saqlangan kontaktlar ma’lumotlarini, shuningdek, ba‘zi shaxslarga qilgan qo‘ng‘iroqlar muntazamligi, ularga yozgan e-pochta xabarlari yoki boshqa xabar almashish yo‘llari orqali xabarlashganingiz haqidagi ma’lumotlarni o‘qishga ruxsat beradi. Ushbu ruxsat ilovalarga aloqa ma’lumotlaringizni saqlash uchun ruxsat beradi va zararli ilovalar sizga bildirmasdan kontaktlar ma’lumotlaringizni boshqalarga ulashishi mumkin."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Ilovaga Android TV qurilmangizdagi kontaktlar haqidagi axborotni, jumladan, muayyan shaxslar bilan chaqiruv, email orqali xabarlashish yoki muloqot qilish takroriyligi haqidagi axborotni oʻqish huquqini beradi. Bu ruxsat ilovalarga kontaktlaringizga oid axborotni saqlash huquqini berib, zararli ilovalar uning yordamida kontakt maʼlumotlarini sizdan beruxsat boshqalarga ulashishi mumkin."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Ilovaga telefoningizda saqlangan kontaktlar ma’lumotlarini, shuningdek, ba‘zi shaxslarga qilgan qo‘ng‘iroqlar muntazamligi, ularga yozgan e-pochta xabarlari yoki boshqa xabar almashish yo‘llari orqali xabarlashganingiz haqidagi ma’lumotlarni o‘qishga ruxsat beradi. Ushbu ruxsat ilovalarga kontaktlar ma’lumotlaringizni saqlash uchun ruxsat beradi va zararli ilovalar sizga bildirmasdan aloqa ma’lumotlaringizni boshqalarga ulashishi mumkin."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Bu planshetga telefoningizdagi kontaktlarga oid axborotni oʻqish huquqini beradi. Ilovalar ham planshetdagi kontaktlarni yaratgan hisoblaringizga ruxsat oladi Jumladan, oʻrnatilgan ilovalar ochgan hisoblarga ham ruxsat beriladi. Bu ruxsat ilovalarga kontaktlaringizga oid axborotni saqlash huquqini berib, zararli ilovalar uning yordamida kontakt maʼlumotlarini sizdan beruxsat boshqalarga ulashishi mumkin."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Bu ilovaga Android TV qurilmangizdagi kontaktlarga oid axborotni oʻqish huquqini beradi. Ilovalar ham Android TV qurilmasidagi kontaktlarni yaratgan hisoblaringizga ruxsat oladi Jumladan, oʻrnatilgan ilovalar ochgan hisoblarga ham ruxsat beriladi. Bu ruxsat ilovalarga kontaktlaringizga oid axborotni saqlash huquqini berib, zararli ilovalar uning yordamida kontakt maʼlumotlarini sizdan beruxsat boshqalarga ulashishi mumkin."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Bu ilovaga telefoningizdagi kontaktlarga oid axborotni oʻqish huquqini beradi. Ilovalar ham telefondagi kontaktlarni yaratgan hisoblaringizga ruxsat oladi Jumladan, oʻrnatilgan ilovalar ochgan hisoblarga ham ruxsat beriladi. Bu ruxsat ilovalarga kontaktlaringizga oid axborotni saqlash huquqini berib, zararli ilovalar uning yordamida kontakt maʼlumotlarini sizdan beruxsat boshqalarga ulashishi mumkin."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"kontaktlaringizni tahrirlash"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Ilovaga planshetingizda saqlangan kontaktlar ma’lumotlarini, shuningdek, ba‘zi shaxslarga qilgan qo‘ng‘iroqlar muntazamligi, ularga yozgan e-pochta xabarlari yoki boshqa xabar almashish yo‘llari orqali xabarlashganingiz haqidagi ma’lumotlarni o‘zgartirishga ruxsat beradi. Ushbu ruxsat ilovalarga kontaktlar ma’lumotlarini o‘chirishga ruxsat beradi."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Ilovaga Android TV qurilmangizda saqlangan kontaktlar haqidagi axborotni, jumladan, muayyan kontakt bilan chaqiruv, email yoki boshqa usullar orqali muloqot qilish takroriyligini tahrirlash huquqini beradi. Bu ruxsat orqali ilovalar kontaktlar haqidagi axborotni oʻchirib tashlashi mumkin."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Ilovaga telefoningizda saqlangan kontaktlar ma’lumotlarini, shuningdek, ba‘zi shaxslarga qilgan qo‘ng‘iroqlar muntazamligi, ularga yozgan e-pochta xabarlari yoki boshqa xabar almashish yo‘llari orqali xabarlashganingiz haqidagi ma’lumotlarni o‘zgartirishga ruxsat beradi. Ushbu ruxsat ilovalarga kontaktlar ma’lumotlarini o‘chirishga ruxsat beradi."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Bu ilovaga planshetingizdagi kontaktlarga oid axborotni oʻzgartirish huquqini beradi. Bu ruxsat ilovalarga kontaktlarga oid axborotni oʻchirish imkonini ham beradi."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Bu ilovaga Android TV qurilmangizdagi kontaktlarga oid axborotni oʻzgartirish huquqini beradi. Bu ruxsat ilovalarga kontaktlarga oid axborotni oʻchirish imkonini ham beradi."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Bu ilovaga telefoningizdagi kontaktlarga oid axborotni oʻzgartirish huquqini beradi. Bu ruxsat ilovalarga kontaktlarga oid axborotni oʻchirish imkonini ham beradi."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"qo‘ng‘iroq jurnallarini o‘qish"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Bu ilova chaqiruvlar tarixini o‘qiy oladi."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"qo‘ng‘iroq jurnaliga yozish"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"qo‘shimcha manzillarga kirish buyruqlari"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Ilovaga qo‘shimcha joylashuv xizmati buyruqlaridan foydalanishga ruxsat beradi. Uning yordamida ilova GPS yoki boshqa joylashuv ma’lumoti manbalarining ishlashiga xalaqit qilishi mumkin."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"aniq joylashuv axborotini olishga faqat old fonda ruxsat"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Bu ilova faqat fon rejimida aniq joylashuv axborotingizdan foydalanishi mumkin. Ilova ushbu joylashuv xizmatlaridan foydalana olishi uchun ular telefoningizda yoniq turishi va ishlashi kerak. Bunda batareya sarfi oshishi mumkin."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"faqat faol rejimda taxminiy joylashuv axborotiga (tarmoq asosida) ruxsat"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Bu ilova faqat faol rejimda ekanida Wi-Fi va uyali tarmoq antennalari kabi tarmoq manbalari asosida joylashuvingiz axborotini olishi mumkin. Ilova ushbu joylashuv xizmatlaridan foydalana olishi uchun ular planshetingizda yoniq bo‘lishi va ishlashi kerak."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Bu ilova faqat faol rejimda ekanida Wi-Fi va uyali tarmoq antennalari kabi tarmoq manbalari asosida joylashuvingiz axborotini olishi mumkin. Ilova ushbu joylashuv xizmatlaridan foydalana olishi uchun ular Android TV qurilmangizda yoniq boʻlishi va ishlashi kerak."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Bu ilova faqat faol rejimda ekanida Wi-Fi va uyali tarmoq antennalari kabi tarmoq manbalari asosida joylashuvingiz axborotini olishi mumkin. Ilova ushbu joylashuv xizmatlaridan foydalana olishi uchun ular telefoningizda yoniq bo‘lishi va ishlashi kerak."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Bu ilova faqat fon rejimida aniq joylashuv axborotingizdan foydalanishi mumkin. Ilova joylashuv xizmatlaridan foydalana olishi uchun ular qurilmangizda yoniq turishi va ishlashi kerak. Bunda batareya sarfi oshishi mumkin."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"taxminiy joylashuv axborotini olishga faqat old fonda ruxsat"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Bu ilova faqat fon rejimida taxminiy joylashuv axborotingizdan foydalanishi mumkin. Ilova joylashuv xizmatlaridan foydalana olishi uchun ular avtomobilingizda yoniq boʻlishi va ishlashi kerak."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"fonda joylashuv axborotidan foydalanish"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Agar taxminiy yoki aniq joylashuv axborotiga qo‘shimcha tarzda ruxsat berilgan bo‘lsa, ilova ishlayotganda joylashuv axborotidan fonda foydalana oladi."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Bu ilova joylashuv axborotidan orqa fonda ham, old fonda ham foydalanishi mumkin."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"audio sozlamalaringizni o‘zgartirish"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ilovalarga tovush va ovoz chiqarish uchun foydalaniladigan karnay kabi global audio sozlamalarini o‘zgartirish uchun ruxsat beradi."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ovoz yozib olish"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Ilovaga planshetdagi Bluetooth‘ning sozlamasini ko‘rishga va bog‘langan qurilmalarga ulanish va ulardan ulanish so‘rovlarini qabul qulishga imkon beradi."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Ilovaga Android TV qurilmangizdagi Bluetooth sozlamasini koʻrishga va bogʻlangan qurilmalarga ulanish va ulardan ulanish talablarni qabul qilishga imkon beradi."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Ilovaga telefondagi Bluetooth‘ning sozlamasini ko‘rishga va bog‘langan qurilmalarga ulanish va ulardan ulanish so‘rovlarini qabul qulishga imkon beradi."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"NFC modulini boshqarish"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Ilova qisqa masofali aloqa (NFC) texnologiyasi yordamida NFC yorliqlari, kartalar va o‘qish moslamalari bilan ma’lumot almashishi mumkin."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ekran qulfini o‘chirib qo‘yish"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> qurilmasiga ulandi"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Fayllarni ko‘rish uchun bosing"</string>
<string name="pin_target" msgid="8036028973110156895">"Qadash"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Olib tashlash"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Ilova haqida"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Demo boshlanmoqda…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291"><b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" xizmatidagi <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> va <xliff:g id="TYPE_2">%3$s</xliff:g> yangilansinmi?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Saqlash"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Kerak emas"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Keyinroq"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Hech qachon"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Yangilash"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Davom etish"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"parol"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Ekranni ikkiga ajratish tugmasi"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Ekran qulfi"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Skrinshot"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi qalqib chiquvchi oynada."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> taglavhalar paneli."</string>
</resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 13c75f6..c21b81d 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Ứng dụng quản trị hồ sơ công việc bị thiếu hoặc hỏng. Do vậy, hồ sơ công việc của bạn và dữ liệu liên quan đã bị xóa. Hãy liên hệ với quản trị viên của bạn để được trợ giúp."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Hồ sơ công việc của bạn không có sẵn trên thiết bị này nữa"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Quá nhiều lần nhập mật khẩu"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Quản trị viên đã từ bỏ quyền sở hữu thiết bị để cho phép dùng vào mục đích cá nhân"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Thiết bị được quản lý"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Tổ chức của bạn sẽ quản lý thiết bị này và có thể theo dõi lưu lượng truy cập mạng. Nhấn để biết chi tiết."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Thiết bị của bạn sẽ bị xóa"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Cho phép ứng dụng gửi nội dung truyền phát hấp dẫn người xem. Nội dung này sẽ vẫn còn sau khi quá trình truyền phát kết thúc. Việc sử dụng quá mức có thể làm cho thiết bị Android TV bị chậm hoặc không ổn định do việc sử dụng quá mức khiến thiết bị sử dụng quá nhiều bộ nhớ."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Cho phép ứng dụng gửi nội dung truyền phát hấp dẫn người xem. Nội dung này sẽ vẫn còn sau khi quá trình truyền phát kết thúc. Việc sử dụng quá mức có thể làm cho điện thoại bị chậm hoặc không ổn định do khiến điện thoại sử dụng quá nhiều bộ nhớ."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"đọc danh sách liên hệ của bạn"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Cho phép ứng dụng đọc dữ liệu về các liên hệ được lưu trữ trên máy tính bảng của bạn, bao gồm tần suất bạn đã gọi điện, gửi email hoặc liên lạc theo các cách khác với những người cụ thể. Quyền này cho phép ứng dụng lưu dữ liệu liên lạc của bạn và các ứng dụng độc hại có thể chia sẻ dữ liệu liên lạc mà bạn không biết."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Cho phép ứng dụng đọc dữ liệu về người liên hệ mà bạn lưu trên thiết bị Android TV, bao gồm cả tần suất bạn gọi điện, gửi email hoặc liên lạc theo cách khác với những cá nhân cụ thể. Quyền này cho phép ứng dụng lưu dữ liệu về người liên hệ của bạn, và các ứng dụng độc hại có thể chia sẻ dữ liệu về người liên hệ mà bạn không biết."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Cho phép ứng dụng đọc dữ liệu về các liên hệ được lưu trữ trên điện thoại của bạn, bao gồm tần suất bạn đã gọi điện, gửi email hoặc liên lạc theo các cách khác với những người cụ thể. Quyền này cho phép ứng dụng lưu dữ liệu liên lạc của bạn và các ứng dụng độc hại có thể chia sẻ dữ liệu liên lạc mà bạn không biết."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Cho phép ứng dụng đọc dữ liệu về người liên hệ mà bạn lưu trữ trên máy tính bảng của mình. Ứng dụng cũng sẽ có quyền truy cập vào các tài khoản đã tạo người liên hệ trên máy tính bảng của bạn. Đây có thể là các tài khoản do ứng dụng (bạn đã cài đặt) tạo. Quyền này cho phép ứng dụng lưu dữ liệu về người liên hệ của bạn và các ứng dụng độc hại có thể lén lút chia sẻ dữ liệu này."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Cho phép ứng dụng đọc dữ liệu về người liên hệ mà bạn lưu trữ trên thiết bị Android TV của mình. Ứng dụng cũng sẽ có quyền truy cập vào các tài khoản đã tạo người liên hệ trên thiết bị Android TV của bạn. Đây có thể là các tài khoản do ứng dụng (bạn đã cài đặt) tạo. Quyền này cho phép ứng dụng lưu dữ liệu về người liên hệ của bạn và các ứng dụng độc hại có thể lén lút chia sẻ dữ liệu này."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Cho phép ứng dụng đọc dữ liệu về người liên hệ mà bạn lưu trữ trên điện thoại. Ứng dụng cũng sẽ có quyền truy cập vào các tài khoản đã tạo người liên hệ trên điện thoại của bạn. Đây có thể là các tài khoản do ứng dụng (bạn đã cài đặt) tạo. Quyền này cho phép ứng dụng lưu dữ liệu về người liên hệ của bạn và các ứng dụng độc hại có thể lén lút chia sẻ dữ liệu này."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"sửa đổi danh sách liên hệ của bạn"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Cho phép ứng dụng sửa đổi dữ liệu về các địa chỉ liên hệ được lưu trữ trên máy tính bảng của bạn, bao gồm tần suất mà bạn đã gọi, gửi email hoặc liên lạc theo các cách khác với những địa chỉ liên hệ cụ thể. Quyền này cho phép ứng dụng xóa dữ liệu liên lạc."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Cho phép ứng dụng sửa đổi dữ liệu về người liên hệ mà bạn lưu trên thiết bị Android TV, bao gồm cả tần suất bạn gọi điện, gửi email hoặc liên lạc theo cách khác với những người liên hệ cụ thể. Quyền này cho phép ứng dụng xóa dữ liệu về người liên hệ."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Cho phép ứng dụng sửa đổi dữ liệu về các địa chỉ liên hệ được lưu trữ trên điện thoại của bạn, bao gồm tần suất mà bạn đã gọi, gửi email hoặc liên lạc theo các cách khác với những địa chỉ liên hệ cụ thể. Quyền này cho phép ứng dụng xóa dữ liệu liên lạc."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Cho phép ứng dụng sửa đổi dữ liệu về người liên hệ mà bạn lưu trữ trên máy tính bảng của mình. Quyền này cho phép ứng dụng xóa dữ liệu về người liên hệ."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Cho phép ứng dụng sửa đổi dữ liệu về người liên hệ mà bạn lưu trữ trên thiết bị Android TV. Quyền này cho phép ứng dụng xóa dữ liệu về người liên hệ."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Cho phép ứng dụng sửa đổi dữ liệu về người liên hệ mà bạn lưu trữ trên điện thoại của mình. Quyền này cho phép ứng dụng xóa dữ liệu về người liên hệ."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"đọc nhật ký cuộc gọi"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Ứng dụng này có thể đọc nhật ký cuộc gọi của bạn."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"ghi nhật ký cuộc gọi"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"truy cập vào các lệnh của nhà cung cấp vị trí bổ sung"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Cho phép ứng dụng truy cập vào các lệnh của nhà cung cấp vị trí bổ sung. Điều này có thể cho phép ứng dụng can thiệp vào hoạt động của Hệ thống định vị toàn cầu (GPS) hoặc các nguồn vị trí khác."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"chỉ truy cập vị trí chính xác trong nền trước"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Bất cứ khi nào chạy trong nền trước, ứng dụng này có thể nhận thông tin vị trí chính xác của bạn. Để ứng dụng có thể sử các dụng dịch vụ vị trí, điện thoại của bạn phải có các dịch vụ này và dịch vụ ở trạng thái bật. Hoạt động này có thể tăng mức tiêu thụ pin."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"chỉ truy cập vị trí gần đúng (dựa trên mạng) trong nền trước"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Bất cứ khi nào chạy trong nền trước, ứng dụng này có thể nhận thông tin vị trí dựa trên nguồn mạng, chẳng hạn như trạm phát sóng di động và mạng Wi-Fi. Để ứng dụng có thể dùng các dịch vụ vị trí, máy tính bảng của bạn phải có các dịch vụ này ở trạng thái bật."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Chỉ khi ở nền trước, ứng dụng này mới có thể nhận thông tin vị trí của bạn dựa trên các nguồn mạng, chẳng hạn như trạm phát sóng di động và mạng Wi-Fi. Bạn phải bật và sử dụng được các dịch vụ vị trí này trên thiết bị Android TV thì ứng dụng mới có thể dùng các dịch vụ đó."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Bất cứ khi nào chạy trong nền trước, ứng dụng này có thể nhận thông tin vị trí dựa trên nguồn mạng, chẳng hạn như trạm phát sóng di động và mạng Wi-Fi. Để ứng dụng có thể dùng các dịch vụ vị trí, điện thoại của bạn phải có các dịch vụ này ở trạng thái bật."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Ứng dụng này chỉ có thể nhận thông tin vị trí chính xác của bạn khi mở trên màn hình. Để ứng dụng có thể sử các dụng dịch vụ vị trí, thiết bị của bạn phải có các dịch vụ này và dịch vụ ở trạng thái bật. Hoạt động này có thể tăng mức tiêu thụ pin."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"chỉ truy cập thông tin vị trí gần đúng khi ứng dụng mở trên màn hình"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Ứng dụng này chỉ có thể nhận được thông tin vị trí gần đúng của bạn khi mở trên màn hình. Để ứng dụng có thể dùng các dịch vụ vị trí này, thiết bị của bạn phải có các dịch vụ này và dịch vụ ở trạng thái bật."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"truy cập vào vị trí trong nền"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Nếu bạn cấp cho ứng dụng quyền truy cập bổ sung vào vị trị gần đúng hoặc chính xác, thì ứng dụng có thể truy cập vào vị trí đó khi chạy trong nền."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Ngoài quyền truy cập vào thông tin vị trí khi mở trên màn hình, ứng dụng này còn có thể truy cập vào thông tin vị trí khi đang chạy trong nền."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"thay đổi cài đặt âm thanh của bạn"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Cho phép ứng dụng sửa đổi cài đặt âm thanh chung chẳng hạn như âm lượng và loa nào được sử dụng cho thiết bị ra."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ghi âm"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Cho phép ứng dụng xem cấu hình của Bluetooth trên máy tính bảng và tạo và chấp nhận các kết nối với các thiết bị được ghép nối."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Cho phép ứng dụng xem cấu hình của Bluetooth trên thiết bị Android TV, đồng thời tạo và chấp nhận các kết nối với thiết bị được ghép nối."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Cho phép ứng dụng xem cấu hình của Bluetooth trên điện thoại, tạo và chấp nhận các kết nối với các thiết bị được ghép nối."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"kiểm soát Liên lạc trường gần"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Cho phép ứng dụng giao tiếp với thẻ Giao tiếp trường gần (NFC), thẻ và trình đọc."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"vô hiệu hóa khóa màn hình của bạn"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Đã kết nối với <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Nhấn để xem tệp"</string>
<string name="pin_target" msgid="8036028973110156895">"Ghim"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Bỏ ghim"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Thông tin ứng dụng"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Đang bắt đầu bản trình diễn..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Cập nhật các mục này: <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> và <xliff:g id="TYPE_2">%3$s</xliff:g> trong "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Lưu"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Không, cảm ơn"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Để sau"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Không bao giờ"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Cập nhật"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Tiếp tục"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"mật khẩu"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Bật/tắt chế độ chia đôi màn hình"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Khóa màn hình"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Chụp ảnh màn hình"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"Ứng dụng <xliff:g id="APP_NAME">%1$s</xliff:g> trong Cửa sổ bật lên."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Thanh phụ đề của <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 1df2a40..cc53668 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"工作资料管理应用缺失或损坏,因此系统已删除您的工作资料及相关数据。如需帮助,请与您的管理员联系。"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"您的工作资料已不在此设备上"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"密码尝试次数过多"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"管理员已将该设备开放给个人使用"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"设备为受管理设备"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"贵单位会管理该设备,且可能会监控网络流量。点按即可了解详情。"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"系统将清空您的设备"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"允许应用发送置顶广播,这类广播在广播结束后仍会继续存在。过度使用这项功能可能会导致 Android TV 设备使用过多内存,从而降低其运行速度或稳定性。"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"允许该应用发送持久广播消息,此类消息在广播结束后仍会保留。过度使用可能会导致手机使用过多内存,从而降低其速度或稳定性。"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"读取联系人"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"允许该应用读取您平板电脑上存储的联系人的相关数据,包括您通过打电话、发送电子邮件或以其他方式与特定个人通信的频率。此权限可让应用保存您的联系人数据,而恶意应用可能会在您不知情的情况下分享联系人数据。"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"允许应用读取您的 Android TV 设备上存储的联系人相关数据,包括您与特定用户通话、发送电子邮件或通过其他方式进行通信的频率。此权限可让应用保存您的联系人数据,而恶意应用可能会在您不知情的情况下分享联系人数据。"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"允许该应用读取您手机上存储的联系人的相关数据,包括您通过打电话、发送电子邮件或以其他方式与特定个人通信的频率。此权限可让应用保存您的联系人数据,而恶意应用可能会在您不知情的情况下分享联系人数据。"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"允许该应用读取您的平板电脑上存储的联系人相关数据。应用还将有权访问您的平板电脑上已创建联系人的帐号,其中可能包括您已安装的应用所创建的帐号。此权限允许应用保存您的联系人数据,而恶意应用可能会在您不知情的情况下分享联系人数据。"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"允许该应用读取您的 Android TV 设备上存储的联系人相关数据。应用还将有权访问您的 Android TV 设备上已创建联系人的帐号,其中可能包括您已安装的应用所创建的帐号。此权限允许应用保存您的联系人数据,而恶意应用可能会在您不知情的情况下分享联系人数据。"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"允许该应用读取您手机上存储的联系人相关数据。应用还将有权访问您的手机上已创建联系人的帐号,其中可能包括您已安装的应用所创建的帐号。此权限允许应用保存您的联系人数据,而恶意应用可能会在您不知情的情况下分享联系人数据。"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"修改您的通讯录"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"允许该应用修改您平板电脑上存储的联系人的相关数据,包括您通过打电话、发送电子邮件或以其他方式与特定联系人通信的频率。此权限可让应用删除联系人数据。"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"允许应用修改您 Android TV 设备上存储的联系人相关数据,包括您与特定联系人通话、发送电子邮件或通过其他方式进行通信的频率。此权限可让应用删除联系人数据。"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"允许该应用修改您手机上存储的联系人的相关数据,包括您通过打电话、发送电子邮件或以其他方式与特定联系人通信的频率。此权限可让应用删除联系人数据。"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"允许该应用修改您平板电脑上存储的联系人相关数据。此权限允许应用删除联系人数据。"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"允许该应用修改您的 Android TV 设备上存储的联系人相关数据。此权限允许应用删除联系人数据。"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"允许该应用修改您手机上存储的联系人相关数据。此权限允许应用删除联系人数据。"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"读取通话记录"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"此应用可读取您的通话记录。"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"新建/修改/删除通话记录"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"获取额外的位置信息提供程序命令"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"允许该应用使用其他的位置信息提供程序命令。此权限使该应用可以干扰GPS或其他位置信息源的运作。"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"只能在前台获取精确的位置信息"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"此应用只有在前台运行时才能获取您的精确位置信息。您的手机必须支持并开启这些位置信息服务,此应用才能使用这些服务。这可能会增加耗电量。"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"只能在前台获取大概位置(基于网络)"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"此应用只能在前台根据网络来源(例如手机信号塔和 WLAN 网络)获取您的位置信息。您的平板电脑必须支持并开启这些位置信息服务,此应用才能使用这些服务。"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"只要这个应用在前台运行,就可以根据网络来源(例如手机信号塔和 WLAN 网络)获取您的位置信息。您的 Android TV 设备必须支持并开启这些位置信息服务,此应用才能使用这些服务。"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"此应用只能在前台根据网络来源(例如手机信号塔和 WLAN 网络)获取您的位置信息。您的手机必须支持并开启这些位置信息服务,此应用才能使用这些服务。"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"此应用只有在前台运行时才能获取您的精确位置信息。您的设备必须支持并开启位置信息服务,此应用才能使用这些服务。这可能会增加耗电量。"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"只有在前台运行时才能获取大致位置信息"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"此应用只有在前台运行时才能获取您的大致位置信息。您的设备必须支持并开启位置信息服务,此应用才能使用这些服务。"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"在后台使用位置信息"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"如果另外授予大致位置信息或精确位置信息访问权限,该应用便可在后台运行时使用位置信息。"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"此应用不仅在前台运行时可以获取位置信息,在后台运行时也能获取位置信息。"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"更改您的音频设置"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允许该应用修改全局音频设置,例如音量和用于输出的扬声器。"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"录音"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"允许该应用查看平板电脑上的蓝牙配置,以及与配对设备建立连接或接受其连接请求。"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"允许应用查看 Android TV 设备上的蓝牙配置,以及与配对设备建立连接或接受其连接请求。"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"允许该应用查看手机上的蓝牙配置,以及与配对设备建立连接或接受其连接请求。"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"控制近距离通信"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"允许应用与近距离无线通信(NFC)标签、卡和读取器通信。"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"停用屏幕锁定"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"已连接到<xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"点按即可查看文件"</string>
<string name="pin_target" msgid="8036028973110156895">"固定"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"取消固定"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"应用信息"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"正在启动演示模式…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"要在"<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"中更新<xliff:g id="TYPE_0">%1$s</xliff:g>、<xliff:g id="TYPE_1">%2$s</xliff:g>和<xliff:g id="TYPE_2">%3$s</xliff:g>这些内容吗?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"保存"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"不用了"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"以后再说"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"永不"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"更新"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"继续"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"密码"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"开启/关闭分屏"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"锁定屏幕"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"屏幕截图"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"以弹出式窗口形式打开的<xliff:g id="APP_NAME">%1$s</xliff:g>应用。"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>的标题栏。"</string>
</resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index d4d1f7c..8bb32c3 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"工作設定檔管理員應用程式已遺失或損毀。因此,您的工作設定檔和相關資料已刪除。請聯絡您的管理員以取得協助。"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"您的工作設定檔無法再在此裝置上使用"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"密碼輸入錯誤的次數過多"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"管理員已開放裝置供個人使用"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"裝置已受管理"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"您的機構會管理此裝置,並可能會監控網絡流量。輕按即可瞭解詳情。"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"您的裝置將被清除"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"允許應用程式傳送置頂廣播,並在廣播結束後仍然繼續。過度使用會佔用大量記憶體,可能會令 Android TV 減慢運行速度或無法穩定運行。"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"允許應用程式傳送在廣播結束後仍繼續存在的記憶廣播。過度使用可能會促使手機過度使用記憶體,因而拖慢運行速度或造成不穩定。"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"讀取您的通訊錄"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"允許應用程式讀取平板電腦上儲存的聯絡人資料,包括您與個別聯絡人通話、電郵或以其他通訊方式聯絡的頻率。這項權限允許應用程式儲存您的聯絡人資料,而惡意應用程式也可能在您不知情下擅自共用聯絡人資料。"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"允許應用程式讀取儲存在 Android TV 裝置上的聯絡人資料,包括您與特定聯絡人通話、傳送電郵或以其他通訊方式聯絡的頻率。這項權限允許應用程式儲存您的聯絡人資料,而惡意應用程式也可能在您不知情時擅自共用聯絡資料。"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"允許應用程式讀取手機上儲存的聯絡人資料,包括您與個別聯絡人通話、電郵或以其他通訊方式聯絡的頻率。這項權限允許應用程式儲存您的聯絡人資料,而惡意應用程式也可能在您不知情下擅自共用聯絡人資料。"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"允許應用程式讀取儲存在平板電腦上的聯絡人資料。應用程式亦可存取平板電腦上已建立聯絡人的帳戶,其中可能包括已安裝應用程式所建立的帳戶。這項權限允許應用程式儲存您的聯絡人資料,而惡意應用程式也可能在您不知情時擅自共用聯絡資料。"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"允許應用程式讀取儲存在 Android TV 裝置上的聯絡人資料。應用程式亦可存取 Android TV 裝置上已建立聯絡人的帳戶,其中可能包括已安裝應用程式所建立的帳戶。這項權限允許應用程式儲存您的聯絡人資料,而惡意應用程式也可能在您不知情時擅自共用聯絡資料。"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"允許應用程式讀取儲存在手機上的聯絡人資料。應用程式亦可存取手機上已建立聯絡人的帳戶,其中可能包括已安裝應用程式所建立的帳戶。這項權限允許應用程式儲存您的聯絡人資料,而惡意應用程式也可能在您不知情時擅自共用聯絡資料。"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"修改您的通訊錄"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"允許應用程式修改平板電腦上儲存的聯絡人資料,包括您與個別聯絡人通話、電郵或以其他通訊方式聯絡的頻率。這項權限允許應用程式刪除聯絡人資料。"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"允許應用程式修改儲存在 Android TV 裝置上的聯絡人資料,包括您與特定聯絡人通話、傳送電郵或以其他通訊方式聯絡的頻率。這項權限允許應用程式刪除聯絡資料。"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"允許應用程式修改手機上儲存的聯絡人資料,包括您與個別聯絡人通話、電郵或以其他通訊方式聯絡的頻率。這項權限允許應用程式刪除聯絡人資料。"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"允許應用程式修改儲存在平板電腦上的聯絡人資料。這項權限允許應用程式刪除聯絡人資料。"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"允許應用程式修改儲存在 Android TV 裝置上的聯絡人資料。這項權限允許應用程式刪除聯絡人資料。"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"允許應用程式修改儲存在手機上的聯絡人資料。這項權限允許應用程式刪除聯絡人資料。"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"讀取通話記錄"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"此應用程式可以讀取您的通話記錄。"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"寫入通話記錄"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"接收額外的位置提供者指令"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"允許應用程式存取額外的位置提供者指令。這項設定可能會使應用程式干擾 GPS 或其他位置來源的運作。"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"只在前景存取精確位置"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"此應用程式只可在前台運行時獲取您的確實位置資訊。您的手機必須支援並啟用這些定位服務,應用程式方可使用這項功能,但這樣做可能會增加耗電量。"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"只可在前景存取大概位置 (根據網絡定位)"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"此應用程式只能在前景中根據網絡來源 (例如手機訊號發射塔和 Wi-Fi 網絡) 獲取您的位置資訊。您必須在平板電腦上開啟這些定位服務,才能讓此應用程式使用位置資訊。"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"此應用程式只能在前景中根據網絡來源 (例如手機訊號發射塔和 Wi-Fi 網絡) 獲取您的位置資訊。您必須在適用的 Android TV 裝置上開啟這些定位服務,才能讓此應用程式使用位置資訊。"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"此應用程式只能在前景中根據網絡來源 (例如手機訊號發射塔和 Wi-Fi 網絡) 獲取您的位置資訊。您必須在手機上開啟這些定位服務,才能讓此應用程式使用位置資訊。"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"此應用程式只可在前景執行時獲取您的確實位置資訊。您的裝置必須支援並已啟用定位服務,應用程式才能使用此功能。不過,這樣做可能會增加耗電量。"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"只在前景存取大概位置"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"此應用程式只可在前景執行時獲取您的大概位置資訊。您的裝置必須支援並已啟用定位服務,應用程式才能使用此功能。"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"在背景存取位置資訊"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"如果您另外授予概略位置或精確位置的存取權,這個應用程式在背景運行時將可存取位置資訊。"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"此應用程式除了可在前景存取位置資訊外,亦可在背景中存取位置。"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"更改音效設定"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允許應用程式修改全域音頻設定,例如音量和用於輸出的喇叭。"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"錄製音效"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"允許應用程式查看平板電腦的藍牙設定,以及建立和接受與其他配對裝置的連線。"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"允許應用程式查看 Android TV 裝置的藍牙設定,以及建立和接受與其他配對裝置的連線。"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"允許應用程式查看手機的藍牙設定,以及建立和接受與其他配對裝置的連線。"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"控制近距離無線通訊"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"允許應用程式使用近距離無線通訊 (NFC) 標記、卡片及讀取程式進行通訊。"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"停用螢幕上鎖"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"已連線至 <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"輕按即可查看檔案"</string>
<string name="pin_target" msgid="8036028973110156895">"固定"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"取消固定"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"應用程式資料"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"正在開始示範…"</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"要在 "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" 中更新<xliff:g id="TYPE_0">%1$s</xliff:g>、<xliff:g id="TYPE_1">%2$s</xliff:g>和<xliff:g id="TYPE_2">%3$s</xliff:g>嗎?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"儲存"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"不用了,謝謝"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"暫時不要"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"永不"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"更新"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"繼續"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"密碼"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"切換分割螢幕"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"將畫面上鎖"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"螢幕截圖"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」應用程式在彈出式視窗中顯示。"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」的說明列。"</string>
</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 9997b68..8d18a83 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"工作資料夾管理員應用程式遺失或已毀損,因此系統刪除了你的工作資料夾和相關資料。如需協助,請與你的管理員聯絡。"</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"你的工作資料夾已不在這個裝置上"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"密碼輸入錯誤的次數過多"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"管理員將這部裝置開放給個人使用"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"裝置受到管理"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"貴機構會管理這個裝置,且可能監控網路流量。輕觸即可瞭解詳情。"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"你的裝置資料將遭到清除"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"允許應用程式傳送記憶廣播,這類廣播在廣播動作結束後仍繼續存在。請注意,過度使用這項功能可能會使得 Android TV 裝置占用過多記憶體,導致系統的執行速度變慢或穩定性降低。"</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"允許應用程式傳送記憶廣播,這類廣播在廣播動作結束後仍繼續存在。請注意,過度使用此功能可能導致手機使用過多的記憶體,導致手機的執行速度變慢或不穩定。"</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"讀取你的聯絡人"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"允許應用程式讀取平板電腦上儲存的聯絡人資料,包括你與特定聯絡人通話、傳送電子郵件或使用其他通訊方式的互動頻率。這項權限可讓應用程式儲存你的聯絡人資料,惡意應用程式也可能私自共用聯絡人資料。"</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"允許應用程式讀取 Android TV 裝置上儲存的聯絡人資料,包括你與特定使用者通話、傳送電子郵件或使用其他通訊方式的頻率。這項權限可讓應用程式儲存你的聯絡人資料,惡意應用程式也可能在你不知情的情況下洩露你的聯絡人資料。"</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"允許應用程式讀取手機上儲存的聯絡人資料,包括你與特定聯絡人通話、傳送電子郵件或使用其他通訊方式的互動頻率。這項權限可讓應用程式儲存你的聯絡人資料,惡意應用程式也可能私自共用聯絡人資料。"</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"允許應用程式讀取平板電腦上儲存的聯絡人資料。如果你已在平板電腦上的帳戶建立聯絡人,應用程式也將可以存取這些帳戶,當中可能包括你安裝的應用程式所建立的帳戶。這項權限可讓應用程式儲存你的聯絡人資料,惡意應用程式也可能在你不知情的情況下洩露你的聯絡人資料。"</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"允許應用程式讀取 Android TV 裝置上儲存的聯絡人資料。如果你已在 Android TV 裝置上的帳戶建立聯絡人,應用程式也將可以存取這些帳戶,當中可能包括你安裝的應用程式所建立的帳戶。這項權限可讓應用程式儲存你的聯絡人資料,惡意應用程式也可能在你不知情的情況下洩露你的聯絡人資料。"</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"允許應用程式讀取手機上儲存的聯絡人資料。如果你已在手機上的帳戶建立聯絡人,應用程式也將可以存取這些帳戶,當中可能包括你安裝的應用程式所建立的帳戶。這項權限可讓應用程式儲存你的聯絡人資料,惡意應用程式也可能在你不知情的情況下洩露你的聯絡人資料。"</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"修改你的聯絡人"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"允許應用程式讀取平板電腦上儲存的聯絡人資料,包括你與特定聯絡人通話、傳送電子郵件或使用其他通訊方式的互動頻率。這項權限可讓應用程式刪除聯絡人資料。"</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"允許應用程式修改 Android TV 裝置上儲存的聯絡人資料,包括你與特定聯絡人通話、傳送電子郵件或使用其他通訊方式的頻率。這項權限可讓應用程式刪除聯絡人資料。"</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"允許應用程式讀取手機上儲存的聯絡人資料,包括你與特定聯絡人通話、傳送電子郵件或使用其他通訊方式的互動頻率。這項權限可讓應用程式刪除聯絡人資料。"</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"允許應用程式修改平板電腦上儲存的聯絡人資料。這項權限可讓應用程式刪除聯絡人資料。"</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"允許應用程式修改 Android TV 裝置上儲存的聯絡人資料。這項權限可讓應用程式刪除聯絡人資料。"</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"允許應用程式修改手機上儲存的聯絡人資料。這項權限可讓應用程式刪除聯絡人資料。"</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"讀取通話記錄"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"這個應用程式可讀取通話記錄。"</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"寫入通話記錄"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"接收額外的位置提供者指令"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"允許應用程式存取額外位置資訊提供者指令。這項設定可能會造成應用程式干擾 GPS 或其他位置資訊來源的運作。"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"僅可在前景中取得精確位置"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"這個應用程式只能在前景中取得你的確切位置。你必須在手機上開啟這些定位服務,才能讓這個應用程式取得確切位置。請注意,這麼做可能會增加耗電量。"</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"只有在前景執行時才能根據網路取得概略位置"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"只要這個應用程式在前景執行,就可以根據網路來源 (例如基地台和 Wi-Fi 網路) 取得你的位置資訊。如要讓這個應用程式使用定位服務,你必須在平板電腦上開啟這些服務。"</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"只要這個應用程式在前景執行,就可以根據網路來源 (例如基地台和 Wi-Fi 網路) 取得你的位置資訊。如要讓這個應用程式使用定位服務,你必須在 Android TV 裝置上開啟這些服務。"</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"只要這個應用程式在前景執行,就可以根據網路來源 (例如基地台和 Wi-Fi 網路) 取得你的位置資訊。如要讓這個應用程式使用定位服務,你必須在手機上開啟這些服務。"</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"這個應用程式只能在前景執行時取得你的確切位置。你必須在裝置上開啟定位服務,這個應用程式才能取得定位資訊。請注意,這麼做可能會增加耗電量。"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"僅可在前景中取得概略位置"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"這個應用程式只能在前景執行時取得你的概略位置。你必須在裝置上開啟定位服務,這個應用程式才能取得定位資訊。"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"在背景存取位置資訊"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"除了概略位置或精確位置的存取權外,若您另外授予這項存取權,這個應用程式就能在背景執行時存取位置資訊。"</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"這個應用程式在前景和背景執行時,皆可取得位置資訊。"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"變更音訊設定"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允許應用程式修改全域音訊設定,例如音量和用來輸出的喇叭。"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"錄製音訊"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"允許應用程式查看平板電腦的藍牙設定,以及建立和接受與其他配對裝置的連線。"</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"允許應用程式查看 Android TV 裝置的藍牙設定,以及建立及接受與其他配對裝置的連線。"</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"允許應用程式查看手機的藍牙設定,以及建立和接受與其他配對裝置的連線。"</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"控制近距離無線通訊"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"允許應用程式與近距離無線通訊 (NFC) 電子感應標籤、卡片及感應器進行通訊。"</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"停用螢幕鎖定"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"已連線至 <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"輕觸即可查看檔案"</string>
<string name="pin_target" msgid="8036028973110156895">"固定"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"取消固定"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"應用程式資訊"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"正在啟動示範模式..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"要更新 "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" 中的<xliff:g id="TYPE_0">%1$s</xliff:g>、<xliff:g id="TYPE_1">%2$s</xliff:g>和<xliff:g id="TYPE_2">%3$s</xliff:g>嗎?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"儲存"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"不用了,謝謝"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"暫時不要"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"永遠不要"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"更新"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"繼續"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"密碼"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"切換分割畫面模式"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"螢幕鎖定"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"擷取螢幕畫面"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」應用程式顯示在彈出式視窗中。"</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」的說明文字列。"</string>
</resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 5838a93..435211e 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -188,8 +188,7 @@
<string name="work_profile_deleted_details" msgid="3773706828364418016">"Uhlelo lokusebenza lokulawula lephrofayela yomsebenzi kungenzeka alukho noma lonakele. Njengomphumela, iphrofayela yakho yomsebenzi nedatha ehlobene isusiwe. Xhumana nomlawuli wakho ukuze uthole usizo."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"Iphrofayela yakho yomsebenzi ayisatholakali kule divayisi"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Imizamo yamaphasiwedi eminingi kakhulu"</string>
- <!-- no translation found for device_ownership_relinquished (4080886992183195724) -->
- <skip />
+ <string name="device_ownership_relinquished" msgid="4080886992183195724">"Umphathi udedela idivayisi ngokusetshenziswa komuntu siqu"</string>
<string name="network_logging_notification_title" msgid="554983187553845004">"Idivayisi iphethwe"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Inhlangano yakho iphethe le divayisi futhi kungenzeka ingaqaphi ithrafikhi yenethiwekhi. Thephela imininingwane."</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Idivayisi yakho izosulwa"</string>
@@ -381,13 +380,13 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Ivumela uhlelo lokusebenza ukuthi lithumele ukusakaza okunamathelayo, okusalayo ngemuva kokuphela kokusakaza. Ukusetshenziswa ngokweqile kungenza idivayisi yakho ye-Android TV ihambe kancane noma ingazinzi ngokuyenza ukuthi isebenzise imemori eningi kakhulu."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Ivumela uhlelo lokusebenza ukuthumela ukusakaza okunamathelayo, okusalayo emva kokuba ukusakazwa sekuphelile. Ukusebenzisa kakhulu kuhle kwenze ifoni ukuthi ingasheshi noma ingahlali kahle ngokuyibangela ukusebenzisa imemori eningi."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"funda oxhumana nabo"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="3792628955305119168">"Ivumela uhlelo lokusebenza ukufunda idatha mayelana noxhumana nabo abalondolozwe kuthebhulethi yakho, kufaka phakathi nobuningi obushayele, wathumela i-imeyili, noma oxhumene nabo ngezinye izindlela nomuntu oyedwa. Le mvume ivumela izinhlelo zokusebenza ukulondoloza idatha yoxhumana nabo, izinhlelo zokusebenza ezingalungile zingaba idatha yokuxhumana ngaphandle kolwazi lakho."</string>
- <string name="permdesc_readContacts" product="tv" msgid="2387823103274997441">"Ivumela uhlelo lokusebenza ukuthi lifunde idatha mayelana noxhumana nabo abangqinwe kudivayisi ye-Android TV, okufaka imvamisa oshaye ngayo, wathumela ama-imeyili, noma waxhumana ngezinye izindlela nabantu abathile. Le mvume ivumela izinhlelo zokusebenza ukuthi zilondoloze idatha yoxhumana nabo, futhi izinhlelo zokusebenza ezinobungozi zingabelana ngedatha yokuxhumana ngaphandle kolwazi lakho."</string>
- <string name="permdesc_readContacts" product="default" msgid="6938416250821270191">"Ivumela uhlelo lokusebenza ukufunda idatha mayelana noxhumana nabo abalondolozwe efonini yakho, kufaka phakathi nobuningi obushayele, wathumela i-imeyili, noma oxhumene nabo ngezinye izindlela nomuntu oyedwa. Le mvume ivumela izinhlelo zokusebenza ukulondoloza idatha yoxhumana nabo, izinhlelo zokusebenza ezingalungile zingaba idatha yokuxhumana ngaphandle kolwazi lakho."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Ivumela uhlelo lokusebenza ukufunda idatha mayelana noxhumana nabo abalondolozwe kuthebhulethi yakho. Izinhlelo zokusebenza zizophinda zibe nokufinyelela kuma-akhawunti akuthebulethi yakho adale oxhumana nabo. Lokhu kungafaka ama-akhawunti adalwe izinhlelo zokusebenza ozifakile. Le mvume ivumela izinhlelo zokusebenza ukuthi zilondoloze idatha yoxhumana nabo, kanye nezinhlelo zokusebenza ezinobungozi zingabelana ngedatha yoxhumana nabo ngaphandle kolwazi lwakho."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Ivumela uhlelo lokusebenza ukufunda idatha mayelana noxhumana nabo abalondolozwe kudivayisi yakho ye-Android TV. Izinhlelo zokusebenza zizophinda zibe nokufinyelela kuma-akhawunti kudivayisi yakho ye-Android TV adale oxhumana nabo. Lokhu kungafaka ama-akhawunti adalwe izinhlelo zokusebenza ozifakile. Le mvume ivumela izinhlelo zokusebenza ukuthi zilondoloze idatha yoxhumana nabo, kanye nezinhlelo zokusebenza ezinobungozi zingabelana ngedatha yoxhumana nabo ngaphandle kolwazi lwakho."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Ivumela uhlelo lokusebenza ukufunda idatha mayelana noxhumana nabo abalondolozwe efonini yakho. Izinhlelo zokusebenza zizophinda zibe nokufinyelela kuma-akhawunti akufoni yakho adale oxhumana nabo. Lokhu kungafaka ama-akhawunti adalwe izinhlelo zokusebenza ozifakile. Le mvume ivumela izinhlelo zokusebenza ukuthi zilondoloze idatha yoxhumana nabo, kanye nezinhlelo zokusebenza ezinobungozi zingabelana ngedatha yoxhumana nabo ngaphandle kolwazi lwakho."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"shintsha oxhumana nabo"</string>
- <string name="permdesc_writeContacts" product="tablet" msgid="4460252002098005534">"Ivumela uhlelo lokusebenza ukushintsha idatha mayelana noxhumana nabo abalondolozwe kuthebhulethi yakho, kufaka phakathi ubuningi bokushayela, ukuthumela i-imeyili, noma oxhumene nabo ngezinye izindlela. Le mvume ivumela izinhlelo zokusebenza ukususa idatha yoxhumana nabo."</string>
- <string name="permdesc_writeContacts" product="tv" msgid="3870937407268625273">"Ivumela uhlelo lokusebenza ukuguqula idatha emayelana noxhumana nabo abagcinwe kudivayisi yakho ye-Android TV yakho, okufaka imvamisa oshaye ngayo, wathumela ama-imeyili, noma waxhumana ngezinye izindlela noxhumana nabo abathile. Le mvume ivumela izinhlelo zokusebenza ukususa idatha yoxhumana nabo."</string>
- <string name="permdesc_writeContacts" product="default" msgid="4152877294201215490">"Ivumela uhlelo lokusebenza ukushintsha idatha mayelana noxhumana nabo abalondolozwe efonini yakho, kufaka phakathi ubuningi bokushayela, ukuthumela i-imeyili, noma oxhumene nabo ngezinye izindlela. Le mvume ivumela izinhlelo zokusebenza ukususa idatha yoxhumana nabo."</string>
+ <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Ivumela uhlelo lokusebenza ukushintsha idatha mayelana noxhumana nabo abalondolozwe kuthebhulethi yakho. Le mvume ivumela izinhlelo zokusebenza ukususa idatha yoxhumana nabo."</string>
+ <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Ivumela uhlelo lokusebenza ukushintsha idatha mayelana noxhumana nabo abalondolozwe kudivayisi yakho ye-Android. Le mvume ivumela izinhlelo zokusebenza ukususa idatha yoxhumana nabo."</string>
+ <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"Ivumela uhlelo lokusebenza ukushintsha idatha mayelana noxhumana nabo abalondolozwe efonini yakho. Le mvume ivumela izinhlelo zokusebenza ukususa idatha yoxhumana nabo."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"funda irekhodi lamakholi"</string>
<string name="permdesc_readCallLog" msgid="8964770895425873433">"Lolu hlelo lokusebenza lungafunda umlando wakho wekholi."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"bhala irekhodi lamakholi"</string>
@@ -407,13 +406,11 @@
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"finyelela kweminye imiyalo yokunikeza indawo"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Ivumela uhlelo lokusebenza ukufinyelela imiyalo eyengeziwe yabahlinzeki bendawo. Lokhu kungase kuvumele uhlelo lokusebenza ukuthi liphazamisane nomsebenzi we-GPS noma eminye imithombo yendawo."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"finyelela indawo eqondile kuphela phambili"</string>
- <string name="permdesc_accessFineLocation" msgid="3056141052532120237">"Lolu hlelo lokusebenza lungakutholela indawo eqondile kuphela uma liphambili. Lawa masevisi endawo kufanele avulwe futhi atholakale efonini yakho ukuze uhlelo lokusebenza lukwazi ukuwasebenzisa. Lokhu kungakhulisa ukusebenza kwebhethri."</string>
- <string name="permlab_accessCoarseLocation" msgid="8215351553392299056">"finyelela indawo eseduze (esuselwa kunethiwekhi) kuphela ngaphambili"</string>
- <string name="permdesc_accessCoarseLocation" product="tablet" msgid="7479449026750078899">"Lolu hlelo lokusebenza lungathola indawo yakho kusukela kumithombo yenethiwekhi efana nezinqaba zeselula namanethiwekhi e-Wi-Fi, kodwa kuphela uma uhlelo lokusebenza lungaphambili. Lawa masevisi endawo kumele avulwe futhi atholakale kuthebulethi yakho ukuze uhlelo lokusebenza lukwazi ukuwasebenzisa."</string>
- <string name="permdesc_accessCoarseLocation" product="tv" msgid="6994518594789550469">"Lolu hlelo lokusebenza lungathola indawo yakho kusukela kumithombo yenethiwekhi efana nezinqaba zeselula namanethiwekhi e-Wi-Fi, kodwa kuphela uma uhlelo lokusebenza lungaphambili. Lawa masevisi endawo kumele avulwe futhi atholakale kudivayisi yakho ye-Android TV ukuze uhlelo lokusebenza lukwazi ukuwasebenzisa."</string>
- <string name="permdesc_accessCoarseLocation" product="default" msgid="8962998102400124341">"Lolu hlelo lokusebenza lungathola indawo yakho kusukela kumithombo yenethiwekhi efana nezinqaba zeselula kanye namanethiwekhi e-Wi-Fi, kodwa kuphela uma uhlelo lokusebenza lungaphambili. Lawa masevisi endawo kumele avulwe futhi atholakale kufoni yakho ukuze uhlelo lokusebenza lukwazi ukuwasebenzisa."</string>
+ <string name="permdesc_accessFineLocation" msgid="9221079523494157324">"Lolu hlelo lokusebenza lungakutholela indawo eqondile kuphela uma liphambili. Amasevisi endawo kufanele avulwe futhi atholakale kudivayisi yakho ukuze uhlelo lokusebenza lukwazi ukuwasebenzisa. Lokhu kungakhulisa ukusebenza kwebhethri."</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"finyelela indawo enembile kuphela engaphambili"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="4826281078353537786">"Lolu hlelo lokusebenza lungakutholela indawo enembile kuphela uma ingaphambili. Amasevisi endawo kumele avulwe futhi atholakale kudivayisi yakho ukuze uhlelo lokusebenza lukwazi ukuwasebenzisa."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"finyelela kundawo ngemuva"</string>
- <string name="permdesc_accessBackgroundLocation" msgid="6904788108073882096">"Uma lokhu kunikezwa ngokungeziwe ekufinyeleleni okulinganiselwe noma okunembile kwendawo uhlelo lokusebenza lungafinyelela kundawo ngenkathi lusebenza ngemuva."</string>
+ <string name="permdesc_accessBackgroundLocation" msgid="623676842127558197">"Lolu hlelo lokusebenza lungafinyelela indawo ngenkathi isebenza ngasemuva, ngokungeziwe ekufinyeleleni kwendawo yangaphambili."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"shintsha izilungiselelo zakho zomsindo"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ivumela uhlelo lokusebenza ukushintsha izilungiselelo zomsindo we-global njengevolomu nokuthi isiphi isipika esisetshenziselwa okukhiphayo."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"qopha umsindo"</string>
@@ -494,6 +491,10 @@
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Ivumela uhlelo lokusebenza ukubuka ukucushwa kwe-Bluetooth kuthebhulethi, nokwenza futhi nokwamukela uxhumo namadivayisi amatanisiwe."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Ivumela uhlelo lokusebenza ukubuka ukucushwa kwe-Bluetooth kudivayisi ye-Android TV, nokwenza futhi nokwamukela uxhumo namadivayisi abhangqiwe."</string>
<string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Ivumela uhlelo lokusebenza ukubuka ukucushwa kwe-Bluetooth efonini, ukwenza futhi nokwamukela uxhumo namadivayisi amatanisiwe."</string>
+ <!-- no translation found for permlab_preferredPaymentInfo (5274423844767445054) -->
+ <skip />
+ <!-- no translation found for permdesc_preferredPaymentInfo (8583552469807294967) -->
+ <skip />
<string name="permlab_nfc" msgid="1904455246837674977">"lawula Uxhumano Lwenkambu Eseduze"</string>
<string name="permdesc_nfc" msgid="8352737680695296741">"Ivuela uhlelo lokusebenza ukuthi ixhumane ne-Near Field Communication (NFC) amathegi, amakhadi kanye nezinhlelo zokufunda."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"khubaza ukukhiya kwakho iskrini"</string>
@@ -1862,7 +1863,11 @@
<string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"Kuxhumekile ku-<xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string>
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Thepha ukuze ubuke onke amafayela"</string>
<string name="pin_target" msgid="8036028973110156895">"Phina"</string>
+ <!-- no translation found for pin_specific_target (7824671240625957415) -->
+ <skip />
<string name="unpin_target" msgid="3963318576590204447">"Susa ukuphina"</string>
+ <!-- no translation found for unpin_specific_target (3859828252160908146) -->
+ <skip />
<string name="app_info" msgid="6113278084877079851">"Ulwazi lohlelo lokusebenza"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iqalisa i-demo..."</string>
@@ -1905,6 +1910,8 @@
<string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Buyekeza lezi zinto ku-"<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, ne-<xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Londoloza"</string>
<string name="autofill_save_no" msgid="9212826374207023544">"Cha ngiyabonga"</string>
+ <string name="autofill_save_notnow" msgid="2853932672029024195">"Hhayi manje"</string>
+ <string name="autofill_save_never" msgid="6821841919831402526">"Akusoze"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Buyekeza"</string>
<string name="autofill_continue_yes" msgid="7914985605534510385">"Qhubeka"</string>
<string name="autofill_save_type_password" msgid="5624528786144539944">"iphasiwedi"</string>
@@ -2000,5 +2007,5 @@
<string name="accessibility_system_action_toggle_split_screen_label" msgid="6626177163849387748">"Guqula ukuhlukanisa isikrini"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Khiya isikrini"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Isithombe-skrini"</string>
- <string name="accessibility_freeform_caption" msgid="7873194416838321119">"<xliff:g id="APP_NAME">%1$s</xliff:g> uhlelo lokusebenza kuwindi le-Pop-up."</string>
+ <string name="accessibility_freeform_caption" msgid="8377519323496290122">"Ibha yamazwibela we-<xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 166cde0..b17d473 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -298,6 +298,9 @@
<!-- Additional flag from base permission type: this permission can be automatically
granted to the system telephony apps -->
<flag name="telephony" value="0x400000" />
+ <!-- Additional flag from base permission type: this permission can be automatically
+ granted to the system companion device manager service -->
+ <flag name="companion" value="0x800000" />
</attr>
<!-- Flags indicating more context for a permission group. -->
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index b49fe49..29f9f6c 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -23,7 +23,6 @@
<bool name="preferences_prefer_dual_pane">false</bool>
<bool name="show_ongoing_ime_switcher">true</bool>
<bool name="action_bar_expanded_action_views_exclusive">true</bool>
- <bool name="target_honeycomb_needs_options_menu">true</bool>
<!-- Whether or not to use the drawable/lockscreen_notselected and
drawable/lockscreen_selected instead of the generic dots when displaying
the LockPatternView.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bfbd959..9073a02 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2357,15 +2357,22 @@
<!-- Whether to only install system packages on a user if they're whitelisted for that user
type. These are flags and can be freely combined.
- 0 (0b000) - disable whitelist (install all system packages; no logging)
- 1 (0b001) - enforce (only install system packages if they are whitelisted)
- 2 (0b010) - log (log when a non-whitelisted package is run)
- 4 (0b100) - treat any package not mentioned in the whitelist file as implicitly whitelisted
- 8 (0b1000) - ignore OTAs (don't install system packages during OTAs)
+ 0 - disable whitelist (install all system packages; no logging)
+ 1 - enforce (only install system packages if they are whitelisted)
+ 2 - log (log when a non-whitelisted package is run)
+ 4 - any package not mentioned in the whitelist file is implicitly whitelisted on all users
+ 8 - same as 4, but just for the SYSTEM user
+ 16 - ignore OTAs (don't install system packages during OTAs)
+ Common scenarios:
+ - to enable feature (fully enforced) for a complete whitelist: 1
+ - to enable feature for an incomplete whitelist (so use implicit whitelist mode): 5
+ - to enable feature but implicitly whitelist for SYSTEM user to ease local development: 9
+ - to disable feature completely if it had never been enabled: 16
+ - to henceforth disable feature and try to undo its previous effects: 0
Note: This list must be kept current with PACKAGE_WHITELIST_MODE_PROP in
frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java -->
- <integer name="config_userTypePackageWhitelistMode">13</integer> <!-- 0b1101 -->
- <!-- TODO(b/143200798): Change to value 5, i.e. 0b0101, when b/143200798 is resolved. -->
+ <integer name="config_userTypePackageWhitelistMode">29</integer> <!-- 1+4+8+16 -->
+ <!-- TODO(b/143200798): Change to value 13, i.e. 1+4+8, when b/143200798 is resolved. -->
<!-- Whether UI for multi user should be shown -->
<bool name="config_enableMultiUserUI">false</bool>
@@ -2544,6 +2551,28 @@
<string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
>com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
+ <!-- Component name of the activity that shows the usb containment status. -->
+ <string name="config_usbContaminantActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbContaminantActivity</string>
+
+ <!-- Component name of the activity that shows the request for access to a usb device. -->
+ <string name="config_usbPermissionActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbPermissionActivity</string>
+
+ <!-- Component name of the activity that shows more information about a usb accessory. -->
+ <string name="config_usbAccessoryUriActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbAccessoryUriActivity</string>
+
+ <!-- Component name of the activity that confirms the activity to start when a usb device is
+ plugged in. -->
+ <string name="config_usbConfirmActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbConfirmActivity</string>
+
+ <!-- Component name of the activity to select the activity to start when a usb device is plugged
+ in. -->
+ <string name="config_usbResolverActivity" translatable="false"
+ >com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
+
<!-- Name of the dialog that is used to request the user's consent to VPN connection -->
<string name="config_customVpnConfirmDialogComponent" translatable="false"
>com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog</string>
@@ -2630,6 +2659,18 @@
<item>353</item>
</string-array>
+ <!-- Ims supported call barring MMI code -->
+ <string-array translatable="false" name="config_callBarringMMI_for_ims">
+ <item>33</item>
+ <item>331</item>
+ <item>332</item>
+ <item>35</item>
+ <item>351</item>
+ <item>330</item>
+ <item>333</item>
+ <item>353</item>
+ </string-array>
+
<!-- Override the default detection behavior for the framework method
android.view.ViewConfiguration#hasPermanentMenuKey().
Valid settings are:
@@ -2652,11 +2693,6 @@
<!-- Package name for default network scorer app; overridden by product overlays. -->
<string name="config_defaultNetworkScorerPackageName"></string>
- <!-- Feature flag to enable memory efficient task snapshots that are used in recents optimized
- for low memory devices and replace the app transition starting window with the splash
- screen. -->
- <bool name="config_lowRamTaskSnapshotsAndRecents">false</bool>
-
<!-- The amount to scale fullscreen snapshots for Overview and snapshot starting windows. -->
<item name="config_fullTaskSnapshotScale" format="float" type="dimen">1.0</item>
@@ -2667,11 +2703,45 @@
property. If this is false, then the following recents config flags are ignored. -->
<bool name="config_hasRecents">true</bool>
- <!-- Component name for the activity that will be presenting the Recents UI, which will receive special permissions for API related
- to fetching and presenting recent tasks. The default configuration uses Launcehr3QuickStep as default launcher and points to
- the corresponding recents component. When using a different default launcher, change this appropriately or use the default
- systemui implementation: com.android.systemui/.recents.RecentsActivity -->
- <string name="config_recentsComponentName" translatable="false">com.android.launcher3/com.android.quickstep.RecentsActivity</string>
+ <!-- Component name for the activity that will be presenting the Recents UI, which will receive
+ special permissions for API related to fetching and presenting recent tasks. The default
+ configuration uses Launcehr3QuickStep as default launcher and points to the corresponding
+ recents component. When using a different default launcher, change this appropriately or
+ use the default systemui implementation: com.android.systemui/.recents.RecentsActivity -->
+ <string name="config_recentsComponentName" translatable="false"
+ >com.android.launcher3/com.android.quickstep.RecentsActivity</string>
+
+ <!-- SystemUi service component -->
+ <string name="config_systemUIServiceComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.SystemUIService</string>
+
+ <!-- Keyguard component -->
+ <string name="config_keyguardComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
+
+ <!-- Screen record dialog component -->
+ <string name="config_screenRecorderComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.screenrecord.ScreenRecordDialog</string>
+
+ <!-- The component name of a special dock app that merely launches a dream.
+ We don't want to launch this app when docked because it causes an unnecessary
+ activity transition. We just want to start the dream. -->
+ <string name="config_somnambulatorComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.Somnambulator</string>
+
+ <!-- The component name of a special dock app that merely launches a dream.
+ We don't want to launch this app when docked because it causes an unnecessary
+ activity transition. We just want to start the dream.. -->
+ <string name="config_screenshotServiceComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.screenshot.TakeScreenshotService</string>
+
+ <!-- The component notified when there is an error while taking a screenshot. -->
+ <string name="config_screenshotErrorReceiverComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.screenshot.ScreenshotServiceErrorReceiver</string>
+
+ <!-- The component for the activity shown to grant permissions for a slice. -->
+ <string name="config_slicePermissionComponent" translatable="false"
+ >com.android.systemui/com.android.systemui.SlicePermissionActivity</string>
<!-- The minimum number of visible recent tasks to be presented to the user through the
SystemUI. Can be -1 if there is no minimum limit. -->
@@ -2844,10 +2914,6 @@
<!-- Whether to use voip audio mode for ims call -->
<bool name="config_use_voip_mode_for_ims">false</bool>
- <!-- ImsService package name to bind to by default. If none is specified in an overlay, an
- empty string is passed in -->
- <string name="config_ims_package"/>
-
<!-- String array containing numbers that shouldn't be logged. Country-specific. -->
<string-array name="unloggable_phone_numbers" />
@@ -3025,9 +3091,6 @@
<!-- Specifies the maximum burn-in offset vertically. -->
<integer name="config_burnInProtectionMaxVerticalOffset">0</integer>
- <!-- Keyguard component -->
- <string name="config_keyguardComponent" translatable="false">com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
-
<!-- Limit for the number of face templates per user -->
<integer name="config_faceMaxTemplatesPerUser">1</integer>
@@ -3066,7 +3129,9 @@
The path is assumed to be specified in display coordinates with pixel units and in
the display's native orientation, with the origin of the coordinate system at the
- center top of the display.
+ center top of the display. Optionally, you can append either `@left` or `@right` to the
+ end of the path string, in order to change the path origin to either the top left,
+ or top right of the display.
To facilitate writing device-independent emulation overlays, the marker `@dp` can be
appended after the path string to interpret coordinates in dp instead of px units.
@@ -3495,18 +3560,26 @@
set to empty string), a default textclassifier will be loaded in the calling app's process.
See android.view.textclassifier.TextClassificationManager.
-->
+ <!-- TODO(b/144896755) remove the config -->
<string name="config_defaultTextClassifierPackage" translatable="false"></string>
- <!-- A list of the default system textclassifier service package name. Only one of the packages
- will be activated and the fist package is the default system textclassifier service. OS
- only tries to bind the first trusted service and the others can be selected via device
- config. These services must be trusted, as they can be activated without explicit consent
- of the user. Example: "com.android.textclassifier"
+ <!-- A list of supported system textClassifier service package names. Only one of the packages
+ will be activated. The first package in the list is the default system textClassifier
+ service. OS only tries to bind and grant permissions to the first trusted service and the
+ others can be selected via device config. These services must be trusted, as they can be
+ activated without explicit consent of the user. Example: "com.android.textclassifier"
-->
<string-array name="config_defaultTextClassifierPackages" translatable="false">
<item>android.ext.services</item>
</string-array>
+ <!-- The package name for the system companion device manager service.
+ This service must be trusted, as it can be activated without explicit consent of the user.
+ Example: "com.android.companiondevicemanager"
+ See android.companion.CompanionDeviceManager.
+ -->
+ <string name="config_companionDeviceManagerPackage" translatable="false"></string>
+
<!-- The package name for the default wellbeing app.
This package must be trusted, as it has the permissions to control other applications
on the device.
@@ -4100,6 +4173,14 @@
<!-- Which binder services to include in incident reports containing restricted images. -->
<string-array name="config_restrictedImagesServices" translatable="false"/>
+ <!-- List of biometric sensors on the device, in decreasing strength. Consumed by AuthService
+ when registering authenticators with BiometricService. Format must be ID:Modality:Strength,
+ where: IDs are unique per device, Modality as defined in BiometricAuthenticator.java,
+ and Strength as defined in Authenticators.java -->
+ <string-array name="config_biometric_sensors" translatable="false" >
+ <item>0:2:15</item> <!-- ID0:Fingerprint:Strong -->
+ </string-array>
+
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
@@ -4180,9 +4261,30 @@
<!-- Add packages here -->
</string-array>
+ <!-- Whether or not wcg (wide color gamut) should be enabled on this device,
+ we only enabled it while the device has ability of mixed color spaces composition -->
+ <bool name="config_enableWcgMode">false</bool>
+
<!-- When true, enables the whitelisted app to handle bug reports from power menu short press. -->
<bool name="config_bugReportHandlerEnabled">false</bool>
<!-- The package name for the default bug report handler app from power menu short press. This app must be whitelisted. -->
<string name="config_defaultBugReportHandlerApp" translatable="false"></string>
+
+ <!-- The default value used for RawContacts.ACCOUNT_NAME when contacts are inserted without this
+ column set. These contacts are stored locally on the device and will not be removed even
+ if no android.account.Account with this name exists. A null string will be used if the
+ value is left empty. When this is non-empty then config_rawContactsLocalAccountType
+ should also be non-empty. -->
+ <string name="config_rawContactsLocalAccountName" translatable="false"></string>
+
+ <!-- The default value used for RawContacts.ACCOUNT_TYPE when contacts are inserted without this
+ column set. These contacts are stored locally on the device and will not be removed even
+ if no android.account.Account with this type exists. A null string will be used if the
+ value is left empty. When this is non-empty then config_rawContactsLocalAccountName
+ should also be non-empty.-->
+ <string name="config_rawContactsLocalAccountType" translatable="false"></string>
+
+ <!-- Whether or not to use assistant stream volume separately from music volume -->
+ <bool name="config_useAssistantVolume">false</bool>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 9791241..bf7558c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -760,6 +760,7 @@
<dimen name="chooser_edge_margin_normal">24dp</dimen>
<dimen name="chooser_preview_image_font_size">20sp</dimen>
<dimen name="chooser_preview_image_border">1dp</dimen>
+ <dimen name="chooser_preview_image_max_dimen">200dp</dimen>
<dimen name="chooser_preview_width">-1px</dimen>
<dimen name="chooser_target_width">90dp</dimen>
<dimen name="chooser_header_scroll_elevation">4dp</dimen>
@@ -772,4 +773,8 @@
<dimen name="resolver_small_margin">18dp</dimen>
<dimen name="resolver_edge_margin">24dp</dimen>
<dimen name="resolver_elevation">1dp</dimen>
+
+ <!-- Assistant handles -->
+ <dimen name="assist_handle_shadow_radius">2dp</dimen>
+
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 78c4efe..4bbfeaf 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3007,8 +3007,6 @@
</public-group>
<public-group type="drawable" first-id="0x010800b5">
- <!-- @hide @SystemApi -->
- <public name="stat_notify_wifi_in_range" />
</public-group>
<public-group type="style" first-id="0x010302e5">
@@ -3019,11 +3017,11 @@
</public-group>
<public-group type="string" first-id="0x01040025">
- <!-- @hide @SystemApi -->
+ <!-- @hide -->
<public name="notification_channel_network_status" />
- <!-- @hide @SystemApi -->
+ <!-- @hide -->
<public name="notification_channel_network_alerts" />
- <!-- @hide @SystemApi -->
+ <!-- @hide -->
<public name="notification_channel_network_available" />
<!-- @hide @SystemApi -->
<public name="config_defaultCallRedirection" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0f5da39..31becf7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1011,53 +1011,32 @@
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readContacts">read your contacts</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_readContacts" product="tablet">Allows the app to read
- data about your contacts stored on your tablet, including the frequency
- with which you\'ve called, emailed, or communicated in other ways with
- specific individuals. Apps will also have access to the accounts on your
- tablet that have created contacts. This may include accounts created by
- apps you have installed. This permission allows apps to save your contact
- data, and malicious apps may share contact data without your
- knowledge.</string>
+ <string name="permdesc_readContacts" product="tablet">Allows the app to read data about your contacts stored on your tablet.
+ Apps will also have access to the accounts on your tablet that have created contacts.
+ This may include accounts created by apps you have installed.
+ This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_readContacts" product="tv">Allows the app to read
- data about your contacts stored on your Android TV device, including the frequency
- with which you\'ve called, emailed, or communicated in other ways with
- specific individuals. Apps will also have access to the accounts on your
- Android TV device that have created contacts. This may include accounts
- created by apps you have installed. This permission allows apps to save
- your contact data, and malicious apps may share contact data without your
- knowledge.</string>
+ <string name="permdesc_readContacts" product="tv">Allows the app to read data about your contacts stored on your Android TV device.
+ Apps will also have access to the accounts on your Android TV device that have created contacts.
+ This may include accounts created by apps you have installed.
+ This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_readContacts" product="default">Allows the app to
- read data about your contacts stored on your phone, including the
- frequency with which you\'ve called, emailed, or communicated in other ways
- with specific individuals. Apps will also have access to the accounts
- on your phone that have created contacts. This may include accounts
- created by apps you have installed. This permission allows apps to
- save your contact data, and malicious apps may share contact data
- without your knowledge.</string>
+ <string name="permdesc_readContacts" product="default">Allows the app to read data about your contacts stored on your phone.
+ Apps will also have access to the accounts on your phone that have created contacts.
+ This may include accounts created by apps you have installed.
+ This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_writeContacts">modify your contacts</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeContacts" product="tablet">Allows the app to
- modify the data about your contacts stored on your tablet, including the
- frequency with which you\'ve called, emailed, or communicated in other ways
- with specific contacts. This permission allows apps to delete contact
- data.</string>
+ <string name="permdesc_writeContacts" product="tablet">Allows the app to modify the data about your contacts stored on your tablet.
+ This permission allows apps to delete contact data.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeContacts" product="tv">Allows the app to
- modify the data about your contacts stored on your Android TV device, including the
- frequency with which you\'ve called, emailed, or communicated in other ways
- with specific contacts. This permission allows apps to delete contact
- data.</string>
+ <string name="permdesc_writeContacts" product="tv">Allows the app to modify the data about your contacts stored on your Android TV device.
+ This permission allows apps to delete contact data.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeContacts" product="default">Allows the app to
- modify the data about your contacts stored on your phone, including the
- frequency with which you\'ve called, emailed, or communicated in other ways
- with specific contacts. This permission allows apps to delete contact
- data.</string>
+ <string name="permdesc_writeContacts" product="default">Allows the app to modify the data about your contacts stored on your phone.
+ This permission allows apps to delete contact data.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readCallLog">read call log</string>
@@ -1102,6 +1081,7 @@
<string name="permdesc_writeCalendar" product="default">This app can add, remove, or change calendar events on your phone. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the applicatfion to do this. -->
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_accessLocationExtraCommands">access extra location provider commands</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessLocationExtraCommands">Allows the app to access
@@ -1111,21 +1091,17 @@
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_accessFineLocation">access precise location only in the foreground</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_accessFineLocation">This app can get your exact location only when it is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them. This may increase battery consumption.</string>
+ <string name="permdesc_accessFineLocation">This app can get your exact location only when it is in the foreground. Location services must be turned on and available on your device for the app to be able to use them. This may increase battery consumption.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_accessCoarseLocation">access approximate location (network-based) only in the foreground</string>
+ <string name="permlab_accessCoarseLocation">access approximate location only in the foreground</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_accessCoarseLocation" product="tablet">This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when when the app is in the foreground. These location services must be turned on and available on your tablet for the app to be able to use them.</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_accessCoarseLocation" product="tv">This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when when the app is in the foreground. These location services must be turned on and available on your Android TV device for the app to be able to use them.</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_accessCoarseLocation" product="default">This app can get your location based on network sources such as cell towers and Wi-Fi networks, but only when the app is in the foreground. These location services must be turned on and available on your phone for the app to be able to use them.</string>
+ <string name="permdesc_accessCoarseLocation">This app can get your approximate location only when it is in the foreground. Location services must be turned on and available on your device for the app to be able to use them.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_accessBackgroundLocation">access location in the background</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_accessBackgroundLocation">If this is granted additionally to the approximate or precise location access the app can access the location while running in the background.</string>
+ <string name="permdesc_accessBackgroundLocation">This app can access location while running in the background, in addition to foreground location access.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_modifyAudioSettings">change your audio settings</string>
@@ -1369,6 +1345,12 @@
connections with paired devices.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_preferredPaymentInfo">Preferred NFC Payment Service Information</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_preferredPaymentInfo">Allows the app to get preferred nfc payment service information like
+ registered aids and route destination.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_nfc">control Near Field Communication</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_nfc">Allows the app to communicate
@@ -4970,8 +4952,12 @@
<!-- Resolver target actions strings -->
<!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
<string name="pin_target">Pin</string>
+ <!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
+ <string name="pin_specific_target">Pin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string>
<!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
- <string name="unpin_target">Unpin</string>
+ <string name="unpin_target">Unpin </string>
+ <!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
+ <string name="unpin_specific_target">Unpin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string>
<!-- View application info for a target. -->
<string name="app_info">App info</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ee9287c..e82439e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -247,6 +247,8 @@
<java-symbol type="id" name="mic" />
<java-symbol type="id" name="overlay" />
<java-symbol type="id" name="app_ops" />
+ <java-symbol type="id" name="profile_pager" />
+ <java-symbol type="id" name="content_preview_container" />
<java-symbol type="attr" name="actionModeShareDrawable" />
<java-symbol type="attr" name="alertDialogCenterButtons" />
@@ -293,7 +295,6 @@
<java-symbol type="bool" name="config_enableBurnInProtection" />
<java-symbol type="bool" name="config_hotswapCapable" />
<java-symbol type="bool" name="config_mms_content_disposition_support" />
- <java-symbol type="string" name="config_ims_package" />
<java-symbol type="string" name="config_wwan_network_service_package" />
<java-symbol type="string" name="config_wlan_network_service_package" />
<java-symbol type="string" name="config_wwan_network_service_class" />
@@ -356,9 +357,19 @@
<java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
<java-symbol type="dimen" name="config_fullTaskSnapshotScale" />
<java-symbol type="bool" name="config_use16BitTaskSnapshotPixelFormat" />
- <java-symbol type="bool" name="config_lowRamTaskSnapshotsAndRecents" />
<java-symbol type="bool" name="config_hasRecents" />
<java-symbol type="string" name="config_recentsComponentName" />
+ <java-symbol type="string" name="config_systemUIServiceComponent" />
+ <java-symbol type="string" name="config_screenRecorderComponent" />
+ <java-symbol type="string" name="config_somnambulatorComponent" />
+ <java-symbol type="string" name="config_screenshotServiceComponent" />
+ <java-symbol type="string" name="config_screenshotErrorReceiverComponent" />
+ <java-symbol type="string" name="config_slicePermissionComponent" />
+ <java-symbol type="string" name="config_usbContaminantActivity" />
+ <java-symbol type="string" name="config_usbPermissionActivity" />
+ <java-symbol type="string" name="config_usbAccessoryUriActivity" />
+ <java-symbol type="string" name="config_usbConfirmActivity" />
+ <java-symbol type="string" name="config_usbResolverActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
@@ -440,6 +451,7 @@
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
<java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
+ <java-symbol type="xml" name="config_user_types" />
<java-symbol type="integer" name="config_safe_media_volume_index" />
<java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
<java-symbol type="integer" name="config_mobile_mtu" />
@@ -456,6 +468,7 @@
<java-symbol type="string" name="config_deviceSpecificAudioService" />
<java-symbol type="integer" name="config_num_physical_slots" />
<java-symbol type="array" name="config_integrityRuleProviderPackages" />
+ <java-symbol type="bool" name="config_useAssistantVolume" />
<java-symbol type="color" name="tab_indicator_text_v4" />
@@ -1229,6 +1242,7 @@
<java-symbol type="array" name="config_cdma_dun_supported_types" />
<java-symbol type="array" name="config_disabledUntilUsedPreinstalledImes" />
<java-symbol type="array" name="config_callBarringMMI" />
+ <java-symbol type="array" name="config_callBarringMMI_for_ims" />
<java-symbol type="array" name="config_globalActionsList" />
<java-symbol type="array" name="config_telephonyEuiccDeviceCapabilities" />
<java-symbol type="array" name="config_telephonyHardware" />
@@ -1328,6 +1342,7 @@
<java-symbol type="drawable" name="picture_emergency" />
<java-symbol type="drawable" name="platlogo" />
<java-symbol type="drawable" name="stat_notify_sync_error" />
+ <java-symbol type="drawable" name="stat_notify_wifi_in_range" />
<java-symbol type="drawable" name="ic_wifi_signal_0" />
<java-symbol type="drawable" name="ic_wifi_signal_1" />
<java-symbol type="drawable" name="ic_wifi_signal_2" />
@@ -1533,6 +1548,8 @@
<java-symbol type="layout" name="user_switching_dialog" />
<java-symbol type="layout" name="common_tab_settings" />
<java-symbol type="layout" name="notification_material_media_seekbar" />
+ <java-symbol type="layout" name="resolver_list_per_profile" />
+ <java-symbol type="layout" name="chooser_list_per_profile" />
<java-symbol type="anim" name="slide_in_child_bottom" />
<java-symbol type="anim" name="slide_in_right" />
@@ -1657,7 +1674,6 @@
<java-symbol type="bool" name="config_perDisplayFocusEnabled" />
<java-symbol type="bool" name="config_showNavigationBar" />
<java-symbol type="bool" name="config_supportAutoRotation" />
- <java-symbol type="bool" name="target_honeycomb_needs_options_menu" />
<java-symbol type="dimen" name="docked_stack_divider_thickness" />
<java-symbol type="dimen" name="docked_stack_divider_insets" />
<java-symbol type="dimen" name="docked_stack_minimize_thickness" />
@@ -2468,6 +2484,8 @@
<java-symbol type="string" name="face_authenticated_no_confirmation_required" />
<java-symbol type="string" name="face_authenticated_confirmation_required" />
+ <java-symbol type="array" name="config_biometric_sensors" />
+
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_keyguard_ignorelist" />
@@ -2704,6 +2722,7 @@
<java-symbol type="layout" name="chooser_grid_preview_file" />
<java-symbol type="id" name="chooser_row_text_option" />
<java-symbol type="dimen" name="chooser_row_text_option_translate" />
+ <java-symbol type="dimen" name="chooser_preview_image_max_dimen"/>
<java-symbol type="integer" name="config_maxShortcutTargetsPerApp" />
<java-symbol type="layout" name="resolve_grid_item" />
<java-symbol type="id" name="day_picker_view_pager" />
@@ -2941,6 +2960,8 @@
<!-- Resolver target actions -->
<java-symbol type="array" name="resolver_target_actions_pin" />
<java-symbol type="array" name="resolver_target_actions_unpin" />
+ <java-symbol type="string" name="pin_specific_target" />
+ <java-symbol type="string" name="unpin_specific_target" />
<java-symbol type="array" name="non_removable_euicc_slots" />
@@ -3768,4 +3789,16 @@
<java-symbol type="string" name="accessibility_system_action_toggle_split_screen_label" />
<java-symbol type="string" name="accessibility_freeform_caption" />
+
+ <!-- For Wide Color Gamut -->
+ <java-symbol type="bool" name="config_enableWcgMode" />
+
+ <!-- For contacts provider. -->
+ <java-symbol type="string" name="config_rawContactsLocalAccountName" />
+ <java-symbol type="string" name="config_rawContactsLocalAccountType" />
+
+ <!-- Assistant handles -->
+ <java-symbol type="dimen" name="assist_handle_shadow_radius" />
+
+
</resources>
diff --git a/core/res/res/xml/config_user_types.xml b/core/res/res/xml/config_user_types.xml
new file mode 100644
index 0000000..5fd8a95
--- /dev/null
+++ b/core/res/res/xml/config_user_types.xml
@@ -0,0 +1,98 @@
+<?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.
+-->
+
+<!--
+This xml file allows customization of Android multiuser user types.
+It is parsed by frameworks/base/services/core/java/com/android/server/pm/UserTypeFactory.java.
+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++ IMPORTANT NOTE ++++++++++++++++++++++++++++++++++++++++
+Although device customization is possible here, it is largely untested.
+In particular, although this file allows new profile types to be created, and allows modifying the
+number of managed profiles allowed on the device, the consequences of doing so is untested.
+OEMs are advised to test very carefully any significant customization.
+Further support is planned for later releases.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+Pre-defined (AOSP) user types can be customized and new types can be defined. The syntax is the
+same in both cases.
+
+Currently, only profiles (not full or system users) can be freely customized/defined.
+Full users (i.e. non-system, non-profile) users cannot be defined, and the only property of them
+that can be customized are the default-restrictions.
+System users cannot be customized here; their default-restrictions must be set using
+com.android.internal.R.array.config_defaultFirstUserRestrictions.
+
+The following example modifies two AOSP user types (the FULL user android.os.usertype.full.SECONDARY
+and the PROFILE user android.os.usertype.profile.MANAGED) and creates a new PROFILE user type
+(com.example.profilename):
+
+<user-types>
+ <full-type name="android.os.usertype.full.SECONDARY" >
+ <default-restrictions no_sms="true" />
+ </full-type>
+
+ <profile-type
+ name='android.os.usertype.profile.MANAGED'
+ max-allowed-per-parent='2'
+ icon-badge='@android:drawable/ic_corp_icon_badge_case'
+ badge-plain='@android:drawable/ic_corp_badge_case'
+ badge-no-background='@android:drawable/ic_corp_badge_no_background' >
+ <badge-labels>
+ <item res='@android:string/managed_profile_label_badge' />
+ <item res='@android:string/managed_profile_label_badge_2' />
+ </badge-labels>
+ <badge-colors>
+ <item res='@android:color/profile_badge_1' />
+ <item res='@android:color/profile_badge_2' />
+ </badge-colors>
+ <default-restrictions no_sms="true" no_outgoing_calls="true" />
+ </profile-type>
+
+ <profile-type
+ name="com.example.profilename"
+ max-allowed-per-parent="2" />
+</user-types>
+
+Mandatory attributes:
+ name
+
+Supported optional properties (to be used as shown in the example above) are as follows.
+For profile and full users:
+ default-restrictions (with values defined in UserRestrictionUtils.USER_RESTRICTIONS)
+For profile users only:
+ max-allowed-per-parent
+ icon-badge
+ badge-plain
+ badge-no-background
+ badge-labels
+ badge-colors
+
+See UserTypeFactory.java and UserTypeDetails.java for the meaning (and default values) of these
+fields.
+
+Any property that is specified overwrites the AOSP default. For example, if there is no
+default-restrictions element, then the AOSP defaults for that user type will be used; however, if
+there is a default-restrictions element, then the AOSP default restrictions will be completely
+ignored and will instead obey this configuration.
+
+If this file is updated, the properties of any pre-existing user types will be updated too.
+Note, however, that default-restrictions refers to the restrictions applied at the time of user
+creation; therefore, the active restrictions of any pre-existing users will not be updated.
+
+-->
+<user-types>
+</user-types>
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index b906d84..ed613c3 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -176,14 +176,12 @@
mDevice.setPin(mPin);
break;
case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
- mDevice.setPasskey(mPasskey);
break;
case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
case BluetoothDevice.PAIRING_VARIANT_CONSENT:
mDevice.setPairingConfirmation(true);
break;
case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
- mDevice.setRemoteOutOfBandData();
break;
}
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c009f58..caae908 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -24,6 +24,7 @@
],
static_libs: [
"frameworks-base-testutils",
+ "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport
"core-tests-support",
"android-common",
"frameworks-core-util-lib",
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index bbd442d..6c5d548 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -16,7 +16,11 @@
package android.app.activity;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskDescription;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.content.res.Configuration;
@@ -110,7 +114,123 @@
assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD);
}
}
-
+
+ @SmallTest
+ public void testTaskDescriptionCopyFrom() {
+ TaskDescription td1 = new TaskDescription(
+ "test label", // label
+ null, // bitmap
+ 21, // iconRes
+ "dummy file", // iconFilename
+ 0x111111, // colorPrimary
+ 0x222222, // colorBackground
+ 0x333333, // statusBarColor
+ 0x444444, // navigationBarColor
+ true, // ensureStatusBarContrastWhenTransparent
+ true, // ensureNavigationBarContrastWhenTransparent
+ RESIZE_MODE_RESIZEABLE, // resizeMode
+ 10, // minWidth
+ 20 // minHeight
+ );
+
+ TaskDescription td2 = new TaskDescription();
+ // Must overwrite all the fields
+ td2.copyFrom(td1);
+
+ assertEquals(td1.getLabel(), td2.getLabel());
+ assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
+ assertEquals(td1.getIconFilename(), td2.getIconFilename());
+ assertEquals(td1.getIconResource(), td2.getIconResource());
+ assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
+ assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
+ assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
+ assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
+ td2.getEnsureStatusBarContrastWhenTransparent());
+ assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
+ td2.getEnsureNavigationBarContrastWhenTransparent());
+ assertEquals(td1.getResizeMode(), td2.getResizeMode());
+ assertEquals(td1.getMinWidth(), td2.getMinWidth());
+ assertEquals(td1.getMinHeight(), td2.getMinHeight());
+ }
+
+ @SmallTest
+ public void testTaskDescriptionCopyFromPreserveHiddenFields() {
+ TaskDescription td1 = new TaskDescription(
+ "test label", // label
+ null, // bitmap
+ 21, // iconRes
+ "dummy file", // iconFilename
+ 0x111111, // colorPrimary
+ 0x222222, // colorBackground
+ 0x333333, // statusBarColor
+ 0x444444, // navigationBarColor
+ false, // ensureStatusBarContrastWhenTransparent
+ false, // ensureNavigationBarContrastWhenTransparent
+ RESIZE_MODE_UNRESIZEABLE, // resizeMode
+ 10, // minWidth
+ 20 // minHeight
+ );
+
+ TaskDescription td2 = new TaskDescription(
+ "test label2", // label
+ null, // bitmap
+ 212, // iconRes
+ "dummy file2", // iconFilename
+ 0x1111112, // colorPrimary
+ 0x2222222, // colorBackground
+ 0x3333332, // statusBarColor
+ 0x4444442, // navigationBarColor
+ true, // ensureStatusBarContrastWhenTransparent
+ true, // ensureNavigationBarContrastWhenTransparent
+ RESIZE_MODE_RESIZEABLE, // resizeMode
+ 102, // minWidth
+ 202 // minHeight
+ );
+
+ // Must overwrite all public and hidden fields, since other has all fields set.
+ td2.copyFromPreserveHiddenFields(td1);
+
+ assertEquals(td1.getLabel(), td2.getLabel());
+ assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
+ assertEquals(td1.getIconFilename(), td2.getIconFilename());
+ assertEquals(td1.getIconResource(), td2.getIconResource());
+ assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
+ assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
+ assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
+ assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
+ td2.getEnsureStatusBarContrastWhenTransparent());
+ assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
+ td2.getEnsureNavigationBarContrastWhenTransparent());
+ assertEquals(td1.getResizeMode(), td2.getResizeMode());
+ assertEquals(td1.getMinWidth(), td2.getMinWidth());
+ assertEquals(td1.getMinHeight(), td2.getMinHeight());
+
+ TaskDescription td3 = new TaskDescription();
+ // Must overwrite only public fields, and preserve hidden fields.
+ td2.copyFromPreserveHiddenFields(td3);
+
+ // Overwritten fields
+ assertEquals(td3.getLabel(), td2.getLabel());
+ assertEquals(td3.getInMemoryIcon(), td2.getInMemoryIcon());
+ assertEquals(td3.getIconFilename(), td2.getIconFilename());
+ assertEquals(td3.getIconResource(), td2.getIconResource());
+ assertEquals(td3.getPrimaryColor(), td2.getPrimaryColor());
+ assertEquals(td3.getEnsureStatusBarContrastWhenTransparent(),
+ td2.getEnsureStatusBarContrastWhenTransparent());
+ assertEquals(td3.getEnsureNavigationBarContrastWhenTransparent(),
+ td2.getEnsureNavigationBarContrastWhenTransparent());
+
+ // Preserved fields
+ assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
+ assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
+ assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getResizeMode(), td2.getResizeMode());
+ assertEquals(td1.getMinWidth(), td2.getMinWidth());
+ assertEquals(td1.getMinHeight(), td2.getMinHeight());
+ }
+
// If any entries in appear in the list, sanity check them against all running applications
private void checkErrorListSanity(List<ActivityManager.ProcessErrorStateInfo> errList) {
if (errList == null) return;
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index 576ac73..5cb7852 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -239,4 +239,17 @@
verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
.times(1)).onThermalStatusChanged(status);
}
+
+ @Test
+ public void testUserspaceRebootNotSupported_throwsUnsupportedOperationException() {
+ // Can't use assumption framework with AndroidTestCase :(
+ if (mPm.isRebootingUserspaceSupported()) {
+ return;
+ }
+ try {
+ mPm.reboot(PowerManager.REBOOT_USERSPACE);
+ fail("UnsupportedOperationException not thrown");
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 8c1c3b5..ae835e4 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -33,9 +33,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.HashMap;
-import java.util.Map;
-
/** Tests that ensure appropriate settings are backed up. */
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -488,22 +485,20 @@
}
@Test
- public void setProperties() {
- Map<String, String> keyValues = new HashMap<>();
- keyValues.put(KEY, VALUE);
- keyValues.put(KEY2, VALUE2);
+ public void setProperties() throws DeviceConfig.BadConfigException {
+ Properties properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
+ .setString(KEY2, VALUE2).build();
- DeviceConfig.setProperties(new Properties(NAMESPACE, keyValues));
- Properties properties = DeviceConfig.getProperties(NAMESPACE);
+ DeviceConfig.setProperties(properties);
+ properties = DeviceConfig.getProperties(NAMESPACE);
assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
- Map<String, String> newKeyValues = new HashMap<>();
- newKeyValues.put(KEY, VALUE2);
- newKeyValues.put(KEY3, VALUE3);
+ properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE2)
+ .setString(KEY3, VALUE3).build();
- DeviceConfig.setProperties(new Properties(NAMESPACE, newKeyValues));
+ DeviceConfig.setProperties(properties);
properties = DeviceConfig.getProperties(NAMESPACE);
assertThat(properties.getKeyset()).containsExactly(KEY, KEY3);
assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE2);
@@ -514,18 +509,15 @@
}
@Test
- public void setProperties_multipleNamespaces() {
- Map<String, String> keyValues = new HashMap<>();
- keyValues.put(KEY, VALUE);
- keyValues.put(KEY2, VALUE2);
-
- Map<String, String> keyValues2 = new HashMap<>();
- keyValues2.put(KEY2, VALUE);
- keyValues2.put(KEY3, VALUE2);
-
+ public void setProperties_multipleNamespaces() throws DeviceConfig.BadConfigException {
final String namespace2 = "namespace2";
- DeviceConfig.setProperties(new Properties(NAMESPACE, keyValues));
- DeviceConfig.setProperties(new Properties(namespace2, keyValues2));
+ Properties properties1 = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
+ .setString(KEY2, VALUE2).build();
+ Properties properties2 = new Properties.Builder(namespace2).setString(KEY2, VALUE)
+ .setString(KEY3, VALUE2).build();
+
+ DeviceConfig.setProperties(properties1);
+ DeviceConfig.setProperties(properties2);
Properties properties = DeviceConfig.getProperties(NAMESPACE);
assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
@@ -549,6 +541,26 @@
deleteViaContentProvider(namespace2, KEY3);
}
+ @Test
+ public void propertiesBuilder() {
+ boolean booleanValue = true;
+ int intValue = 123;
+ float floatValue = 4.56f;
+ long longValue = -789L;
+ String key4 = "key4";
+ String key5 = "key5";
+
+ Properties properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
+ .setBoolean(KEY2, booleanValue).setInt(KEY3, intValue).setLong(key4, longValue)
+ .setFloat(key5, floatValue).build();
+ assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
+ assertThat(properties.getString(KEY, "defaultValue")).isEqualTo(VALUE);
+ assertThat(properties.getBoolean(KEY2, false)).isEqualTo(booleanValue);
+ assertThat(properties.getInt(KEY3, 0)).isEqualTo(intValue);
+ assertThat(properties.getLong("key4", 0L)).isEqualTo(longValue);
+ assertThat(properties.getFloat("key5", 0f)).isEqualTo(floatValue);
+ }
+
// 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/util/CloseGuardTest.java b/core/tests/coretests/src/android/util/CloseGuardTest.java
new file mode 100644
index 0000000..d86c7b7
--- /dev/null
+++ b/core/tests/coretests/src/android/util/CloseGuardTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.util;
+
+import libcore.dalvik.system.CloseGuardSupport;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+/** Unit tests for {@link android.util.CloseGuard} */
+public class CloseGuardTest {
+
+ @Rule
+ public final TestRule rule = CloseGuardSupport.getRule();
+
+ @Test
+ public void testEnabled_NotOpen() throws Throwable {
+ ResourceOwner owner = new ResourceOwner();
+ assertUnreleasedResources(owner, 0);
+ }
+
+ @Test
+ public void testEnabled_OpenNotClosed() throws Throwable {
+ ResourceOwner owner = new ResourceOwner();
+ owner.open();
+ assertUnreleasedResources(owner, 1);
+ }
+
+ @Test
+ public void testEnabled_OpenThenClosed() throws Throwable {
+ ResourceOwner owner = new ResourceOwner();
+ owner.open();
+ owner.close();
+ assertUnreleasedResources(owner, 0);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testOpen_withNullMethodName_throwsNPE() throws Throwable {
+ CloseGuard closeGuard = new CloseGuard();
+ closeGuard.open(null);
+ }
+
+ private void assertUnreleasedResources(ResourceOwner owner, int expectedCount)
+ throws Throwable {
+ try {
+ CloseGuardSupport.getFinalizerChecker().accept(owner, expectedCount);
+ } finally {
+ // Close the resource so that CloseGuard does not generate a warning for real when it
+ // is actually finalized.
+ owner.close();
+ }
+ }
+
+ /**
+ * A test user of {@link CloseGuard}.
+ */
+ private static class ResourceOwner {
+
+ private final CloseGuard mCloseGuard;
+
+ ResourceOwner() {
+ mCloseGuard = new CloseGuard();
+ }
+
+ public void open() {
+ mCloseGuard.open("close");
+ }
+
+ public void close() {
+ mCloseGuard.close();
+ }
+
+ /**
+ * Make finalize public so that it can be tested directly without relying on garbage
+ * collection to trigger it.
+ */
+ @Override
+ public void finalize() throws Throwable {
+ mCloseGuard.warnIfOpen();
+ super.finalize();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/coretests/src/android/util/LocalLogTest.java
index 6cdcb5e..d4861cd 100644
--- a/core/tests/coretests/src/android/util/LocalLogTest.java
+++ b/core/tests/coretests/src/android/util/LocalLogTest.java
@@ -29,14 +29,24 @@
@LargeTest
public class LocalLogTest extends TestCase {
- public void testA() {
+ public void testA_localTimestamps() {
+ boolean localTimestamps = true;
+ doTestA(localTimestamps);
+ }
+
+ public void testA_nonLocalTimestamps() {
+ boolean localTimestamps = false;
+ doTestA(localTimestamps);
+ }
+
+ private void doTestA(boolean localTimestamps) {
String[] lines = {
- "foo",
- "bar",
- "baz"
+ "foo",
+ "bar",
+ "baz"
};
String[] want = lines;
- testcase(new LocalLog(10), lines, want);
+ testcase(new LocalLog(10, localTimestamps), lines, want);
}
public void testB() {
diff --git a/core/tests/coretests/src/android/util/StatsEventTest.java b/core/tests/coretests/src/android/util/StatsEventTest.java
index 097bada..ac25e27 100644
--- a/core/tests/coretests/src/android/util/StatsEventTest.java
+++ b/core/tests/coretests/src/android/util/StatsEventTest.java
@@ -44,7 +44,7 @@
@Test
public void testNoFields() {
final long minTimestamp = SystemClock.elapsedRealtimeNanos();
- final StatsEvent statsEvent = StatsEvent.newBuilder().build();
+ final StatsEvent statsEvent = StatsEvent.newBuilder().usePooledBuffer().build();
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
final int expectedAtomId = 0;
@@ -99,6 +99,7 @@
.writeBoolean(field2)
.writeInt(field3)
.writeInt(field4)
+ .usePooledBuffer()
.build();
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
@@ -167,6 +168,7 @@
.writeString(field1)
.writeFloat(field2)
.writeByteArray(field3)
+ .usePooledBuffer()
.build();
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
@@ -230,6 +232,7 @@
.setAtomId(expectedAtomId)
.writeAttributionChain(uids, tags)
.writeLong(field2)
+ .usePooledBuffer()
.build();
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
@@ -299,6 +302,7 @@
final StatsEvent statsEvent = StatsEvent.newBuilder()
.setAtomId(expectedAtomId)
.writeKeyValuePairs(intMap, longMap, stringMap, floatMap)
+ .usePooledBuffer()
.build();
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
@@ -392,6 +396,7 @@
.addBooleanAnnotation(field1AnnotationId, field1AnnotationValue)
.writeBoolean(field2)
.addIntAnnotation(field2AnnotationId, field2AnnotationValue)
+ .usePooledBuffer()
.build();
final long maxTimestamp = SystemClock.elapsedRealtimeNanos();
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 1a48260..68d95cd 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -19,11 +19,9 @@
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.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -118,7 +116,8 @@
consumers.put(ITYPE_NAVIGATION_BAR, navConsumer);
mController = new InsetsAnimationControlImpl(consumers,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- () -> mMockTransactionApplier, mMockController);
+ () -> mMockTransactionApplier, mMockController, 10 /* durationMs */,
+ false /* fade */);
}
@Test
@@ -131,9 +130,11 @@
@Test
public void testChangeInsets() {
- mController.changeInsets(Insets.of(0, 30, 40, 0));
+ mController.setInsetsAndAlpha(Insets.of(0, 30, 40, 0), 1f /* alpha */,
+ 0f /* fraction */);
mController.applyChangeInsets(new InsetsState());
assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+ assertEquals(1f, mController.getCurrentAlpha(), 1f - mController.getCurrentAlpha());
ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
verify(mMockTransactionApplier).scheduleApply(captor.capture());
@@ -148,26 +149,43 @@
}
@Test
+ public void testChangeAlphaNoInsets() {
+ Insets initialInsets = mController.getCurrentInsets();
+ mController.setInsetsAndAlpha(null, 0.5f, 0f /* fraction*/);
+ mController.applyChangeInsets(new InsetsState());
+ assertEquals(0.5f, mController.getCurrentAlpha(), 0.5f - mController.getCurrentAlpha());
+ assertEquals(initialInsets, mController.getCurrentInsets());
+ }
+
+ @Test
+ public void testChangeInsetsAndAlpha() {
+ mController.setInsetsAndAlpha(Insets.of(0, 30, 40, 0), 0.5f, 1f);
+ mController.applyChangeInsets(new InsetsState());
+ assertEquals(0.5f, mController.getCurrentAlpha(), 0.5f - mController.getCurrentAlpha());
+ assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+ }
+
+ @Test
public void testFinishing() {
when(mMockController.getState()).thenReturn(mInsetsState);
- mController.finish(navigationBars());
+ mController.finish(true /* shown */);
mController.applyChangeInsets(mInsetsState);
- assertFalse(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible());
+ assertTrue(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible());
assertTrue(mInsetsState.getSource(ITYPE_NAVIGATION_BAR).isVisible());
- assertEquals(Insets.of(0, 0, 100, 0), mController.getCurrentInsets());
- verify(mMockController).notifyFinished(eq(mController), eq(navigationBars()));
+ assertEquals(Insets.of(0, 100, 100, 0), mController.getCurrentInsets());
+ verify(mMockController).notifyFinished(eq(mController), eq(true /* shown */));
}
@Test
public void testCancelled() {
mController.onCancelled();
try {
- mController.changeInsets(Insets.NONE);
+ mController.setInsetsAndAlpha(Insets.NONE, 1f /*alpha */, 0f /* fraction */);
fail("Expected exception to be thrown");
} catch (IllegalStateException ignored) {
}
verify(mMockListener).onCancelled();
- mController.finish(navigationBars());
+ mController.finish(true /* shown */);
}
private void assertPosition(Matrix m, Rect original, Rect transformed) {
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index e4d8279..a89fc1e 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -123,7 +123,7 @@
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(statusBars(), mockListener);
+ mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
verify(mockListener).onReady(any(), anyInt());
mController.onControlsChanged(new InsetsSourceControl[0]);
verify(mockListener).onCancelled();
@@ -135,7 +135,7 @@
mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200));
WindowInsetsAnimationControlListener controlListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(0, controlListener);
+ mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, controlListener);
verify(controlListener).onCancelled();
verify(controlListener, never()).onReady(any(), anyInt());
}
@@ -331,12 +331,13 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
WindowInsetsAnimationControlListener mockListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(statusBars(), mockListener);
+ mController.controlWindowInsetsAnimation(statusBars(), 0 /* durationMs */,
+ mockListener);
ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
- controllerCaptor.getValue().finish(0 /* shownTypes */);
+ controllerCaptor.getValue().finish(false /* shown */);
});
waitUntilNextFrame();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 9a57847..cf5d079 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,13 +16,25 @@
package android.view;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+
import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets.Side;
+import android.view.WindowInsets.Type;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -55,6 +67,8 @@
@Test
public void negativeInsets_areSetToZero() throws Exception {
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
mViewRootImpl.getAttachInfo().getContentInsets().set(-10, -20, -30 , -40);
mViewRootImpl.getAttachInfo().getStableInsets().set(-10, -20, -30 , -40);
final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
@@ -65,6 +79,8 @@
@Test
public void negativeInsets_areSetToZero_positiveAreLeftAsIs() throws Exception {
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
mViewRootImpl.getAttachInfo().getContentInsets().set(-10, 20, -30 , 40);
mViewRootImpl.getAttachInfo().getStableInsets().set(10, -20, 30 , -40);
final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
@@ -75,6 +91,8 @@
@Test
public void positiveInsets_areLeftAsIs() throws Exception {
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
mViewRootImpl.getAttachInfo().getContentInsets().set(10, 20, 30 , 40);
mViewRootImpl.getAttachInfo().getStableInsets().set(10, 20, 30 , 40);
final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
@@ -83,6 +101,80 @@
assertThat(insets.getStableInsets(), equalTo(Insets.of(10, 20, 30, 40)));
}
+ @Test
+ public void adjustLayoutParamsForInsets_layoutFullscreen() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
+ attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+
+ assertEquals(0, attrs.getFitWindowInsetsTypes() & Type.statusBars());
+ }
+
+ @Test
+ public void adjustLayoutParamsForInsets_layoutInScreen() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
+ attrs.flags = FLAG_LAYOUT_IN_SCREEN;
+ ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+
+ assertEquals(0, attrs.getFitWindowInsetsTypes() & Type.statusBars());
+ }
+
+ @Test
+ public void adjustLayoutParamsForInsets_layoutHideNavigation() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
+ attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+
+ assertEquals(0, attrs.getFitWindowInsetsTypes() & Type.systemBars());
+ }
+
+ @Test
+ public void adjustLayoutParamsForInsets_toast() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_TOAST);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+
+ assertEquals(Type.systemBars(), attrs.getFitWindowInsetsTypes() & Type.systemBars());
+ assertEquals(true, attrs.getFitIgnoreVisibility());
+ }
+
+ @Test
+ public void adjustLayoutParamsForInsets_systemAlert() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_SYSTEM_ALERT);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+
+ assertEquals(Type.systemBars(), attrs.getFitWindowInsetsTypes() & Type.systemBars());
+ assertEquals(true, attrs.getFitIgnoreVisibility());
+ }
+
+ @Test
+ public void adjustLayoutParamsForInsets_noAdjust() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
+ final int types = Type.all();
+ final int sides = Side.TOP | Side.LEFT;
+ final boolean fitMaxInsets = true;
+ attrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ attrs.setFitWindowInsetsTypes(types);
+ attrs.setFitWindowInsetsSides(sides);
+ attrs.setFitIgnoreVisibility(fitMaxInsets);
+ ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
+
+ assertEquals(types, attrs.getFitWindowInsetsTypes());
+ assertEquals(sides, attrs.getFitWindowInsetsSides());
+ assertEquals(fitMaxInsets, attrs.getFitIgnoreVisibility());
+ }
+
private static class ViewRootImplAccessor {
private final ViewRootImpl mViewRootImpl;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 6bce651..0d5db6d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -436,6 +436,22 @@
}
@Test
+ public void windowsChangedWithWindowsChangeA11yFocusedEvent_dontClearCache() {
+ AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1);
+ mAccessibilityCache.add(nodeInfo);
+ AccessibilityEvent event = new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+ event.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED);
+ mAccessibilityCache.onAccessibilityEvent(event);
+ AccessibilityNodeInfo cachedNode = mAccessibilityCache.getNode(WINDOW_ID_1,
+ nodeInfo.getSourceNodeId());
+ try {
+ assertNotNull(cachedNode);
+ } finally {
+ nodeInfo.recycle();
+ }
+ }
+
+ @Test
public void subTreeChangeEvent_clearsNodeAndChild() {
AccessibilityEvent event = AccessibilityEvent
.obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
diff --git a/core/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java b/core/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java
deleted file mode 100644
index d54ce51..0000000
--- a/core/tests/coretests/src/android/view/textclassifier/ConfigParserTest.java
+++ /dev/null
@@ -1,138 +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 android.view.textclassifier;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.provider.DeviceConfig;
-import android.support.test.uiautomator.UiDevice;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.function.Supplier;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ConfigParserTest {
- private static final Supplier<String> SETTINGS =
- () -> "int=42,float=12.3,boolean=true,string=abc";
- private static final String CLEAR_DEVICE_CONFIG_KEY_CMD =
- "device_config delete " + DeviceConfig.NAMESPACE_TEXTCLASSIFIER;
- private static final String[] DEVICE_CONFIG_KEYS = new String[]{
- "boolean",
- "string",
- "int",
- "float"
- };
-
- private ConfigParser mConfigParser;
-
- @Before
- public void setup() throws IOException {
- mConfigParser = new ConfigParser(SETTINGS);
- clearDeviceConfig();
- }
-
- @After
- public void tearDown() throws IOException {
- clearDeviceConfig();
- }
-
- @Test
- public void getBoolean_deviceConfig() {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- "boolean",
- "false",
- false);
- boolean value = mConfigParser.getBoolean("boolean", true);
- assertThat(value).isFalse();
- }
-
- @Test
- public void getBoolean_settings() {
- boolean value = mConfigParser.getBoolean(
- "boolean",
- false);
- assertThat(value).isTrue();
- }
-
- @Test
- public void getInt_deviceConfig() {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- "int",
- "1",
- false);
- int value = mConfigParser.getInt("int", 0);
- assertThat(value).isEqualTo(1);
- }
-
- @Test
- public void getInt_settings() {
- int value = mConfigParser.getInt("int", 0);
- assertThat(value).isEqualTo(42);
- }
-
- @Test
- public void getFloat_deviceConfig() {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- "float",
- "3.14",
- false);
- float value = mConfigParser.getFloat("float", 0);
- assertThat(value).isWithin(0.0001f).of(3.14f);
- }
-
- @Test
- public void getFloat_settings() {
- float value = mConfigParser.getFloat("float", 0);
- assertThat(value).isWithin(0.0001f).of(12.3f);
- }
-
- @Test
- public void getString_deviceConfig() {
- DeviceConfig.setProperty(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- "string",
- "hello",
- false);
- String value = mConfigParser.getString("string", "");
- assertThat(value).isEqualTo("hello");
- }
-
- @Test
- public void getString_settings() {
- String value = mConfigParser.getString("string", "");
- assertThat(value).isEqualTo("abc");
- }
-
- private static void clearDeviceConfig() throws IOException {
- UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- for (String key : DEVICE_CONFIG_KEYS) {
- uiDevice.executeShellCommand(CLEAR_DEVICE_CONFIG_KEY_CMD + " " + key);
- }
- }
-}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
index 64fb141..82fa73f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
@@ -16,230 +16,141 @@
package android.view.textclassifier;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import android.provider.DeviceConfig;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.Assert;
+import com.google.common.primitives.Floats;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TextClassificationConstantsTest {
-
private static final float EPSILON = 0.0001f;
@Test
- public void testLoadFromString() {
- final String s = "local_textclassifier_enabled=true,"
- + "system_textclassifier_enabled=true,"
- + "model_dark_launch_enabled=true,"
- + "smart_selection_enabled=true,"
- + "smart_text_share_enabled=true,"
- + "smart_linkify_enabled=true,"
- + "smart_select_animation_enabled=true,"
- + "suggest_selection_max_range_length=10,"
- + "classify_text_max_range_length=11,"
- + "generate_links_max_text_length=12,"
- + "generate_links_log_sample_rate=13,"
- + "entity_list_default=phone,"
- + "entity_list_not_editable=address:flight,"
- + "entity_list_editable=date:datetime,"
- + "in_app_conversation_action_types_default=text_reply,"
- + "notification_conversation_action_types_default=send_email:call_phone,"
- + "lang_id_threshold_override=0.3,"
- + "lang_id_context_settings=10:1:0.5,"
- + "detect_language_from_text_enabled=true,"
- + "template_intent_factory_enabled=true,"
- + "translate_in_classification_enabled=true";
- final TextClassificationConstants constants = new TextClassificationConstants(() -> s);
+ public void testLoadFromDeviceConfig_booleanValue() throws Exception {
+ // Saves config original value.
+ final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED);
- assertWithMessage("local_textclassifier_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isTrue();
- assertWithMessage("system_textclassifier_enabled")
- .that(constants.isSystemTextClassifierEnabled()).isTrue();
- assertWithMessage("model_dark_launch_enabled")
- .that(constants.isModelDarkLaunchEnabled()).isTrue();
- assertWithMessage("smart_selection_enabled")
- .that(constants.isSmartSelectionEnabled()).isTrue();
- assertWithMessage("smart_text_share_enabled")
- .that(constants.isSmartTextShareEnabled()).isTrue();
- assertWithMessage("smart_linkify_enabled")
- .that(constants.isSmartLinkifyEnabled()).isTrue();
- assertWithMessage("smart_select_animation_enabled")
- .that(constants.isSmartSelectionAnimationEnabled()).isTrue();
- assertWithMessage("suggest_selection_max_range_length")
- .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10);
- assertWithMessage("classify_text_max_range_length")
- .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(11);
- assertWithMessage("generate_links_max_text_length")
- .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(12);
- assertWithMessage("generate_links_log_sample_rate")
- .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(13);
- assertWithMessage("entity_list_default")
- .that(constants.getEntityListDefault())
- .containsExactly("phone");
- assertWithMessage("entity_list_not_editable")
- .that(constants.getEntityListNotEditable())
- .containsExactly("address", "flight");
- assertWithMessage("entity_list_editable")
- .that(constants.getEntityListEditable())
- .containsExactly("date", "datetime");
- assertWithMessage("in_app_conversation_action_types_default")
- .that(constants.getInAppConversationActionTypes())
- .containsExactly("text_reply");
- assertWithMessage("notification_conversation_action_types_default")
- .that(constants.getNotificationConversationActionTypes())
- .containsExactly("send_email", "call_phone");
- assertWithMessage("lang_id_threshold_override")
- .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(0.3f);
- Assert.assertArrayEquals("lang_id_context_settings",
- constants.getLangIdContextSettings(), new float[]{10, 1, 0.5f}, EPSILON);
- assertWithMessage("detect_language_from_text_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isTrue();
- assertWithMessage("template_intent_factory_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isTrue();
- assertWithMessage("translate_in_classification_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isTrue();
+ final TextClassificationConstants constants = new TextClassificationConstants();
+ try {
+ // Sets and checks different value.
+ setDeviceConfig(TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED, "false");
+ assertWithMessage(TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED)
+ .that(constants.isLocalTextClassifierEnabled()).isFalse();
+ } finally {
+ // Restores config original value.
+ setDeviceConfig(TextClassificationConstants.LOCAL_TEXT_CLASSIFIER_ENABLED,
+ originalValue);
+ }
}
@Test
- public void testLoadFromString_differentValues() {
- final String testTextClassifier = "com.example.textclassifier";
- final String s = "local_textclassifier_enabled=false,"
- + "system_textclassifier_enabled=false,"
- + "model_dark_launch_enabled=false,"
- + "smart_selection_enabled=false,"
- + "smart_text_share_enabled=false,"
- + "smart_linkify_enabled=false,"
- + "smart_select_animation_enabled=false,"
- + "suggest_selection_max_range_length=8,"
- + "classify_text_max_range_length=7,"
- + "generate_links_max_text_length=6,"
- + "generate_links_log_sample_rate=5,"
- + "entity_list_default=email:url,"
- + "entity_list_not_editable=date,"
- + "entity_list_editable=flight,"
- + "in_app_conversation_action_types_default=view_map:track_flight,"
- + "notification_conversation_action_types_default=share_location,"
- + "lang_id_threshold_override=2,"
- + "lang_id_context_settings=30:0.5:0.3,"
- + "detect_language_from_text_enabled=false,"
- + "template_intent_factory_enabled=false,"
- + "textclassifier_service_package_override=" + testTextClassifier + ","
- + "translate_in_classification_enabled=false";
- final TextClassificationConstants constants = new TextClassificationConstants(() -> s);
+ public void testLoadFromDeviceConfig_IntValue() throws Exception {
+ // Saves config original value.
+ final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH);
- assertWithMessage("local_textclassifier_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isFalse();
- assertWithMessage("system_textclassifier_enabled")
- .that(constants.isSystemTextClassifierEnabled()).isFalse();
- assertWithMessage("model_dark_launch_enabled")
- .that(constants.isModelDarkLaunchEnabled()).isFalse();
- assertWithMessage("smart_selection_enabled")
- .that(constants.isSmartSelectionEnabled()).isFalse();
- assertWithMessage("smart_text_share_enabled")
- .that(constants.isSmartTextShareEnabled()).isFalse();
- assertWithMessage("smart_linkify_enabled")
- .that(constants.isSmartLinkifyEnabled()).isFalse();
- assertWithMessage("smart_select_animation_enabled")
- .that(constants.isSmartSelectionAnimationEnabled()).isFalse();
- assertWithMessage("suggest_selection_max_range_length")
- .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(8);
- assertWithMessage("classify_text_max_range_length")
- .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(7);
- assertWithMessage("generate_links_max_text_length")
- .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(6);
- assertWithMessage("generate_links_log_sample_rate")
- .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(5);
- assertWithMessage("entity_list_default")
- .that(constants.getEntityListDefault())
- .containsExactly("email", "url");
- assertWithMessage("entity_list_not_editable")
- .that(constants.getEntityListNotEditable())
- .containsExactly("date");
- assertWithMessage("entity_list_editable")
- .that(constants.getEntityListEditable())
- .containsExactly("flight");
- assertWithMessage("in_app_conversation_action_types_default")
- .that(constants.getInAppConversationActionTypes())
- .containsExactly("view_map", "track_flight");
- assertWithMessage("notification_conversation_action_types_default")
- .that(constants.getNotificationConversationActionTypes())
- .containsExactly("share_location");
- assertWithMessage("lang_id_threshold_override")
- .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(2f);
- Assert.assertArrayEquals("lang_id_context_settings",
- constants.getLangIdContextSettings(), new float[]{30, 0.5f, 0.3f}, EPSILON);
- assertWithMessage("detect_language_from_text_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isFalse();
- assertWithMessage("template_intent_factory_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isFalse();
- assertWithMessage("translate_in_classification_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isFalse();
- assertWithMessage("textclassifier_service_package_override")
- .that(constants.getTextClassifierServiceName()).isEqualTo(
- testTextClassifier);
+ final TextClassificationConstants constants = new TextClassificationConstants();
+ try {
+ // Sets and checks different value.
+ setDeviceConfig(TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH, "8");
+ assertWithMessage(TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH)
+ .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(8);
+ } finally {
+ // Restores config original value.
+ setDeviceConfig(TextClassificationConstants.SUGGEST_SELECTION_MAX_RANGE_LENGTH,
+ originalValue);
+ }
}
@Test
- public void testLoadFromString_defaultValues() {
- final TextClassificationConstants constants = new TextClassificationConstants(() -> "");
+ public void testLoadFromDeviceConfig_StringValue() throws Exception {
+ // Saves config original value.
+ final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ TextClassificationConstants.TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE);
- assertWithMessage("local_textclassifier_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isTrue();
- assertWithMessage("system_textclassifier_enabled")
- .that(constants.isSystemTextClassifierEnabled()).isTrue();
- assertWithMessage("model_dark_launch_enabled")
- .that(constants.isModelDarkLaunchEnabled()).isFalse();
- assertWithMessage("smart_selection_enabled")
- .that(constants.isSmartSelectionEnabled()).isTrue();
- assertWithMessage("smart_text_share_enabled")
- .that(constants.isSmartTextShareEnabled()).isTrue();
- assertWithMessage("smart_linkify_enabled")
- .that(constants.isSmartLinkifyEnabled()).isTrue();
- assertWithMessage("smart_select_animation_enabled")
- .that(constants.isSmartSelectionAnimationEnabled()).isTrue();
- assertWithMessage("suggest_selection_max_range_length")
- .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10 * 1000);
- assertWithMessage("classify_text_max_range_length")
- .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(10 * 1000);
- assertWithMessage("generate_links_max_text_length")
- .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(100 * 1000);
- assertWithMessage("generate_links_log_sample_rate")
- .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(100);
- assertWithMessage("entity_list_default")
- .that(constants.getEntityListDefault())
- .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
- assertWithMessage("entity_list_not_editable")
- .that(constants.getEntityListNotEditable())
- .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
- assertWithMessage("entity_list_editable")
- .that(constants.getEntityListEditable())
- .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
- assertWithMessage("in_app_conversation_action_types_default")
- .that(constants.getInAppConversationActionTypes())
- .containsExactly("text_reply", "create_reminder", "call_phone", "open_url",
- "send_email", "send_sms", "track_flight", "view_calendar", "view_map",
- "add_contact", "copy");
- assertWithMessage("notification_conversation_action_types_default")
- .that(constants.getNotificationConversationActionTypes())
- .containsExactly("text_reply", "create_reminder", "call_phone", "open_url",
- "send_email", "send_sms", "track_flight", "view_calendar", "view_map",
- "add_contact", "copy");
- assertWithMessage("lang_id_threshold_override")
- .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(-1f);
- Assert.assertArrayEquals("lang_id_context_settings",
- constants.getLangIdContextSettings(), new float[]{20, 1, 0.4f}, EPSILON);
- assertWithMessage("detect_language_from_text_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isTrue();
- assertWithMessage("template_intent_factory_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isTrue();
- assertWithMessage("translate_in_classification_enabled")
- .that(constants.isLocalTextClassifierEnabled()).isTrue();
- assertWithMessage("textclassifier_service_package_override")
- .that(constants.getTextClassifierServiceName()).isNull();
+ final TextClassificationConstants constants = new TextClassificationConstants();
+ try {
+ // Sets and checks different value.
+ final String testTextClassifier = "com.example.textclassifier";
+ setDeviceConfig(TextClassificationConstants.TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE,
+ testTextClassifier);
+ assertWithMessage(TextClassificationConstants.TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE)
+ .that(constants.getTextClassifierServicePackageOverride()).isEqualTo(
+ testTextClassifier);
+ } finally {
+ // Restores config original value.
+ setDeviceConfig(TextClassificationConstants.TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE,
+ originalValue);
+ }
+ }
+
+ @Test
+ public void testLoadFromDeviceConfig_FloatValue() throws Exception {
+ // Saves config original value.
+ final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ TextClassificationConstants.LANG_ID_THRESHOLD_OVERRIDE);
+
+ final TextClassificationConstants constants = new TextClassificationConstants();
+ try {
+ // Sets and checks different value.
+ setDeviceConfig(TextClassificationConstants.LANG_ID_THRESHOLD_OVERRIDE, "2");
+ assertWithMessage(TextClassificationConstants.LANG_ID_THRESHOLD_OVERRIDE)
+ .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(2f);
+ } finally {
+ // Restores config original value.
+ setDeviceConfig(TextClassificationConstants.LANG_ID_THRESHOLD_OVERRIDE, originalValue);
+ }
+ }
+
+ @Test
+ public void testLoadFromDeviceConfig_StringList() throws Exception {
+ // Saves config original value.
+ final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ TextClassificationConstants.ENTITY_LIST_DEFAULT);
+
+ final TextClassificationConstants constants = new TextClassificationConstants();
+ try {
+ // Sets and checks different value.
+ setDeviceConfig(TextClassificationConstants.ENTITY_LIST_DEFAULT, "email:url");
+ assertWithMessage(TextClassificationConstants.ENTITY_LIST_DEFAULT)
+ .that(constants.getEntityListDefault())
+ .containsExactly("email", "url");
+ } finally {
+ // Restores config original value.
+ setDeviceConfig(TextClassificationConstants.ENTITY_LIST_DEFAULT, originalValue);
+ }
+ }
+
+ @Test
+ public void testLoadFromDeviceConfig_FloatList() throws Exception {
+ // Saves config original value.
+ final String originalValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ TextClassificationConstants.LANG_ID_CONTEXT_SETTINGS);
+
+ final TextClassificationConstants constants = new TextClassificationConstants();
+ try {
+ // Sets and checks different value.
+ setDeviceConfig(TextClassificationConstants.LANG_ID_CONTEXT_SETTINGS, "30:0.5:0.3");
+ assertThat(Floats.asList(constants.getLangIdContextSettings())).containsExactly(30f,
+ 0.5f, 0.3f).inOrder();
+ } finally {
+ // Restores config original value.
+ setDeviceConfig(TextClassificationConstants.LANG_ID_CONTEXT_SETTINGS, originalValue);
+ }
+ }
+
+ private void setDeviceConfig(String key, String value) {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key,
+ value, /* makeDefault */ false);
}
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index b3f2bbe..8faf790 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -78,7 +78,7 @@
TextClassifier fallback = TextClassifier.NO_OP;
TextClassifier classifier = new TextClassifierImpl(
- fakeContext, new TextClassificationConstants(() -> null), fallback);
+ fakeContext, new TextClassificationConstants(), fallback);
String text = "Contact me at +12122537077";
String classifiedText = "+12122537077";
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index deb0f18..2304ba6 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -69,7 +69,7 @@
public String mTextClassifierType;
private static final TextClassificationConstants TC_CONSTANTS =
- new TextClassificationConstants(() -> "");
+ new TextClassificationConstants();
private static final LocaleList LOCALES = LocaleList.forLanguageTags("en-US");
private static final String NO_TYPE = null;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
index b9cc8f4..ec5813f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
@@ -29,6 +29,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -77,6 +80,7 @@
final TextLinks result = TextLinks.CREATOR.createFromParcel(parcel);
final List<TextLinks.TextLink> resultList = new ArrayList<>(result.getLinks());
+ assertEquals(fullText, result.getText());
assertEquals(2, resultList.size());
assertEquals(0, resultList.get(0).getStart());
assertEquals(4, resultList.get(0).getEnd());
@@ -103,10 +107,13 @@
Arrays.asList(TextClassifier.HINT_TEXT_IS_EDITABLE),
Arrays.asList("a", "b", "c"),
Arrays.asList("b"));
+ final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1000L),
+ ZoneId.of("UTC"));
final TextLinks.Request reference = new TextLinks.Request.Builder("text")
.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY))
.setEntityConfig(entityConfig)
.setExtras(BUNDLE)
+ .setReferenceTime(referenceTime)
.build();
reference.setCallingPackageName(packageName);
@@ -124,5 +131,6 @@
result.getEntityConfig().resolveEntityListModifications(Collections.emptyList()));
assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
assertEquals(packageName, result.getCallingPackageName());
+ assertEquals(referenceTime, result.getReferenceTime());
}
}
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
new file mode 100644
index 0000000..b4f2c91
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.widget;
+
+import static android.widget.espresso.TextViewActions.dragOnText;
+import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.replaceText;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import com.google.common.base.Strings;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class EditorCursorDragTest {
+ @Rule
+ public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
+ TextViewActivity.class);
+
+ private boolean mOriginalFlagValue;
+ private Instrumentation mInstrumentation;
+ private Activity mActivity;
+
+ @Before
+ public void before() throws Throwable {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+ mOriginalFlagValue = Editor.FLAG_ENABLE_CURSOR_DRAG;
+ Editor.FLAG_ENABLE_CURSOR_DRAG = true;
+ }
+ @After
+ public void after() throws Throwable {
+ Editor.FLAG_ENABLE_CURSOR_DRAG = mOriginalFlagValue;
+ }
+
+ @Test
+ public void testCursorDrag_horizontal_whenTextViewContentsFitOnScreen() throws Throwable {
+ String text = "Hello world!";
+ 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.
+ 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.
+ 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";
+ 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.
+ 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.
+ 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_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");
+ 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.
+ 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")));
+
+ // 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")));
+
+ // 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")));
+ }
+
+ @Test
+ public void testCursorDrag_vertical_whenTextViewContentsFitOnScreen() throws Throwable {
+ String text = "012first\n\n" + Strings.repeat("012345\n\n", 10) + "012last";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ // 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));
+ }
+
+ @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";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ int initialCursorPosition = 0;
+ 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")));
+
+ // 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")));
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 6d50e3a..6adb1b8 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -16,6 +16,9 @@
package android.widget;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -50,19 +53,19 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
// 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);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
// 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);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
}
@Test
@@ -71,19 +74,19 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
// 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);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
mTouchState.update(event3, mConfig);
- assertTap(mTouchState, 22f, 33f, 20f, 30f,
+ assertMultiTap(mTouchState, 22f, 33f, 20f, 30f,
MultiTapStatus.DOUBLE_TAP, true);
}
@@ -93,26 +96,26 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
// 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);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
MotionEvent event3 = downEvent(event3Time, event3Time, 200f, 300f);
mTouchState.update(event3, mConfig);
- assertTap(mTouchState, 200f, 300f, 20f, 30f,
+ assertMultiTap(mTouchState, 200f, 300f, 20f, 30f,
MultiTapStatus.DOUBLE_TAP, false);
// Simulate an ACTION_UP event.
long event4Time = 1003;
MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f);
mTouchState.update(event4, mConfig);
- assertTap(mTouchState, 200f, 300f, 200f, 300f,
+ assertMultiTap(mTouchState, 200f, 300f, 200f, 300f,
MultiTapStatus.DOUBLE_TAP, false);
}
@@ -123,21 +126,21 @@
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
event1.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
// 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);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
MotionEvent event3 = downEvent(event3Time, event3Time, 21f, 31f);
event3.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event3, mConfig);
- assertTap(mTouchState, 21f, 31f, 20f, 30f,
+ assertMultiTap(mTouchState, 21f, 31f, 20f, 30f,
MultiTapStatus.DOUBLE_TAP, true);
// Simulate an ACTION_UP event.
@@ -145,7 +148,7 @@
MotionEvent event4 = upEvent(event3Time, event4Time, 21f, 31f);
event4.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event4, mConfig);
- assertTap(mTouchState, 21f, 31f, 21f, 31f,
+ assertMultiTap(mTouchState, 21f, 31f, 21f, 31f,
MultiTapStatus.DOUBLE_TAP, true);
// Generate a third ACTION_DOWN event whose time is within the double-tap timeout.
@@ -153,7 +156,7 @@
MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
event5.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event5, mConfig);
- assertTap(mTouchState, 22f, 32f, 21f, 31f,
+ assertMultiTap(mTouchState, 22f, 32f, 21f, 31f,
MultiTapStatus.TRIPLE_CLICK, true);
}
@@ -163,34 +166,71 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
// 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);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
MotionEvent event3 = downEvent(event3Time, event3Time, 21f, 31f);
mTouchState.update(event3, mConfig);
- assertTap(mTouchState, 21f, 31f, 20f, 30f,
+ assertMultiTap(mTouchState, 21f, 31f, 20f, 30f,
MultiTapStatus.DOUBLE_TAP, true);
// Simulate an ACTION_UP event.
long event4Time = 1003;
MotionEvent event4 = upEvent(event3Time, event4Time, 21f, 31f);
mTouchState.update(event4, mConfig);
- assertTap(mTouchState, 21f, 31f, 21f, 31f,
+ assertMultiTap(mTouchState, 21f, 31f, 21f, 31f,
MultiTapStatus.DOUBLE_TAP, true);
// Generate a third ACTION_DOWN event whose time is within the double-tap timeout.
long event5Time = 1004;
MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
mTouchState.update(event5, mConfig);
- assertTap(mTouchState, 22f, 32f, 21f, 31f,
- MultiTapStatus.FIRST_TAP, false);
+ assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false);
+ }
+
+ @Test
+ public void testUpdate_drag() 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);
+
+ // 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);
+
+ // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
+ int touchSlop = mConfig.getScaledTouchSlop();
+ float newX = event1.getX() + touchSlop + 1;
+ float newY = event1.getY();
+ long event3Time = 1002;
+ MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
+ mTouchState.update(event3, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+
+ // 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);
+ }
+
+ @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));
}
private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
@@ -201,8 +241,12 @@
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 void assertSingleTap(EditorTouchState touchState, float lastDownX,
- float lastDownY, float lastUpX, float lastUpY) {
+ float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) {
assertThat(touchState.getLastDownX(), is(lastDownX));
assertThat(touchState.getLastDownY(), is(lastDownY));
assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -211,9 +255,10 @@
assertThat(touchState.isTripleClick(), is(false));
assertThat(touchState.isMultiTap(), is(false));
assertThat(touchState.isMultiTapInSameArea(), is(false));
+ assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag));
}
- private static void assertTap(EditorTouchState touchState,
+ private static void assertMultiTap(EditorTouchState touchState,
float lastDownX, float lastDownY, float lastUpX, float lastUpY,
@MultiTapStatus int multiTapStatus, boolean isMultiTapInSameArea) {
assertThat(touchState.getLastDownX(), is(lastDownX));
@@ -225,5 +270,6 @@
assertThat(touchState.isMultiTap(), is(multiTapStatus == MultiTapStatus.DOUBLE_TAP
|| multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
+ assertThat(touchState.isMovedEnoughForDrag(), is(false));
}
}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 83ce67b..4808a0b 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -358,6 +358,27 @@
}
/**
+ * Returns an action that drags on text from startIndex to endIndex on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param startIndex The index of the TextView's text to start a drag from
+ * @param endIndex The index of the TextView's text to end the drag at
+ */
+ public static ViewAction dragOnText(int startIndex, int endIndex) {
+ return actionWithAssertions(
+ new DragAction(
+ DragAction.Drag.TAP,
+ new TextCoordinates(startIndex),
+ new TextCoordinates(endIndex),
+ Press.FINGER,
+ TextView.class));
+ }
+
+ /**
* A provider of the x, y coordinates of the handle dragging point.
*/
private static final class CurrentHandleCoordinates implements CoordinatesProvider {
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 7b405434..82854e5 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -20,6 +20,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -49,8 +50,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.media.Ringtone;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
@@ -157,9 +160,12 @@
when(mFrameworkObjectProvider.getRingtone(eq(mContext), any())).thenReturn(mRingtone);
when(mResources.getString(anyInt())).thenReturn("Howdy %s");
+ when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(null);
when(mResources.getIntArray(anyInt())).thenReturn(VIBRATOR_PATTERN_INT);
ResolveInfo resolveInfo = mock(ResolveInfo.class);
+ resolveInfo.serviceInfo = mock(ServiceInfo.class);
+ resolveInfo.serviceInfo.applicationInfo = mApplicationInfo;
when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name");
when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
when(mServiceInfo.getComponentName())
@@ -200,42 +206,47 @@
}
@Test
- public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse() {
+ public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse()
+ throws Exception {
configureNoShortcutService();
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
assertFalse(getController().isAccessibilityShortcutAvailable(false));
}
@Test
- public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue() {
+ public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue()
+ throws Exception {
configureValidShortcutService();
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
assertTrue(getController().isAccessibilityShortcutAvailable(false));
}
@Test
- public void testShortcutAvailable_disabledWithValidServiceWhenCreated_shouldReturnFalse() {
+ public void testShortcutAvailable_disabledWithValidServiceWhenCreated_shouldReturnFalse()
+ throws Exception {
configureValidShortcutService();
configureShortcutEnabled(DISABLED_BUT_LOCK_SCREEN_ON);
assertFalse(getController().isAccessibilityShortcutAvailable(false));
}
@Test
- public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse() {
+ public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse()
+ throws Exception {
configureValidShortcutService();
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
assertFalse(getController().isAccessibilityShortcutAvailable(true));
}
@Test
- public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue() {
+ public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue()
+ throws Exception {
configureValidShortcutService();
configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
assertTrue(getController().isAccessibilityShortcutAvailable(true));
}
@Test
- public void testShortcutAvailable_onLockScreenAndLockScreenPreferenceUnset() {
+ public void testShortcutAvailable_onLockScreenAndLockScreenPreferenceUnset() throws Exception {
// When the user hasn't specified a lock screen preference, we allow from the lock screen
// as long as the user has agreed to enable the shortcut
configureValidShortcutService();
@@ -249,17 +260,19 @@
}
@Test
- public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse() {
+ public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse()
+ throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
- Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
+ configureNoShortcutService();
accessibilityShortcutController.onSettingsChanged();
assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
}
@Test
- public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue() {
+ public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue()
+ throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureNoShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -269,7 +282,8 @@
}
@Test
- public void testShortcutAvailable_whenShortcutBecomesDisabled_shouldReturnFalse() {
+ public void testShortcutAvailable_whenShortcutBecomesDisabled_shouldReturnFalse()
+ throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -279,7 +293,8 @@
}
@Test
- public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue() {
+ public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue()
+ throws Exception {
configureShortcutEnabled(DISABLED);
configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -289,7 +304,8 @@
}
@Test
- public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse() {
+ public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse()
+ throws Exception {
configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -299,7 +315,8 @@
}
@Test
- public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue() {
+ public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue()
+ throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
AccessibilityShortcutController accessibilityShortcutController = getController();
@@ -370,7 +387,7 @@
}
@Test
- public void testClickingDisableButtonInDialog_shouldClearShortcutId() {
+ public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
@@ -458,7 +475,22 @@
}
@Test
- public void testOnAccessibilityShortcut_showsWarningDialog_shouldTtsSpokenPrompt() {
+ public void testOnAccessibilityShortcut_sdkGreaterThanQ_reqA11yButton_callsServiceWithNoToast()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
+ configureRequestAccessibilityButton();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
+ getController().performAccessibilityShortcut();
+
+ verifyZeroInteractions(mToast);
+ verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ }
+
+ @Test
+ public void testOnAccessibilityShortcut_showsWarningDialog_shouldTtsSpokenPrompt()
+ throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
configureTtsSpokenPromptEnabled();
@@ -482,7 +514,8 @@
}
@Test
- public void testOnAccessibilityShortcut_showsWarningDialog_ttsInitFail_noSpokenPrompt() {
+ public void testOnAccessibilityShortcut_showsWarningDialog_ttsInitFail_noSpokenPrompt()
+ throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
configureTtsSpokenPromptEnabled();
@@ -500,19 +533,28 @@
verify(mRingtone).play();
}
- private void configureNoShortcutService() {
+ private void configureNoShortcutService() throws Exception {
+ when(mAccessibilityManagerService
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+ .thenReturn(Collections.emptyList());
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
}
- private void configureValidShortcutService() {
+ private void configureValidShortcutService() throws Exception {
+ when(mAccessibilityManagerService
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+ .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
Settings.Secure.putString(
mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
}
- private void configureFirstFrameworkFeature() {
+ private void configureFirstFrameworkFeature() throws Exception {
ComponentName featureComponentName =
(ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
.keySet().toArray()[0];
+ when(mAccessibilityManagerService
+ .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+ .thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
featureComponentName.flattenToString());
}
@@ -552,6 +594,15 @@
.FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK;
}
+ private void configureRequestAccessibilityButton() {
+ mServiceInfo.flags |= AccessibilityServiceInfo
+ .FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ }
+
+ private void configureApplicationTargetSdkVersion(int versionCode) {
+ mApplicationInfo.targetSdkVersion = versionCode;
+ }
+
private void configureHandlerCallbackInvocation() {
doAnswer((InvocationOnMock invocation) -> {
Message m = (Message) invocation.getArguments()[0];
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index d427cbd..8622b7e 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -313,7 +313,7 @@
assertThat(activity.isFinishing(), is(false));
onView(withId(R.id.empty)).check(matches(isDisplayed()));
- onView(withId(R.id.resolver_list)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.profile_pager)).check(matches(not(isDisplayed())));
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> activity.getAdapter().handlePackagesChanged()
);
@@ -674,12 +674,12 @@
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
+ // Second invocation is from onCreate
verify(mockLogger, Mockito.times(2)).write(logMakerCaptor.capture());
- // First invocation is from onCreate
- assertThat(logMakerCaptor.getAllValues().get(1).getCategory(),
- is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW));
- assertThat(logMakerCaptor.getAllValues().get(1).getSubtype(),
+ assertThat(logMakerCaptor.getAllValues().get(0).getSubtype(),
is(CONTENT_PREVIEW_TEXT));
+ assertThat(logMakerCaptor.getAllValues().get(0).getCategory(),
+ is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW));
}
@Test
@@ -706,10 +706,10 @@
waitForIdle();
verify(mockLogger, Mockito.times(2)).write(logMakerCaptor.capture());
// First invocation is from onCreate
- assertThat(logMakerCaptor.getAllValues().get(1).getCategory(),
- is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW));
- assertThat(logMakerCaptor.getAllValues().get(1).getSubtype(),
+ assertThat(logMakerCaptor.getAllValues().get(0).getSubtype(),
is(CONTENT_PREVIEW_IMAGE));
+ assertThat(logMakerCaptor.getAllValues().get(0).getCategory(),
+ is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW));
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 03705d0..2a10443 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -29,6 +29,7 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.UserHandle;
import android.util.Size;
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
@@ -47,7 +48,7 @@
private UsageStatsManager mUsm;
ChooserListAdapter getAdapter() {
- return (ChooserListAdapter) mAdapter;
+ return mChooserMultiProfilePagerAdapter.getActiveListAdapter();
}
boolean getIsSelected() { return mIsSuccessfullySelected; }
@@ -77,7 +78,7 @@
}
@Override
- protected ResolverListController createListController() {
+ protected ResolverListController createListController(UserHandle userHandle) {
return sOverrides.resolverListController;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 344c286..923ce3e 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -116,14 +116,14 @@
waitForIdle();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- final View resolverList = activity.findViewById(R.id.resolver_list);
- final int initialResolverHeight = resolverList.getHeight();
+ final View viewPager = activity.findViewById(R.id.profile_pager);
+ final int initialResolverHeight = viewPager.getHeight();
activity.runOnUiThread(() -> {
ResolverDrawerLayout layout = (ResolverDrawerLayout)
activity.findViewById(
R.id.contentPanel);
- ((ResolverDrawerLayout.LayoutParams) resolverList.getLayoutParams()).maxHeight
+ ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
= initialResolverHeight - 1;
// Force a relayout
layout.invalidate();
@@ -131,13 +131,13 @@
});
waitForIdle();
assertThat("Drawer should be capped at maxHeight",
- resolverList.getHeight() == (initialResolverHeight - 1));
+ viewPager.getHeight() == (initialResolverHeight - 1));
activity.runOnUiThread(() -> {
ResolverDrawerLayout layout = (ResolverDrawerLayout)
activity.findViewById(
R.id.contentPanel);
- ((ResolverDrawerLayout.LayoutParams) resolverList.getLayoutParams()).maxHeight
+ ((ResolverDrawerLayout.LayoutParams) viewPager.getLayoutParams()).maxHeight
= initialResolverHeight + 1;
// Force a relayout
layout.invalidate();
@@ -145,7 +145,7 @@
});
waitForIdle();
assertThat("Drawer should not change height if its height is less than maxHeight",
- resolverList.getHeight() == initialResolverHeight);
+ viewPager.getHeight() == initialResolverHeight);
}
@Ignore // Failing - b/144929805
@@ -160,11 +160,13 @@
waitForIdle();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- final View resolverList = activity.findViewById(R.id.resolver_list);
+ final View viewPager = activity.findViewById(R.id.profile_pager);
+ final View divider = activity.findViewById(R.id.divider);
final RelativeLayout profileView =
(RelativeLayout) activity.findViewById(R.id.profile_button).getParent();
assertThat("Drawer should show at bottom by default",
- profileView.getBottom() == resolverList.getTop() && profileView.getTop() > 0);
+ profileView.getBottom() + divider.getHeight() == viewPager.getTop()
+ && profileView.getTop() > 0);
activity.runOnUiThread(() -> {
ResolverDrawerLayout layout = (ResolverDrawerLayout)
@@ -174,7 +176,8 @@
});
waitForIdle();
assertThat("Drawer should show at top with new attribute",
- profileView.getBottom() == resolverList.getTop() && profileView.getTop() == 0);
+ profileView.getBottom() + divider.getHeight() == viewPager.getTop()
+ && profileView.getTop() == 0);
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index 5ac1489..64906bb 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -115,7 +115,7 @@
mUsm = new UsageStatsManager(mMockContext, mMockService);
when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
- refererPackage, UserHandle.USER_CURRENT);
+ refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
mController.sort(new ArrayList<ResolvedComponentInfo>());
long beforeReport = getCount(mUsm, packageName, action, annotation);
mController.updateChooserCounts(packageName, UserHandle.USER_CURRENT, action);
@@ -132,7 +132,7 @@
mUsm = new UsageStatsManager(mMockContext, mMockService);
when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
- refererPackage, UserHandle.USER_CURRENT);
+ refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
List<ResolvedComponentInfo> topKList = new ArrayList<>(resolvedComponents);
mController.topK(topKList, 5);
List<ResolvedComponentInfo> sortList = new ArrayList<>(topKList);
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 39cc83c..c5d2cfa 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
import com.android.internal.app.chooser.TargetInfo;
@@ -37,15 +38,15 @@
private UsageStatsManager mUsm;
@Override
- public ResolverListAdapter createAdapter(Context context, List<Intent> payloadIntents,
- Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed,
- boolean useLayoutForBrowsables) {
+ public ResolverListAdapter createResolverListAdapter(Context context,
+ List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
+ boolean filterLastUsed, boolean useLayoutForBrowsables, UserHandle userHandle) {
return new ResolverWrapperAdapter(context, payloadIntents, initialIntents, rList,
- filterLastUsed, createListController(), useLayoutForBrowsables, this);
+ filterLastUsed, createListController(userHandle), useLayoutForBrowsables, this);
}
ResolverWrapperAdapter getAdapter() {
- return (ResolverWrapperAdapter) mAdapter;
+ return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter();
}
@Override
@@ -66,7 +67,7 @@
}
@Override
- protected ResolverListController createListController() {
+ protected ResolverListController createListController(UserHandle userHandle) {
return sOverrides.resolverListController;
}
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
new file mode 100644
index 0000000..ffc925f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.infra;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Unit test for {@link AndroidFuture}.
+ *
+ * <p>To run it:
+ * {@code atest FrameworksCoreTests:com.android.internal.infra.AndroidFutureTest}
+ */
+
+@RunWith(AndroidJUnit4.class)
+public class AndroidFutureTest {
+ @Test
+ public void testGet() throws Exception {
+ AndroidFuture<Integer> future = new AndroidFuture<>();
+ future.complete(5);
+ assertThat(future.get()).isEqualTo(5);
+ }
+
+ @Test
+ public void testWhenComplete_AlreadyComplete() throws Exception {
+ AndroidFuture<Integer> future = new AndroidFuture<>();
+ future.complete(5);
+ CountDownLatch latch = new CountDownLatch(1);
+ future.whenComplete((obj, err) -> {
+ assertThat(obj).isEqualTo(5);
+ assertThat(err).isNull();
+ latch.countDown();
+ });
+ latch.await();
+ }
+
+ @Test
+ public void testWhenComplete_NotYetComplete() throws Exception {
+ AndroidFuture<Integer> future = new AndroidFuture<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ future.whenComplete((obj, err) -> {
+ assertThat(obj).isEqualTo(5);
+ assertThat(err).isNull();
+ latch.countDown();
+ });
+ assertThat(latch.getCount()).isEqualTo(1);
+ future.complete(5);
+ latch.await();
+ assertThat(latch.getCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testCompleteExceptionally() {
+ AndroidFuture<Integer> future = new AndroidFuture<>();
+ Exception origException = new UnsupportedOperationException();
+ future.completeExceptionally(origException);
+ ExecutionException executionException =
+ expectThrows(ExecutionException.class, future::get);
+ assertThat(executionException.getCause()).isSameAs(origException);
+ }
+
+ @Test
+ public void testCompleteExceptionally_Listener() throws Exception {
+ AndroidFuture<Integer> future = new AndroidFuture<>();
+ Exception origException = new UnsupportedOperationException();
+ future.completeExceptionally(origException);
+ CountDownLatch latch = new CountDownLatch(1);
+ future.whenComplete((obj, err) -> {
+ assertThat(obj).isNull();
+ assertThat(err).isSameAs(origException);
+ latch.countDown();
+ });
+ latch.await();
+ }
+
+ @Test
+ public void testWriteToParcel() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ AndroidFuture<Integer> future1 = new AndroidFuture<>();
+ future1.complete(5);
+ future1.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
+ assertThat(future2.get()).isEqualTo(5);
+ }
+
+ @Test
+ public void testWriteToParcel_Exception() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ AndroidFuture<Integer> future1 = new AndroidFuture<>();
+ future1.completeExceptionally(new UnsupportedOperationException());
+ future1.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
+ ExecutionException executionException =
+ expectThrows(ExecutionException.class, future2::get);
+ assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
+ }
+
+ @Test
+ public void testWriteToParcel_Incomplete() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ AndroidFuture<Integer> future1 = new AndroidFuture<>();
+ future1.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
+ future2.complete(5);
+ assertThat(future1.get()).isEqualTo(5);
+ }
+
+ @Test
+ public void testWriteToParcel_Incomplete_Exception() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ AndroidFuture<Integer> future1 = new AndroidFuture<>();
+ future1.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
+ future2.completeExceptionally(new UnsupportedOperationException());
+ ExecutionException executionException =
+ expectThrows(ExecutionException.class, future1::get);
+ assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
+ }
+}
diff --git a/core/tests/overlaytests/device/res/layout/layout.xml b/core/tests/overlaytests/device/res/layout/layout.xml
new file mode 100644
index 0000000..e12c715
--- /dev/null
+++ b/core/tests/overlaytests/device/res/layout/layout.xml
@@ -0,0 +1,39 @@
+<?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"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout android:id="@id/view_1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.overlaytest.view.TestTextView
+ android:id="@id/view_2"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ app:customAttribute="none"/>
+
+ <com.android.overlaytest.view.TestTextView
+ android:id="@id/view_3"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ app:customAttribute="none" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/overlaytests/device/res/values/config.xml b/core/tests/overlaytests/device/res/values/config.xml
index c692a262..e918268 100644
--- a/core/tests/overlaytests/device/res/values/config.xml
+++ b/core/tests/overlaytests/device/res/values/config.xml
@@ -56,4 +56,16 @@
<item>17</item>
<item>19</item>
</integer-array>
+
+ <attr name="customAttribute" />
+ <id name="view_1" />
+ <id name="view_2" />
+ <id name="view_3" />
+
+ <!-- Stabilize the ids of attributes and ids used in test layouts so that they differ from the
+ overlay resource ids -->
+ <public type="attr" name="customAttribute" id="0x7f200000"/>
+ <public type="id" name="view_1" id="0x7f210000"/>
+ <public type="id" name="view_2" id="0x7f210001"/>
+ <public type="id" name="view_3" id="0x7f210002"/>
</resources>
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
index fdb6bbb..636f4c8 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
@@ -18,20 +18,26 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.LocaleList;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.util.Xml;
+import android.view.LayoutInflater;
+import android.view.View;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.ArrayUtils;
+import com.android.overlaytest.view.TestTextView;
import org.junit.Before;
import org.junit.Ignore;
@@ -45,6 +51,7 @@
@Ignore
public abstract class OverlayBaseTest {
+ private Context mContext;
private Resources mResources;
private final int mMode;
static final int MODE_NO_OVERLAY = 0;
@@ -61,7 +68,8 @@
@Before
public void setUp() {
- mResources = InstrumentationRegistry.getContext().getResources();
+ mContext = InstrumentationRegistry.getContext();
+ mResources = mContext.getResources();
}
private int calculateRawResourceChecksum(int resId) throws Throwable {
@@ -321,6 +329,50 @@
assertEquals("com.android.overlaytest", contents);
}
+ @Test
+ public void testRewrite() throws Throwable {
+ final TypedValue result = new TypedValue();
+ mResources.getValue(R.string.str, result, true);
+ assertEquals(result.resourceId & 0xff000000, 0x7f000000);
+ }
+
+ @Test
+ public void testOverlayLayout() throws Throwable {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final View layout = inflater.inflate(R.layout.layout, null);
+ assertNotNull(layout.findViewById(R.id.view_1));
+
+ final TestTextView view2 = layout.findViewById(R.id.view_2);
+ assertNotNull(view2);
+ switch (mMode) {
+ case MODE_NO_OVERLAY:
+ assertEquals("none", view2.getCustomAttributeValue());
+ break;
+ case MODE_SINGLE_OVERLAY:
+ assertEquals("single", view2.getCustomAttributeValue());
+ break;
+ case MODE_MULTIPLE_OVERLAYS:
+ assertEquals("multiple", view2.getCustomAttributeValue());
+ break;
+ default:
+ fail("Unknown mode " + mMode);
+ }
+
+ final TestTextView view3 = layout.findViewById(R.id.view_3);
+ assertNotNull(view3);
+ switch (mMode) {
+ case MODE_NO_OVERLAY:
+ assertEquals("none", view3.getCustomAttributeValue());
+ break;
+ case MODE_SINGLE_OVERLAY:
+ case MODE_MULTIPLE_OVERLAYS:
+ assertEquals("overlaid", view3.getCustomAttributeValue());
+ break;
+ default:
+ fail("Unknown mode " + mMode);
+ }
+ }
+
/*
* testMatrix* tests
*
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/view/TestTextView.java b/core/tests/overlaytests/device/src/com/android/overlaytest/view/TestTextView.java
new file mode 100644
index 0000000..2245e2b
--- /dev/null
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/view/TestTextView.java
@@ -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 com.android.overlaytest.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+public class TestTextView extends TextView {
+
+ private final String mCustomAttributeValue;
+
+ public TestTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.textViewStyle, 0);
+ }
+
+ public TestTextView(Context context, AttributeSet attrs,
+ int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ int[] testResources = new int[]{com.android.overlaytest.R.attr.customAttribute};
+ final Resources.Theme theme = context.getTheme();
+ TypedArray typedArray = theme.obtainStyledAttributes(attrs, testResources, defStyleAttr,
+ defStyleRes);
+ mCustomAttributeValue = typedArray.getString(0);
+ }
+
+ public String getCustomAttributeValue() {
+ return mCustomAttributeValue;
+ }
+}
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml
index 7d28408..873ca3c 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml
@@ -19,5 +19,6 @@
android:versionCode="1"
android:versionName="1.0">
<application android:hasCode="false" />
- <overlay android:targetPackage="com.android.overlaytest" />
+ <overlay android:targetPackage="com.android.overlaytest"
+ android:resourcesMap="@xml/overlays"/>
</manifest>
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/layout/layout.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/layout/layout.xml
new file mode 100644
index 0000000..7b63605
--- /dev/null
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/layout/layout.xml
@@ -0,0 +1,39 @@
+<?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"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout android:id="@+id/view_1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.overlaytest.view.TestTextView
+ android:id="@+id/view_2"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ app:customAttribute="@string/str" />
+
+ <com.android.overlaytest.view.TestTextView
+ android:id="@+id/view_3"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ app:customAttribute="overlaid" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/values/config.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/values/config.xml
index 972137a..74c4963 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/values/config.xml
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/values/config.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="str">single</string>
- <string name="str2">single</string>
<integer name="matrix_101000">300</integer>
<integer name="matrix_101001">300</integer>
<integer name="matrix_101010">300</integer>
@@ -18,7 +17,6 @@
<integer name="matrix_111101">300</integer>
<integer name="matrix_111110">300</integer>
<integer name="matrix_111111">300</integer>
- <bool name="usually_false">true</bool>
<integer-array name="fibonacci">
<item>21</item>
<item>13</item>
@@ -29,7 +27,10 @@
<item>1</item>
<item>1</item>
</integer-array>
+
<!-- The following integer does not exist in the original package. Idmap
generation should therefore ignore it. -->
<integer name="integer_not_in_original_package">0</integer>
+
+ <attr name="customAttribute" />
</resources>
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
new file mode 100644
index 0000000..38e5fa1
--- /dev/null
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/overlays.xml
@@ -0,0 +1,61 @@
+<?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.
+ -->
+<overlay>
+ <item target="drawable/drawable" value="@drawable/drawable"/>
+ <item target="layout/layout" value="@layout/layout"/>
+ <item target="raw/lorem_ipsum" value="@raw/lorem_ipsum"/>
+ <item target="xml/integer" value="@xml/integer"/>
+ <item target="string/str" value="@string/str"/>
+ <item target="string/str2" value="single"/>
+
+ <item target="integer/matrix_100100" value="@integer/matrix_100100"/>
+ <item target="integer/matrix_100101" value="@integer/matrix_100101"/>
+ <item target="integer/matrix_100110" value="@integer/matrix_100110"/>
+ <item target="integer/matrix_100110" value="@integer/matrix_100110"/>
+ <item target="integer/matrix_100111" value="@integer/matrix_100111"/>
+ <item target="integer/matrix_101000" value="@integer/matrix_101000"/>
+ <item target="integer/matrix_101001" value="@integer/matrix_101001"/>
+ <item target="integer/matrix_101010" value="@integer/matrix_101010"/>
+ <item target="integer/matrix_101011" value="@integer/matrix_101011"/>
+ <item target="integer/matrix_101100" value="@integer/matrix_101100"/>
+ <item target="integer/matrix_101101" value="@integer/matrix_101101"/>
+ <item target="integer/matrix_101110" value="@integer/matrix_101110"/>
+ <item target="integer/matrix_101111" value="@integer/matrix_101111"/>
+ <item target="integer/matrix_110100" value="@integer/matrix_110100"/>
+ <item target="integer/matrix_110101" value="@integer/matrix_110101"/>
+ <item target="integer/matrix_110110" value="@integer/matrix_110110"/>
+ <item target="integer/matrix_110111" value="@integer/matrix_110111"/>
+ <item target="integer/matrix_111000" value="@integer/matrix_111000"/>
+ <item target="integer/matrix_111001" value="@integer/matrix_111001"/>
+ <item target="integer/matrix_111010" value="@integer/matrix_111010"/>
+ <item target="integer/matrix_111011" value="@integer/matrix_111011"/>
+ <item target="integer/matrix_111100" value="@integer/matrix_111100"/>
+ <item target="integer/matrix_111101" value="@integer/matrix_111101"/>
+ <item target="integer/matrix_111110" value="@integer/matrix_111110"/>
+ <item target="integer/matrix_111111" value="@integer/matrix_111111"/>
+
+ <item target="bool/usually_false" value="true"/>
+
+ <item target="array/fibonacci" value="@array/fibonacci"/>
+
+ <item target="attr/customAttribute" value="@attr/customAttribute"/>
+ <item target="id/view_1" value="@id/view_1"/>
+ <item target="id/view_2" value="@id/view_2"/>
+ <item target="id/view_3" value="@id/view_3"/>
+</overlay>
+
+
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index e16d1ca..1472b90 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -24,8 +24,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import android.content.Context;
+import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
@@ -54,9 +59,15 @@
// This raises a `SecurityException` if the device is locked. Calling either `Context`
// method results in a broadcast of `android.intent.action. USER_PRESENT`. Only the system
// process is allowed to broadcast that `Intent`.
+ Resources res = mock(Resources.class);
mContext = Mockito.spy(Context.class);
- Mockito.doNothing().when(mContext).sendBroadcastAsUser(any(), any());
- Mockito.doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+ doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
+ doReturn(res).when(mContext).getResources();
+ doReturn("com.android.systemui/.Service").when(res).getString(
+ eq(com.android.internal.R.string.config_screenshotServiceComponent));
+ doReturn("com.android.systemui/.ErrorReceiver").when(res).getString(
+ eq(com.android.internal.R.string.config_screenshotErrorReceiverComponent));
mHandler = new Handler(Looper.getMainLooper());
mScreenshotHelper = new ScreenshotHelper(mContext);
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index ff521be..20395fb 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -88,7 +88,7 @@
prebuilt_etc {
name: "privapp_whitelist_com.android.launcher3",
- product_specific: true,
+ system_ext_specific: true,
sub_dir: "permissions",
src: "com.android.launcher3.xml",
filename_from_src: true,
@@ -120,7 +120,7 @@
prebuilt_etc {
name: "privapp_whitelist_com.android.storagemanager",
- product_specific: true,
+ system_ext_specific: true,
sub_dir: "permissions",
src: "com.android.storagemanager.xml",
filename_from_src: true,
@@ -128,7 +128,7 @@
prebuilt_etc {
name: "privapp_whitelist_com.android.systemui",
- product_specific: true,
+ system_ext_specific: true,
sub_dir: "permissions",
src: "com.android.systemui.xml",
filename_from_src: true,
diff --git a/data/etc/CleanSpec.mk b/data/etc/CleanSpec.mk
index 3fe421e..783a7ed 100644
--- a/data/etc/CleanSpec.mk
+++ b/data/etc/CleanSpec.mk
@@ -51,6 +51,10 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.provision.xml)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.settings.xml)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.settings.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.launcher3.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.launcher3.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.systemui.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.systemui.xml)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index c445651..26a40d3 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -126,7 +126,7 @@
sub_dir: "permissions",
src: "com.android.car.developeroptions.xml",
filename_from_src: true,
- product_specific: true,
+ system_ext_specific: true,
}
prebuilt_etc {
diff --git a/packages/Tethering/CleanSpec.mk b/data/etc/car/CleanSpec.mk
similarity index 90%
copy from packages/Tethering/CleanSpec.mk
copy to data/etc/car/CleanSpec.mk
index 70db351..18f7d34 100644
--- a/packages/Tethering/CleanSpec.mk
+++ b/data/etc/car/CleanSpec.mk
@@ -43,10 +43,8 @@
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
-
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Tethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
-
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.car.developeroptions.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.car.developeroptions.xml)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index ba877f8..ee989cc 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -51,5 +51,6 @@
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+ <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml
index 07a5617..3997371 100644
--- a/data/etc/hiddenapi-package-whitelist.xml
+++ b/data/etc/hiddenapi-package-whitelist.xml
@@ -65,4 +65,7 @@
<hidden-api-whitelisted-app package="com.android.terminal" />
<hidden-api-whitelisted-app package="com.android.wallpaper" />
<hidden-api-whitelisted-app package="jp.co.omronsoft.openwnn" />
+ <!-- STOPSHIP: Remove this when fixing all @hide usage for tethering.-->
+ <hidden-api-whitelisted-app package="com.android.networkstack.tethering" />
+ <hidden-api-whitelisted-app package="com.android.networkstack" />
</config>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 80098c5..0574775 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -157,6 +157,7 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
+ <assign-permission name="android.permission.PREEMPT_SOUND_TRIGGER" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 322cbd7..f1941fc 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -145,7 +145,7 @@
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_CONFIGURATION"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
- <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+ <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
<permission name="android.permission.DUMP"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
@@ -240,6 +240,13 @@
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.networkstack.tethering">
+ <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.UPDATE_APP_OPS_STATS"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.server.telecom">
<permission name="android.permission.BIND_CONNECTION_SERVICE"/>
<permission name="android.permission.BIND_INCALL_SERVICE"/>
@@ -259,6 +266,8 @@
</privapp-permissions>
<privapp-permissions package="com.android.shell">
+ <!-- Needed for test only -->
+ <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.ACCESS_LOWPAN_STATE"/>
<permission name="android.permission.BACKUP"/>
<permission name="android.permission.BATTERY_STATS"/>
@@ -343,6 +352,8 @@
<permission name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
<!-- Permission required for Telecom car mode CTS tests. -->
<permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+ <!-- Permission required for Tethering CTS tests. -->
+ <permission name="android.permission.TETHER_PRIVILEGED"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 440b885..1e98e3a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -7,12 +7,6 @@
"group": "WM_DEBUG_KEEP_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-2138637148": {
- "message": "Clearing focused app, displayId=%d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_FOCUS_LIGHT",
- "at": "com\/android\/server\/wm\/ActivityDisplay.java"
- },
"-2127842445": {
"message": "Clearing startingData for token=%s",
"level": "VERBOSE",
@@ -79,12 +73,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1976550065": {
- "message": "commitVisibility: %s: visible=%b visibleRequested=%b",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1963461591": {
"message": "Removing %s from %s",
"level": "VERBOSE",
@@ -283,6 +271,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1521427940": {
+ "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1515151503": {
"message": ">>> OPEN TRANSACTION removeReplacedWindows",
"level": "INFO",
@@ -391,6 +385,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1263316010": {
+ "message": "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and oldRotation=%s (%d)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotation.java"
+ },
"-1259022216": {
"message": "SURFACE HIDE ( %s ): %s",
"level": "INFO",
@@ -469,6 +469,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1108775960": {
+ "message": "%s is requesting orientation %d (%s)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/WindowContainer.java"
+ },
"-1103716954": {
"message": "Not removing %s due to exit animation",
"level": "VERBOSE",
@@ -637,6 +643,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-766059044": {
+ "message": "Display id=%d selected orientation %s (%d), got rotation %s (%d)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotation.java"
+ },
"-760801764": {
"message": "onAnimationCancelled",
"level": "DEBUG",
@@ -697,12 +709,6 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
- "-633961578": {
- "message": "applyAnimation: transition animation is disabled or skipped. container=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-622997754": {
"message": "postWindowRemoveCleanupLocked: %s",
"level": "VERBOSE",
@@ -715,6 +721,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-603199586": {
+ "message": "Clearing focused app, displayId=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_FOCUS_LIGHT",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-583031528": {
"message": "%s",
"level": "INFO",
@@ -1273,12 +1285,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
- "481370485": {
- "message": "Computed rotation=%d for display id=%d based on lastOrientation=%d and oldRotation=%d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ORIENTATION",
- "at": "com\/android\/server\/wm\/DisplayRotation.java"
- },
"490877640": {
"message": "onStackOrderChanged(): stack=%s",
"level": "DEBUG",
@@ -1321,6 +1327,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
+ "584499099": {
+ "message": "Set focused app to: %s moveFocusNow=%b displayId=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_FOCUS_LIGHT",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"585096182": {
"message": "SURFACE isColorSpaceAgnostic=%b: %s",
"level": "INFO",
@@ -1477,12 +1489,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "841702299": {
- "message": "Changing app %s visible=%b performLayout=%b",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"845234215": {
"message": "App is requesting an orientation, return %d for display id=%d",
"level": "VERBOSE",
@@ -1525,12 +1531,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
- "917739349": {
- "message": "Set focused app to: %s moveFocusNow=%b displayId=%d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_FOCUS_LIGHT",
- "at": "com\/android\/server\/wm\/ActivityDisplay.java"
- },
"954470154": {
"message": "FORCED DISPLAY SCALING DISABLED",
"level": "INFO",
@@ -1741,6 +1741,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "1460759282": {
+ "message": "getAnimationTarget in=%s, out=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"1469292670": {
"message": "Changing focus from %s to %s displayId=%d Callers=%s",
"level": "VERBOSE",
@@ -1807,12 +1813,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "1573332272": {
- "message": "Display id=%d selected orientation %d, got rotation %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ORIENTATION",
- "at": "com\/android\/server\/wm\/DisplayRotation.java"
- },
"1577579529": {
"message": "win=%s destroySurfaces: appStopped=%b win.mWindowRemovalAllowed=%b win.mRemoveOnExit=%b",
"level": "ERROR",
@@ -1993,6 +1993,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "1967975839": {
+ "message": "Changing app %s visible=%b performLayout=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"1984470582": {
"message": "Creating TaskScreenshotAnimatable: task: %s width: %d height: %d",
"level": "DEBUG",
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
new file mode 100644
index 0000000..d8af726
--- /dev/null
+++ b/framework-jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule android.hidl.** android.internal.hidl.@1
+rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 3f3ad57..3b86413 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -28,7 +28,6 @@
import android.os.ServiceManager;
import android.util.Log;
import android.util.TimeUtils;
-import android.view.FrameMetricsObserver;
import android.view.IGraphicsStats;
import android.view.IGraphicsStatsCallback;
import android.view.NativeVectorDrawableAnimator;
@@ -38,8 +37,6 @@
import android.view.TextureLayer;
import android.view.animation.AnimationUtils;
-import com.android.internal.util.VirtualRefBasePtr;
-
import java.io.File;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
@@ -598,9 +595,8 @@
*
* @hide
*/
- public void addFrameMetricsObserver(FrameMetricsObserver observer) {
- long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer);
- observer.mNative = new VirtualRefBasePtr(nativeObserver);
+ public void addObserver(HardwareRendererObserver observer) {
+ nAddObserver(mNativeProxy, observer.getNativeInstance());
}
/**
@@ -608,9 +604,8 @@
*
* @hide
*/
- public void removeFrameMetricsObserver(FrameMetricsObserver observer) {
- nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get());
- observer.mNative = null;
+ public void removeObserver(HardwareRendererObserver observer) {
+ nRemoveObserver(mNativeProxy, observer.getNativeInstance());
}
/**
@@ -1170,10 +1165,9 @@
private static native void nSetFrameCompleteCallback(long nativeProxy,
FrameCompleteCallback callback);
- private static native long nAddFrameMetricsObserver(long nativeProxy,
- FrameMetricsObserver observer);
+ private static native void nAddObserver(long nativeProxy, long nativeObserver);
- private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
+ private static native void nRemoveObserver(long nativeProxy, long nativeObserver);
private static native int nCopySurfaceInto(Surface surface,
int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle);
diff --git a/graphics/java/android/graphics/HardwareRendererObserver.java b/graphics/java/android/graphics/HardwareRendererObserver.java
new file mode 100644
index 0000000..da9d03c
--- /dev/null
+++ b/graphics/java/android/graphics/HardwareRendererObserver.java
@@ -0,0 +1,103 @@
+/*
+ * 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.graphics;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+/**
+ * Provides streaming access to frame stats information from HardwareRenderer to apps.
+ *
+ * @hide
+ */
+public class HardwareRendererObserver {
+ private final long[] mFrameMetrics;
+ private final Handler mHandler;
+ private final OnFrameMetricsAvailableListener mListener;
+ private VirtualRefBasePtr mNativePtr;
+
+ /**
+ * Interface for clients that want frame timing information for each frame rendered.
+ * @hide
+ */
+ public interface OnFrameMetricsAvailableListener {
+ /**
+ * Called when information is available for the previously rendered frame.
+ *
+ * Reports can be dropped if this callback takes too long to execute, as the report producer
+ * cannot wait for the consumer to complete.
+ *
+ * It is highly recommended that clients copy the metrics array within this method
+ * and defer additional computation or storage to another thread to avoid unnecessarily
+ * dropping reports.
+ *
+ * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+ * this callback was invoked.
+ */
+ void onFrameMetricsAvailable(int dropCountSinceLastInvocation);
+ }
+
+ /**
+ * Creates a FrameMetricsObserver
+ *
+ * @param frameMetrics the available metrics. This array is reused on every call to the listener
+ * and thus <strong>this reference should only be used within the scope of the listener callback
+ * as data is not guaranteed to be valid outside the scope of that method</strong>.
+ * @param handler the Handler to use when invoking callbacks
+ */
+ public HardwareRendererObserver(@NonNull OnFrameMetricsAvailableListener listener,
+ @NonNull long[] frameMetrics, @NonNull Handler handler) {
+ if (handler == null || handler.getLooper() == null) {
+ throw new NullPointerException("handler and its looper cannot be null");
+ }
+
+ if (handler.getLooper().getQueue() == null) {
+ throw new IllegalStateException("invalid looper, null message queue\n");
+ }
+
+ mFrameMetrics = frameMetrics;
+ mHandler = handler;
+ mListener = listener;
+ mNativePtr = new VirtualRefBasePtr(nCreateObserver());
+ }
+
+ /*package*/ long getNativeInstance() {
+ return mNativePtr.get();
+ }
+
+ // Called by native on the provided Handler
+ @SuppressWarnings("unused")
+ private void notifyDataAvailable() {
+ mHandler.post(() -> {
+ boolean hasMoreData = true;
+ while (hasMoreData) {
+ // a drop count of -1 is a sentinel that no more buffers are available
+ int dropCount = nGetNextBuffer(mNativePtr.get(), mFrameMetrics);
+ if (dropCount >= 0) {
+ mListener.onFrameMetricsAvailable(dropCount);
+ } else {
+ hasMoreData = false;
+ }
+ }
+ });
+ }
+
+ private native long nCreateObserver();
+ private static native int nGetNextBuffer(long nativePtr, long[] data);
+}
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 6619dba..aecef8e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -270,7 +270,7 @@
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
AssetFileDescriptor assetFd = null;
try {
- if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) {
+ if (mUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
assetFd = mResolver.openTypedAssetFileDescriptor(mUri,
"image/*", null);
} else {
@@ -1675,6 +1675,9 @@
if (r == null) {
return;
}
+ if (r.width() <= 0 || r.height() <= 0) {
+ throw new IllegalStateException("Subset " + r + " is empty/unsorted");
+ }
if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) {
throw new IllegalStateException("Subset " + r + " not contained by "
+ "scaled image bounds: (" + width + " x " + height + ")");
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index f5fa8c5..e93e7df 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-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/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 0840986..e70529b 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -22,7 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-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/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 090d915..51b299c 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.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/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index bad3791..3408b64 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -16,19 +16,19 @@
package android.graphics.drawable;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
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.content.res.Resources;
import android.content.res.Resources.Theme;
import android.util.AttributeSet;
import android.view.InflateException;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.HashMap;
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index 6c90c4c..e197e71 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.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.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 45b2de5..3881955 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -22,7 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
-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;
@@ -784,9 +784,7 @@
mFillPaint.setDither(st.mDither);
mFillPaint.setColorFilter(colorFilter);
if (colorFilter != null && st.mSolidColors == null) {
- // If we don't have a solid color and we don't have a gradient,
- // the app is stroking the shape, set the color to transparent
- mFillPaint.setColor(st.mGradientColors != null ? mAlpha << 24 : 0);
+ mFillPaint.setColor(mAlpha << 24);
}
if (haveStroke) {
mStrokePaint.setAlpha(currStrokeAlpha);
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index c2e3c64..cc7182c 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -21,7 +21,7 @@
import android.annotation.IdRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index bc8a4cb..005a4d1 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.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.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 760d554..fb4146f 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.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.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 8561d95..99d27ba 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.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.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 1540cc2..e5e4d45 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.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.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java
index db5f082..43766b6 100644
--- a/graphics/java/android/graphics/drawable/RotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/RotateDrawable.java
@@ -16,23 +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.Rect;
+import android.util.AttributeSet;
+import android.util.MathUtils;
+import android.util.TypedValue;
+
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.graphics.Canvas;
-import android.graphics.Rect;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
-import android.util.MathUtils;
-import android.util.TypedValue;
-import android.util.AttributeSet;
-
import java.io.IOException;
/**
diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java
index 91ed061..af7eed4 100644
--- a/graphics/java/android/graphics/drawable/ScaleDrawable.java
+++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java
@@ -16,14 +16,9 @@
package android.graphics.drawable;
-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.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
@@ -34,6 +29,11 @@
import android.util.TypedValue;
import android.view.Gravity;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
/**
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index f67188c..2920acb 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.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.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java
index 276f366..401e05f 100644
--- a/graphics/java/android/graphics/drawable/TransitionDrawable.java
+++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java
@@ -16,7 +16,7 @@
package android.graphics.drawable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources;
import android.graphics.Canvas;
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index aa19b2a..e6fa866 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -16,7 +16,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.ComplexColor;
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index bcee559..4e6580e 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.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.text.TextUtils;
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index bd1a492..54710e5 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.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.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Matrix;
@@ -29,7 +29,9 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+
import com.android.internal.util.Preconditions;
+
import dalvik.system.CloseGuard;
import libcore.io.IoUtils;
diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt
deleted file mode 100644
index 4b2331d..0000000
--- a/jarjar_rules_hidl.txt
+++ /dev/null
@@ -1 +0,0 @@
-rule android.hidl.** android.internal.hidl.@1
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 572fa8c..f53a7dc 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -16,7 +16,7 @@
package android.security;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.org.bouncycastle.util.io.pem.PemObject;
import com.android.org.bouncycastle.util.io.pem.PemReader;
@@ -65,6 +65,9 @@
/** Key prefix for VPN. */
public static final String VPN = "VPN_";
+ /** Key prefix for platform VPNs. */
+ public static final String PLATFORM_VPN = "PLATFORM_VPN_";
+
/** Key prefix for WIFI. */
public static final String WIFI = "WIFI_";
diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java
index a50ff79..af188a9 100644
--- a/keystore/java/android/security/GateKeeper.java
+++ b/keystore/java/android/security/GateKeeper.java
@@ -16,7 +16,7 @@
package android.security;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index d9ed5f3..dc57f55 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -16,10 +16,10 @@
package android.security;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.Application;
import android.app.KeyguardManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.face.FaceManager;
@@ -31,7 +31,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.security.KeyStoreException;
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index d033294..71e6559 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.security.KeyStore;
import android.security.keymaster.ExportResult;
import android.security.keymaster.KeyCharacteristics;
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index 94499ce..8a7b623 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -204,10 +204,7 @@
break;
}
case USE_INDIVIDUAL_ATTESTATION: {
- //TODO: Add the Keymaster tag for requesting the use of individual
- //attestation certificate, which should be
- //KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION
- attestArgs.addBoolean(720);
+ attestArgs.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION);
break;
}
default:
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 6df3b8c..52ff9e0 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -21,8 +21,8 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.KeyguardManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.security.GateKeeper;
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 773353d..0b2fd9e 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -43,6 +43,10 @@
return dtohl(e1.overlay_id) < overlay_id;
}
+size_t Idmap_header::Size() const {
+ return sizeof(Idmap_header) + sizeof(uint8_t) * dtohl(debug_info_size);
+}
+
OverlayStringPool::OverlayStringPool(const LoadedIdmap* loaded_idmap)
: data_header_(loaded_idmap->data_header_),
idmap_string_pool_(loaded_idmap->string_pool_.get()) { };
@@ -53,7 +57,7 @@
const char16_t* OverlayStringPool::stringAt(size_t idx, size_t* outLen) const {
const size_t offset = dtohl(data_header_->string_pool_index_offset);
- if (idmap_string_pool_ != nullptr && idx >= size() && idx >= offset) {
+ if (idmap_string_pool_ != nullptr && idx >= ResStringPool::size() && idx >= offset) {
return idmap_string_pool_->stringAt(idx - offset, outLen);
}
@@ -62,13 +66,17 @@
const char* OverlayStringPool::string8At(size_t idx, size_t* outLen) const {
const size_t offset = dtohl(data_header_->string_pool_index_offset);
- if (idmap_string_pool_ != nullptr && idx >= size() && idx >= offset) {
+ if (idmap_string_pool_ != nullptr && idx >= ResStringPool::size() && idx >= offset) {
return idmap_string_pool_->string8At(idx - offset, outLen);
}
return ResStringPool::string8At(idx, outLen);
}
+size_t OverlayStringPool::size() const {
+ return ResStringPool::size() + (idmap_string_pool_ != nullptr ? idmap_string_pool_->size() : 0U);
+}
+
OverlayDynamicRefTable::OverlayDynamicRefTable(const Idmap_data_header* data_header,
const Idmap_overlay_entry* entries,
uint8_t target_assigned_package_id)
@@ -211,8 +219,8 @@
}
auto header = reinterpret_cast<const Idmap_header*>(idmap_data.data());
- const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()) + sizeof(*header);
- size_t data_size = idmap_data.size() - sizeof(*header);
+ const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()) + header->Size();
+ size_t data_size = idmap_data.size() - header->Size();
// Currently idmap2 can only generate one data block.
auto data_header = reinterpret_cast<const Idmap_data_header*>(data_ptr);
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index c276a23..e748bd8 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -10,1439 +10,1479 @@
/* 6 */ {'B', 'a', 's', 's'},
/* 7 */ {'B', 'e', 'n', 'g'},
/* 8 */ {'B', 'r', 'a', 'h'},
- /* 9 */ {'C', 'a', 'n', 's'},
- /* 10 */ {'C', 'a', 'r', 'i'},
- /* 11 */ {'C', 'h', 'a', 'm'},
- /* 12 */ {'C', 'h', 'e', 'r'},
- /* 13 */ {'C', 'o', 'p', 't'},
- /* 14 */ {'C', 'p', 'r', 't'},
- /* 15 */ {'C', 'y', 'r', 'l'},
- /* 16 */ {'D', 'e', 'v', 'a'},
- /* 17 */ {'E', 'g', 'y', 'p'},
- /* 18 */ {'E', 't', 'h', 'i'},
- /* 19 */ {'G', 'e', 'o', 'r'},
- /* 20 */ {'G', 'o', 't', 'h'},
- /* 21 */ {'G', 'r', 'e', 'k'},
- /* 22 */ {'G', 'u', 'j', 'r'},
- /* 23 */ {'G', 'u', 'r', 'u'},
- /* 24 */ {'H', 'a', 'n', 's'},
- /* 25 */ {'H', 'a', 'n', 't'},
- /* 26 */ {'H', 'a', 't', 'r'},
- /* 27 */ {'H', 'e', 'b', 'r'},
- /* 28 */ {'H', 'l', 'u', 'w'},
- /* 29 */ {'H', 'm', 'n', 'g'},
- /* 30 */ {'I', 't', 'a', 'l'},
- /* 31 */ {'J', 'p', 'a', 'n'},
- /* 32 */ {'K', 'a', 'l', 'i'},
- /* 33 */ {'K', 'a', 'n', 'a'},
- /* 34 */ {'K', 'h', 'a', 'r'},
- /* 35 */ {'K', 'h', 'm', 'r'},
- /* 36 */ {'K', 'n', 'd', 'a'},
- /* 37 */ {'K', 'o', 'r', 'e'},
- /* 38 */ {'L', 'a', 'n', 'a'},
- /* 39 */ {'L', 'a', 'o', 'o'},
- /* 40 */ {'L', 'a', 't', 'n'},
- /* 41 */ {'L', 'e', 'p', 'c'},
- /* 42 */ {'L', 'i', 'n', 'a'},
- /* 43 */ {'L', 'i', 's', 'u'},
- /* 44 */ {'L', 'y', 'c', 'i'},
- /* 45 */ {'L', 'y', 'd', 'i'},
- /* 46 */ {'M', 'a', 'n', 'd'},
- /* 47 */ {'M', 'a', 'n', 'i'},
- /* 48 */ {'M', 'e', 'r', 'c'},
- /* 49 */ {'M', 'l', 'y', 'm'},
- /* 50 */ {'M', 'o', 'n', 'g'},
- /* 51 */ {'M', 'r', 'o', 'o'},
- /* 52 */ {'M', 'y', 'm', 'r'},
- /* 53 */ {'N', 'a', 'r', 'b'},
- /* 54 */ {'N', 'k', 'o', 'o'},
- /* 55 */ {'O', 'g', 'a', 'm'},
- /* 56 */ {'O', 'r', 'k', 'h'},
- /* 57 */ {'O', 'r', 'y', 'a'},
- /* 58 */ {'O', 's', 'g', 'e'},
- /* 59 */ {'P', 'a', 'u', 'c'},
- /* 60 */ {'P', 'h', 'l', 'i'},
- /* 61 */ {'P', 'h', 'n', 'x'},
- /* 62 */ {'P', 'l', 'r', 'd'},
- /* 63 */ {'P', 'r', 't', 'i'},
- /* 64 */ {'R', 'u', 'n', 'r'},
- /* 65 */ {'S', 'a', 'm', 'r'},
- /* 66 */ {'S', 'a', 'r', 'b'},
- /* 67 */ {'S', 'a', 'u', 'r'},
- /* 68 */ {'S', 'g', 'n', 'w'},
- /* 69 */ {'S', 'i', 'n', 'h'},
- /* 70 */ {'S', 'o', 'r', 'a'},
- /* 71 */ {'S', 'y', 'r', 'c'},
- /* 72 */ {'T', 'a', 'l', 'e'},
- /* 73 */ {'T', 'a', 'l', 'u'},
- /* 74 */ {'T', 'a', 'm', 'l'},
- /* 75 */ {'T', 'a', 'n', 'g'},
- /* 76 */ {'T', 'a', 'v', 't'},
- /* 77 */ {'T', 'e', 'l', 'u'},
- /* 78 */ {'T', 'f', 'n', 'g'},
- /* 79 */ {'T', 'h', 'a', 'a'},
- /* 80 */ {'T', 'h', 'a', 'i'},
- /* 81 */ {'T', 'i', 'b', 't'},
- /* 82 */ {'U', 'g', 'a', 'r'},
- /* 83 */ {'V', 'a', 'i', 'i'},
- /* 84 */ {'X', 'p', 'e', 'o'},
- /* 85 */ {'X', 's', 'u', 'x'},
- /* 86 */ {'Y', 'i', 'i', 'i'},
- /* 87 */ {'~', '~', '~', 'A'},
- /* 88 */ {'~', '~', '~', 'B'},
+ /* 9 */ {'C', 'a', 'k', 'm'},
+ /* 10 */ {'C', 'a', 'n', 's'},
+ /* 11 */ {'C', 'a', 'r', 'i'},
+ /* 12 */ {'C', 'h', 'a', 'm'},
+ /* 13 */ {'C', 'h', 'e', 'r'},
+ /* 14 */ {'C', 'o', 'p', 't'},
+ /* 15 */ {'C', 'p', 'r', 't'},
+ /* 16 */ {'C', 'y', 'r', 'l'},
+ /* 17 */ {'D', 'e', 'v', 'a'},
+ /* 18 */ {'E', 'g', 'y', 'p'},
+ /* 19 */ {'E', 't', 'h', 'i'},
+ /* 20 */ {'G', 'e', 'o', 'r'},
+ /* 21 */ {'G', 'o', 'n', 'g'},
+ /* 22 */ {'G', 'o', 'n', 'm'},
+ /* 23 */ {'G', 'o', 't', 'h'},
+ /* 24 */ {'G', 'r', 'e', 'k'},
+ /* 25 */ {'G', 'u', 'j', 'r'},
+ /* 26 */ {'G', 'u', 'r', 'u'},
+ /* 27 */ {'H', 'a', 'n', 's'},
+ /* 28 */ {'H', 'a', 'n', 't'},
+ /* 29 */ {'H', 'a', 't', 'r'},
+ /* 30 */ {'H', 'e', 'b', 'r'},
+ /* 31 */ {'H', 'l', 'u', 'w'},
+ /* 32 */ {'H', 'm', 'n', 'g'},
+ /* 33 */ {'H', 'm', 'n', 'p'},
+ /* 34 */ {'I', 't', 'a', 'l'},
+ /* 35 */ {'J', 'p', 'a', 'n'},
+ /* 36 */ {'K', 'a', 'l', 'i'},
+ /* 37 */ {'K', 'a', 'n', 'a'},
+ /* 38 */ {'K', 'h', 'a', 'r'},
+ /* 39 */ {'K', 'h', 'm', 'r'},
+ /* 40 */ {'K', 'n', 'd', 'a'},
+ /* 41 */ {'K', 'o', 'r', 'e'},
+ /* 42 */ {'L', 'a', 'n', 'a'},
+ /* 43 */ {'L', 'a', 'o', 'o'},
+ /* 44 */ {'L', 'a', 't', 'n'},
+ /* 45 */ {'L', 'e', 'p', 'c'},
+ /* 46 */ {'L', 'i', 'n', 'a'},
+ /* 47 */ {'L', 'i', 's', 'u'},
+ /* 48 */ {'L', 'y', 'c', 'i'},
+ /* 49 */ {'L', 'y', 'd', 'i'},
+ /* 50 */ {'M', 'a', 'n', 'd'},
+ /* 51 */ {'M', 'a', 'n', 'i'},
+ /* 52 */ {'M', 'e', 'r', 'c'},
+ /* 53 */ {'M', 'l', 'y', 'm'},
+ /* 54 */ {'M', 'o', 'n', 'g'},
+ /* 55 */ {'M', 'r', 'o', 'o'},
+ /* 56 */ {'M', 'y', 'm', 'r'},
+ /* 57 */ {'N', 'a', 'r', 'b'},
+ /* 58 */ {'N', 'k', 'o', 'o'},
+ /* 59 */ {'N', 's', 'h', 'u'},
+ /* 60 */ {'O', 'g', 'a', 'm'},
+ /* 61 */ {'O', 'r', 'k', 'h'},
+ /* 62 */ {'O', 'r', 'y', 'a'},
+ /* 63 */ {'O', 's', 'g', 'e'},
+ /* 64 */ {'P', 'a', 'u', 'c'},
+ /* 65 */ {'P', 'h', 'l', 'i'},
+ /* 66 */ {'P', 'h', 'n', 'x'},
+ /* 67 */ {'P', 'l', 'r', 'd'},
+ /* 68 */ {'P', 'r', 't', 'i'},
+ /* 69 */ {'R', 'u', 'n', 'r'},
+ /* 70 */ {'S', 'a', 'm', 'r'},
+ /* 71 */ {'S', 'a', 'r', 'b'},
+ /* 72 */ {'S', 'a', 'u', 'r'},
+ /* 73 */ {'S', 'g', 'n', 'w'},
+ /* 74 */ {'S', 'i', 'n', 'h'},
+ /* 75 */ {'S', 'o', 'g', 'd'},
+ /* 76 */ {'S', 'o', 'r', 'a'},
+ /* 77 */ {'S', 'o', 'y', 'o'},
+ /* 78 */ {'S', 'y', 'r', 'c'},
+ /* 79 */ {'T', 'a', 'l', 'e'},
+ /* 80 */ {'T', 'a', 'l', 'u'},
+ /* 81 */ {'T', 'a', 'm', 'l'},
+ /* 82 */ {'T', 'a', 'n', 'g'},
+ /* 83 */ {'T', 'a', 'v', 't'},
+ /* 84 */ {'T', 'e', 'l', 'u'},
+ /* 85 */ {'T', 'f', 'n', 'g'},
+ /* 86 */ {'T', 'h', 'a', 'a'},
+ /* 87 */ {'T', 'h', 'a', 'i'},
+ /* 88 */ {'T', 'i', 'b', 't'},
+ /* 89 */ {'U', 'g', 'a', 'r'},
+ /* 90 */ {'V', 'a', 'i', 'i'},
+ /* 91 */ {'W', 'c', 'h', 'o'},
+ /* 92 */ {'X', 'p', 'e', 'o'},
+ /* 93 */ {'X', 's', 'u', 'x'},
+ /* 94 */ {'Y', 'i', 'i', 'i'},
+ /* 95 */ {'~', '~', '~', 'A'},
+ /* 96 */ {'~', '~', '~', 'B'},
};
const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
- {0x61610000u, 40u}, // aa -> Latn
- {0xA0000000u, 40u}, // aai -> Latn
- {0xA8000000u, 40u}, // aak -> Latn
- {0xD0000000u, 40u}, // aau -> Latn
- {0x61620000u, 15u}, // ab -> Cyrl
- {0xA0200000u, 40u}, // abi -> Latn
- {0xC4200000u, 40u}, // abr -> Latn
- {0xCC200000u, 40u}, // abt -> Latn
- {0xE0200000u, 40u}, // aby -> Latn
- {0x8C400000u, 40u}, // acd -> Latn
- {0x90400000u, 40u}, // ace -> Latn
- {0x9C400000u, 40u}, // ach -> Latn
- {0x80600000u, 40u}, // ada -> Latn
- {0x90600000u, 40u}, // ade -> Latn
- {0xA4600000u, 40u}, // adj -> Latn
- {0xE0600000u, 15u}, // ady -> Cyrl
- {0xE4600000u, 40u}, // adz -> Latn
+ {0x61610000u, 44u}, // aa -> Latn
+ {0xA0000000u, 44u}, // aai -> Latn
+ {0xA8000000u, 44u}, // aak -> Latn
+ {0xD0000000u, 44u}, // aau -> Latn
+ {0x61620000u, 16u}, // ab -> Cyrl
+ {0xA0200000u, 44u}, // abi -> Latn
+ {0xC0200000u, 16u}, // abq -> Cyrl
+ {0xC4200000u, 44u}, // abr -> Latn
+ {0xCC200000u, 44u}, // abt -> Latn
+ {0xE0200000u, 44u}, // aby -> Latn
+ {0x8C400000u, 44u}, // acd -> Latn
+ {0x90400000u, 44u}, // ace -> Latn
+ {0x9C400000u, 44u}, // ach -> Latn
+ {0x80600000u, 44u}, // ada -> Latn
+ {0x90600000u, 44u}, // ade -> Latn
+ {0xA4600000u, 44u}, // adj -> Latn
+ {0xBC600000u, 88u}, // adp -> Tibt
+ {0xE0600000u, 16u}, // ady -> Cyrl
+ {0xE4600000u, 44u}, // adz -> Latn
{0x61650000u, 4u}, // ae -> Avst
{0x84800000u, 1u}, // aeb -> Arab
- {0xE0800000u, 40u}, // aey -> Latn
- {0x61660000u, 40u}, // af -> Latn
- {0x88C00000u, 40u}, // agc -> Latn
- {0x8CC00000u, 40u}, // agd -> Latn
- {0x98C00000u, 40u}, // agg -> Latn
- {0xB0C00000u, 40u}, // agm -> Latn
- {0xB8C00000u, 40u}, // ago -> Latn
- {0xC0C00000u, 40u}, // agq -> Latn
- {0x80E00000u, 40u}, // aha -> Latn
- {0xACE00000u, 40u}, // ahl -> Latn
+ {0xE0800000u, 44u}, // aey -> Latn
+ {0x61660000u, 44u}, // af -> Latn
+ {0x88C00000u, 44u}, // agc -> Latn
+ {0x8CC00000u, 44u}, // agd -> Latn
+ {0x98C00000u, 44u}, // agg -> Latn
+ {0xB0C00000u, 44u}, // agm -> Latn
+ {0xB8C00000u, 44u}, // ago -> Latn
+ {0xC0C00000u, 44u}, // agq -> Latn
+ {0x80E00000u, 44u}, // aha -> Latn
+ {0xACE00000u, 44u}, // ahl -> Latn
{0xB8E00000u, 0u}, // aho -> Ahom
- {0x99200000u, 40u}, // ajg -> Latn
- {0x616B0000u, 40u}, // ak -> Latn
- {0xA9400000u, 85u}, // akk -> Xsux
- {0x81600000u, 40u}, // ala -> Latn
- {0xA1600000u, 40u}, // ali -> Latn
- {0xB5600000u, 40u}, // aln -> Latn
- {0xCD600000u, 15u}, // alt -> Cyrl
- {0x616D0000u, 18u}, // am -> Ethi
- {0xB1800000u, 40u}, // amm -> Latn
- {0xB5800000u, 40u}, // amn -> Latn
- {0xB9800000u, 40u}, // amo -> Latn
- {0xBD800000u, 40u}, // amp -> Latn
- {0x89A00000u, 40u}, // anc -> Latn
- {0xA9A00000u, 40u}, // ank -> Latn
- {0xB5A00000u, 40u}, // ann -> Latn
- {0xE1A00000u, 40u}, // any -> Latn
- {0xA5C00000u, 40u}, // aoj -> Latn
- {0xB1C00000u, 40u}, // aom -> Latn
- {0xE5C00000u, 40u}, // aoz -> Latn
+ {0x99200000u, 44u}, // ajg -> Latn
+ {0x616B0000u, 44u}, // ak -> Latn
+ {0xA9400000u, 93u}, // akk -> Xsux
+ {0x81600000u, 44u}, // ala -> Latn
+ {0xA1600000u, 44u}, // ali -> Latn
+ {0xB5600000u, 44u}, // aln -> Latn
+ {0xCD600000u, 16u}, // alt -> Cyrl
+ {0x616D0000u, 19u}, // am -> Ethi
+ {0xB1800000u, 44u}, // amm -> Latn
+ {0xB5800000u, 44u}, // amn -> Latn
+ {0xB9800000u, 44u}, // amo -> Latn
+ {0xBD800000u, 44u}, // amp -> Latn
+ {0x616E0000u, 44u}, // an -> Latn
+ {0x89A00000u, 44u}, // anc -> Latn
+ {0xA9A00000u, 44u}, // ank -> Latn
+ {0xB5A00000u, 44u}, // ann -> Latn
+ {0xE1A00000u, 44u}, // any -> Latn
+ {0xA5C00000u, 44u}, // aoj -> Latn
+ {0xB1C00000u, 44u}, // aom -> Latn
+ {0xE5C00000u, 44u}, // aoz -> Latn
{0x89E00000u, 1u}, // apc -> Arab
{0x8DE00000u, 1u}, // apd -> Arab
- {0x91E00000u, 40u}, // ape -> Latn
- {0xC5E00000u, 40u}, // apr -> Latn
- {0xC9E00000u, 40u}, // aps -> Latn
- {0xE5E00000u, 40u}, // apz -> Latn
+ {0x91E00000u, 44u}, // ape -> Latn
+ {0xC5E00000u, 44u}, // apr -> Latn
+ {0xC9E00000u, 44u}, // aps -> Latn
+ {0xE5E00000u, 44u}, // apz -> Latn
{0x61720000u, 1u}, // ar -> Arab
- {0x61725842u, 88u}, // ar-XB -> ~~~B
+ {0x61725842u, 96u}, // ar-XB -> ~~~B
{0x8A200000u, 2u}, // arc -> Armi
- {0x9E200000u, 40u}, // arh -> Latn
- {0xB6200000u, 40u}, // arn -> Latn
- {0xBA200000u, 40u}, // aro -> Latn
+ {0x9E200000u, 44u}, // arh -> Latn
+ {0xB6200000u, 44u}, // arn -> Latn
+ {0xBA200000u, 44u}, // aro -> Latn
{0xC2200000u, 1u}, // arq -> Arab
+ {0xCA200000u, 1u}, // ars -> Arab
{0xE2200000u, 1u}, // ary -> Arab
{0xE6200000u, 1u}, // arz -> Arab
{0x61730000u, 7u}, // as -> Beng
- {0x82400000u, 40u}, // asa -> Latn
- {0x92400000u, 68u}, // ase -> Sgnw
- {0x9A400000u, 40u}, // asg -> Latn
- {0xBA400000u, 40u}, // aso -> Latn
- {0xCE400000u, 40u}, // ast -> Latn
- {0x82600000u, 40u}, // ata -> Latn
- {0x9A600000u, 40u}, // atg -> Latn
- {0xA6600000u, 40u}, // atj -> Latn
- {0xE2800000u, 40u}, // auy -> Latn
- {0x61760000u, 15u}, // av -> Cyrl
+ {0x82400000u, 44u}, // asa -> Latn
+ {0x92400000u, 73u}, // ase -> Sgnw
+ {0x9A400000u, 44u}, // asg -> Latn
+ {0xBA400000u, 44u}, // aso -> Latn
+ {0xCE400000u, 44u}, // ast -> Latn
+ {0x82600000u, 44u}, // ata -> Latn
+ {0x9A600000u, 44u}, // atg -> Latn
+ {0xA6600000u, 44u}, // atj -> Latn
+ {0xE2800000u, 44u}, // auy -> Latn
+ {0x61760000u, 16u}, // av -> Cyrl
{0xAEA00000u, 1u}, // avl -> Arab
- {0xB6A00000u, 40u}, // avn -> Latn
- {0xCEA00000u, 40u}, // avt -> Latn
- {0xD2A00000u, 40u}, // avu -> Latn
- {0x82C00000u, 16u}, // awa -> Deva
- {0x86C00000u, 40u}, // awb -> Latn
- {0xBAC00000u, 40u}, // awo -> Latn
- {0xDEC00000u, 40u}, // awx -> Latn
- {0x61790000u, 40u}, // ay -> Latn
- {0x87000000u, 40u}, // ayb -> Latn
- {0x617A0000u, 40u}, // az -> Latn
+ {0xB6A00000u, 44u}, // avn -> Latn
+ {0xCEA00000u, 44u}, // avt -> Latn
+ {0xD2A00000u, 44u}, // avu -> Latn
+ {0x82C00000u, 17u}, // awa -> Deva
+ {0x86C00000u, 44u}, // awb -> Latn
+ {0xBAC00000u, 44u}, // awo -> Latn
+ {0xDEC00000u, 44u}, // awx -> Latn
+ {0x61790000u, 44u}, // ay -> Latn
+ {0x87000000u, 44u}, // ayb -> Latn
+ {0x617A0000u, 44u}, // az -> Latn
{0x617A4951u, 1u}, // az-IQ -> Arab
{0x617A4952u, 1u}, // az-IR -> Arab
- {0x617A5255u, 15u}, // az-RU -> Cyrl
- {0x62610000u, 15u}, // ba -> Cyrl
+ {0x617A5255u, 16u}, // az-RU -> Cyrl
+ {0x62610000u, 16u}, // ba -> Cyrl
{0xAC010000u, 1u}, // bal -> Arab
- {0xB4010000u, 40u}, // ban -> Latn
- {0xBC010000u, 16u}, // bap -> Deva
- {0xC4010000u, 40u}, // bar -> Latn
- {0xC8010000u, 40u}, // bas -> Latn
- {0xD4010000u, 40u}, // bav -> Latn
+ {0xB4010000u, 44u}, // ban -> Latn
+ {0xBC010000u, 17u}, // bap -> Deva
+ {0xC4010000u, 44u}, // bar -> Latn
+ {0xC8010000u, 44u}, // bas -> Latn
+ {0xD4010000u, 44u}, // bav -> Latn
{0xDC010000u, 5u}, // bax -> Bamu
- {0x80210000u, 40u}, // bba -> Latn
- {0x84210000u, 40u}, // bbb -> Latn
- {0x88210000u, 40u}, // bbc -> Latn
- {0x8C210000u, 40u}, // bbd -> Latn
- {0xA4210000u, 40u}, // bbj -> Latn
- {0xBC210000u, 40u}, // bbp -> Latn
- {0xC4210000u, 40u}, // bbr -> Latn
- {0x94410000u, 40u}, // bcf -> Latn
- {0x9C410000u, 40u}, // bch -> Latn
- {0xA0410000u, 40u}, // bci -> Latn
- {0xB0410000u, 40u}, // bcm -> Latn
- {0xB4410000u, 40u}, // bcn -> Latn
- {0xB8410000u, 40u}, // bco -> Latn
- {0xC0410000u, 18u}, // bcq -> Ethi
- {0xD0410000u, 40u}, // bcu -> Latn
- {0x8C610000u, 40u}, // bdd -> Latn
- {0x62650000u, 15u}, // be -> Cyrl
- {0x94810000u, 40u}, // bef -> Latn
- {0x9C810000u, 40u}, // beh -> Latn
+ {0x80210000u, 44u}, // bba -> Latn
+ {0x84210000u, 44u}, // bbb -> Latn
+ {0x88210000u, 44u}, // bbc -> Latn
+ {0x8C210000u, 44u}, // bbd -> Latn
+ {0xA4210000u, 44u}, // bbj -> Latn
+ {0xBC210000u, 44u}, // bbp -> Latn
+ {0xC4210000u, 44u}, // bbr -> Latn
+ {0x94410000u, 44u}, // bcf -> Latn
+ {0x9C410000u, 44u}, // bch -> Latn
+ {0xA0410000u, 44u}, // bci -> Latn
+ {0xB0410000u, 44u}, // bcm -> Latn
+ {0xB4410000u, 44u}, // bcn -> Latn
+ {0xB8410000u, 44u}, // bco -> Latn
+ {0xC0410000u, 19u}, // bcq -> Ethi
+ {0xD0410000u, 44u}, // bcu -> Latn
+ {0x8C610000u, 44u}, // bdd -> Latn
+ {0x62650000u, 16u}, // be -> Cyrl
+ {0x94810000u, 44u}, // bef -> Latn
+ {0x9C810000u, 44u}, // beh -> Latn
{0xA4810000u, 1u}, // bej -> Arab
- {0xB0810000u, 40u}, // bem -> Latn
- {0xCC810000u, 40u}, // bet -> Latn
- {0xD8810000u, 40u}, // bew -> Latn
- {0xDC810000u, 40u}, // bex -> Latn
- {0xE4810000u, 40u}, // bez -> Latn
- {0x8CA10000u, 40u}, // bfd -> Latn
- {0xC0A10000u, 74u}, // bfq -> Taml
+ {0xB0810000u, 44u}, // bem -> Latn
+ {0xCC810000u, 44u}, // bet -> Latn
+ {0xD8810000u, 44u}, // bew -> Latn
+ {0xDC810000u, 44u}, // bex -> Latn
+ {0xE4810000u, 44u}, // bez -> Latn
+ {0x8CA10000u, 44u}, // bfd -> Latn
+ {0xC0A10000u, 81u}, // bfq -> Taml
{0xCCA10000u, 1u}, // bft -> Arab
- {0xE0A10000u, 16u}, // bfy -> Deva
- {0x62670000u, 15u}, // bg -> Cyrl
- {0x88C10000u, 16u}, // bgc -> Deva
+ {0xE0A10000u, 17u}, // bfy -> Deva
+ {0x62670000u, 16u}, // bg -> Cyrl
+ {0x88C10000u, 17u}, // bgc -> Deva
{0xB4C10000u, 1u}, // bgn -> Arab
- {0xDCC10000u, 21u}, // bgx -> Grek
- {0x84E10000u, 16u}, // bhb -> Deva
- {0x98E10000u, 40u}, // bhg -> Latn
- {0xA0E10000u, 16u}, // bhi -> Deva
- {0xA8E10000u, 40u}, // bhk -> Latn
- {0xACE10000u, 40u}, // bhl -> Latn
- {0xB8E10000u, 16u}, // bho -> Deva
- {0xE0E10000u, 40u}, // bhy -> Latn
- {0x62690000u, 40u}, // bi -> Latn
- {0x85010000u, 40u}, // bib -> Latn
- {0x99010000u, 40u}, // big -> Latn
- {0xA9010000u, 40u}, // bik -> Latn
- {0xB1010000u, 40u}, // bim -> Latn
- {0xB5010000u, 40u}, // bin -> Latn
- {0xB9010000u, 40u}, // bio -> Latn
- {0xC1010000u, 40u}, // biq -> Latn
- {0x9D210000u, 40u}, // bjh -> Latn
- {0xA1210000u, 18u}, // bji -> Ethi
- {0xA5210000u, 16u}, // bjj -> Deva
- {0xB5210000u, 40u}, // bjn -> Latn
- {0xB9210000u, 40u}, // bjo -> Latn
- {0xC5210000u, 40u}, // bjr -> Latn
- {0xE5210000u, 40u}, // bjz -> Latn
- {0x89410000u, 40u}, // bkc -> Latn
- {0xB1410000u, 40u}, // bkm -> Latn
- {0xC1410000u, 40u}, // bkq -> Latn
- {0xD1410000u, 40u}, // bku -> Latn
- {0xD5410000u, 40u}, // bkv -> Latn
- {0xCD610000u, 76u}, // blt -> Tavt
- {0x626D0000u, 40u}, // bm -> Latn
- {0x9D810000u, 40u}, // bmh -> Latn
- {0xA9810000u, 40u}, // bmk -> Latn
- {0xC1810000u, 40u}, // bmq -> Latn
- {0xD1810000u, 40u}, // bmu -> Latn
+ {0xDCC10000u, 24u}, // bgx -> Grek
+ {0x84E10000u, 17u}, // bhb -> Deva
+ {0x98E10000u, 44u}, // bhg -> Latn
+ {0xA0E10000u, 17u}, // bhi -> Deva
+ {0xACE10000u, 44u}, // bhl -> Latn
+ {0xB8E10000u, 17u}, // bho -> Deva
+ {0xE0E10000u, 44u}, // bhy -> Latn
+ {0x62690000u, 44u}, // bi -> Latn
+ {0x85010000u, 44u}, // bib -> Latn
+ {0x99010000u, 44u}, // big -> Latn
+ {0xA9010000u, 44u}, // bik -> Latn
+ {0xB1010000u, 44u}, // bim -> Latn
+ {0xB5010000u, 44u}, // bin -> Latn
+ {0xB9010000u, 44u}, // bio -> Latn
+ {0xC1010000u, 44u}, // biq -> Latn
+ {0x9D210000u, 44u}, // bjh -> Latn
+ {0xA1210000u, 19u}, // bji -> Ethi
+ {0xA5210000u, 17u}, // bjj -> Deva
+ {0xB5210000u, 44u}, // bjn -> Latn
+ {0xB9210000u, 44u}, // bjo -> Latn
+ {0xC5210000u, 44u}, // bjr -> Latn
+ {0xCD210000u, 44u}, // bjt -> Latn
+ {0xE5210000u, 44u}, // bjz -> Latn
+ {0x89410000u, 44u}, // bkc -> Latn
+ {0xB1410000u, 44u}, // bkm -> Latn
+ {0xC1410000u, 44u}, // bkq -> Latn
+ {0xD1410000u, 44u}, // bku -> Latn
+ {0xD5410000u, 44u}, // bkv -> Latn
+ {0xCD610000u, 83u}, // blt -> Tavt
+ {0x626D0000u, 44u}, // bm -> Latn
+ {0x9D810000u, 44u}, // bmh -> Latn
+ {0xA9810000u, 44u}, // bmk -> Latn
+ {0xC1810000u, 44u}, // bmq -> Latn
+ {0xD1810000u, 44u}, // bmu -> Latn
{0x626E0000u, 7u}, // bn -> Beng
- {0x99A10000u, 40u}, // bng -> Latn
- {0xB1A10000u, 40u}, // bnm -> Latn
- {0xBDA10000u, 40u}, // bnp -> Latn
- {0x626F0000u, 81u}, // bo -> Tibt
- {0xA5C10000u, 40u}, // boj -> Latn
- {0xB1C10000u, 40u}, // bom -> Latn
- {0xB5C10000u, 40u}, // bon -> Latn
+ {0x99A10000u, 44u}, // bng -> Latn
+ {0xB1A10000u, 44u}, // bnm -> Latn
+ {0xBDA10000u, 44u}, // bnp -> Latn
+ {0x626F0000u, 88u}, // bo -> Tibt
+ {0xA5C10000u, 44u}, // boj -> Latn
+ {0xB1C10000u, 44u}, // bom -> Latn
+ {0xB5C10000u, 44u}, // bon -> Latn
{0xE1E10000u, 7u}, // bpy -> Beng
- {0x8A010000u, 40u}, // bqc -> Latn
+ {0x8A010000u, 44u}, // bqc -> Latn
{0xA2010000u, 1u}, // bqi -> Arab
- {0xBE010000u, 40u}, // bqp -> Latn
- {0xD6010000u, 40u}, // bqv -> Latn
- {0x62720000u, 40u}, // br -> Latn
- {0x82210000u, 16u}, // bra -> Deva
+ {0xBE010000u, 44u}, // bqp -> Latn
+ {0xD6010000u, 44u}, // bqv -> Latn
+ {0x62720000u, 44u}, // br -> Latn
+ {0x82210000u, 17u}, // bra -> Deva
{0x9E210000u, 1u}, // brh -> Arab
- {0xDE210000u, 16u}, // brx -> Deva
- {0xE6210000u, 40u}, // brz -> Latn
- {0x62730000u, 40u}, // bs -> Latn
- {0xA6410000u, 40u}, // bsj -> Latn
+ {0xDE210000u, 17u}, // brx -> Deva
+ {0xE6210000u, 44u}, // brz -> Latn
+ {0x62730000u, 44u}, // bs -> Latn
+ {0xA6410000u, 44u}, // bsj -> Latn
{0xC2410000u, 6u}, // bsq -> Bass
- {0xCA410000u, 40u}, // bss -> Latn
- {0xCE410000u, 18u}, // bst -> Ethi
- {0xBA610000u, 40u}, // bto -> Latn
- {0xCE610000u, 40u}, // btt -> Latn
- {0xD6610000u, 16u}, // btv -> Deva
- {0x82810000u, 15u}, // bua -> Cyrl
- {0x8A810000u, 40u}, // buc -> Latn
- {0x8E810000u, 40u}, // bud -> Latn
- {0x9A810000u, 40u}, // bug -> Latn
- {0xAA810000u, 40u}, // buk -> Latn
- {0xB2810000u, 40u}, // bum -> Latn
- {0xBA810000u, 40u}, // buo -> Latn
- {0xCA810000u, 40u}, // bus -> Latn
- {0xD2810000u, 40u}, // buu -> Latn
- {0x86A10000u, 40u}, // bvb -> Latn
- {0x8EC10000u, 40u}, // bwd -> Latn
- {0xC6C10000u, 40u}, // bwr -> Latn
- {0x9EE10000u, 40u}, // bxh -> Latn
- {0x93010000u, 40u}, // bye -> Latn
- {0xB7010000u, 18u}, // byn -> Ethi
- {0xC7010000u, 40u}, // byr -> Latn
- {0xCB010000u, 40u}, // bys -> Latn
- {0xD7010000u, 40u}, // byv -> Latn
- {0xDF010000u, 40u}, // byx -> Latn
- {0x83210000u, 40u}, // bza -> Latn
- {0x93210000u, 40u}, // bze -> Latn
- {0x97210000u, 40u}, // bzf -> Latn
- {0x9F210000u, 40u}, // bzh -> Latn
- {0xDB210000u, 40u}, // bzw -> Latn
- {0x63610000u, 40u}, // ca -> Latn
- {0xB4020000u, 40u}, // can -> Latn
- {0xA4220000u, 40u}, // cbj -> Latn
- {0x9C420000u, 40u}, // cch -> Latn
- {0xBC420000u, 7u}, // ccp -> Beng
- {0x63650000u, 15u}, // ce -> Cyrl
- {0x84820000u, 40u}, // ceb -> Latn
- {0x80A20000u, 40u}, // cfa -> Latn
- {0x98C20000u, 40u}, // cgg -> Latn
- {0x63680000u, 40u}, // ch -> Latn
- {0xA8E20000u, 40u}, // chk -> Latn
- {0xB0E20000u, 15u}, // chm -> Cyrl
- {0xB8E20000u, 40u}, // cho -> Latn
- {0xBCE20000u, 40u}, // chp -> Latn
- {0xC4E20000u, 12u}, // chr -> Cher
+ {0xCA410000u, 44u}, // bss -> Latn
+ {0xCE410000u, 19u}, // bst -> Ethi
+ {0xBA610000u, 44u}, // bto -> Latn
+ {0xCE610000u, 44u}, // btt -> Latn
+ {0xD6610000u, 17u}, // btv -> Deva
+ {0x82810000u, 16u}, // bua -> Cyrl
+ {0x8A810000u, 44u}, // buc -> Latn
+ {0x8E810000u, 44u}, // bud -> Latn
+ {0x9A810000u, 44u}, // bug -> Latn
+ {0xAA810000u, 44u}, // buk -> Latn
+ {0xB2810000u, 44u}, // bum -> Latn
+ {0xBA810000u, 44u}, // buo -> Latn
+ {0xCA810000u, 44u}, // bus -> Latn
+ {0xD2810000u, 44u}, // buu -> Latn
+ {0x86A10000u, 44u}, // bvb -> Latn
+ {0x8EC10000u, 44u}, // bwd -> Latn
+ {0xC6C10000u, 44u}, // bwr -> Latn
+ {0x9EE10000u, 44u}, // bxh -> Latn
+ {0x93010000u, 44u}, // bye -> Latn
+ {0xB7010000u, 19u}, // byn -> Ethi
+ {0xC7010000u, 44u}, // byr -> Latn
+ {0xCB010000u, 44u}, // bys -> Latn
+ {0xD7010000u, 44u}, // byv -> Latn
+ {0xDF010000u, 44u}, // byx -> Latn
+ {0x83210000u, 44u}, // bza -> Latn
+ {0x93210000u, 44u}, // bze -> Latn
+ {0x97210000u, 44u}, // bzf -> Latn
+ {0x9F210000u, 44u}, // bzh -> Latn
+ {0xDB210000u, 44u}, // bzw -> Latn
+ {0x63610000u, 44u}, // ca -> Latn
+ {0xB4020000u, 44u}, // can -> Latn
+ {0xA4220000u, 44u}, // cbj -> Latn
+ {0x9C420000u, 44u}, // cch -> Latn
+ {0xBC420000u, 9u}, // ccp -> Cakm
+ {0x63650000u, 16u}, // ce -> Cyrl
+ {0x84820000u, 44u}, // ceb -> Latn
+ {0x80A20000u, 44u}, // cfa -> Latn
+ {0x98C20000u, 44u}, // cgg -> Latn
+ {0x63680000u, 44u}, // ch -> Latn
+ {0xA8E20000u, 44u}, // chk -> Latn
+ {0xB0E20000u, 16u}, // chm -> Cyrl
+ {0xB8E20000u, 44u}, // cho -> Latn
+ {0xBCE20000u, 44u}, // chp -> Latn
+ {0xC4E20000u, 13u}, // chr -> Cher
+ {0x89020000u, 44u}, // cic -> Latn
{0x81220000u, 1u}, // cja -> Arab
- {0xB1220000u, 11u}, // cjm -> Cham
- {0xD5220000u, 40u}, // cjv -> Latn
+ {0xB1220000u, 12u}, // cjm -> Cham
+ {0xD5220000u, 44u}, // cjv -> Latn
{0x85420000u, 1u}, // ckb -> Arab
- {0xAD420000u, 40u}, // ckl -> Latn
- {0xB9420000u, 40u}, // cko -> Latn
- {0xE1420000u, 40u}, // cky -> Latn
- {0x81620000u, 40u}, // cla -> Latn
- {0x91820000u, 40u}, // cme -> Latn
- {0x636F0000u, 40u}, // co -> Latn
- {0xBDC20000u, 13u}, // cop -> Copt
- {0xC9E20000u, 40u}, // cps -> Latn
- {0x63720000u, 9u}, // cr -> Cans
- {0xA6220000u, 9u}, // crj -> Cans
- {0xAA220000u, 9u}, // crk -> Cans
- {0xAE220000u, 9u}, // crl -> Cans
- {0xB2220000u, 9u}, // crm -> Cans
- {0xCA220000u, 40u}, // crs -> Latn
- {0x63730000u, 40u}, // cs -> Latn
- {0x86420000u, 40u}, // csb -> Latn
- {0xDA420000u, 9u}, // csw -> Cans
- {0x8E620000u, 59u}, // ctd -> Pauc
- {0x63750000u, 15u}, // cu -> Cyrl
- {0x63760000u, 15u}, // cv -> Cyrl
- {0x63790000u, 40u}, // cy -> Latn
- {0x64610000u, 40u}, // da -> Latn
- {0x8C030000u, 40u}, // dad -> Latn
- {0x94030000u, 40u}, // daf -> Latn
- {0x98030000u, 40u}, // dag -> Latn
- {0x9C030000u, 40u}, // dah -> Latn
- {0xA8030000u, 40u}, // dak -> Latn
- {0xC4030000u, 15u}, // dar -> Cyrl
- {0xD4030000u, 40u}, // dav -> Latn
- {0x8C230000u, 40u}, // dbd -> Latn
- {0xC0230000u, 40u}, // dbq -> Latn
+ {0xAD420000u, 44u}, // ckl -> Latn
+ {0xB9420000u, 44u}, // cko -> Latn
+ {0xE1420000u, 44u}, // cky -> Latn
+ {0x81620000u, 44u}, // cla -> Latn
+ {0x91820000u, 44u}, // cme -> Latn
+ {0x99820000u, 77u}, // cmg -> Soyo
+ {0x636F0000u, 44u}, // co -> Latn
+ {0xBDC20000u, 14u}, // cop -> Copt
+ {0xC9E20000u, 44u}, // cps -> Latn
+ {0x63720000u, 10u}, // cr -> Cans
+ {0x9E220000u, 16u}, // crh -> Cyrl
+ {0xA6220000u, 10u}, // crj -> Cans
+ {0xAA220000u, 10u}, // crk -> Cans
+ {0xAE220000u, 10u}, // crl -> Cans
+ {0xB2220000u, 10u}, // crm -> Cans
+ {0xCA220000u, 44u}, // crs -> Latn
+ {0x63730000u, 44u}, // cs -> Latn
+ {0x86420000u, 44u}, // csb -> Latn
+ {0xDA420000u, 10u}, // csw -> Cans
+ {0x8E620000u, 64u}, // ctd -> Pauc
+ {0x63750000u, 16u}, // cu -> Cyrl
+ {0x63760000u, 16u}, // cv -> Cyrl
+ {0x63790000u, 44u}, // cy -> Latn
+ {0x64610000u, 44u}, // da -> Latn
+ {0x8C030000u, 44u}, // dad -> Latn
+ {0x94030000u, 44u}, // daf -> Latn
+ {0x98030000u, 44u}, // dag -> Latn
+ {0x9C030000u, 44u}, // dah -> Latn
+ {0xA8030000u, 44u}, // dak -> Latn
+ {0xC4030000u, 16u}, // dar -> Cyrl
+ {0xD4030000u, 44u}, // dav -> Latn
+ {0x8C230000u, 44u}, // dbd -> Latn
+ {0xC0230000u, 44u}, // dbq -> Latn
{0x88430000u, 1u}, // dcc -> Arab
- {0xB4630000u, 40u}, // ddn -> Latn
- {0x64650000u, 40u}, // de -> Latn
- {0x8C830000u, 40u}, // ded -> Latn
- {0xB4830000u, 40u}, // den -> Latn
- {0x80C30000u, 40u}, // dga -> Latn
- {0x9CC30000u, 40u}, // dgh -> Latn
- {0xA0C30000u, 40u}, // dgi -> Latn
+ {0xB4630000u, 44u}, // ddn -> Latn
+ {0x64650000u, 44u}, // de -> Latn
+ {0x8C830000u, 44u}, // ded -> Latn
+ {0xB4830000u, 44u}, // den -> Latn
+ {0x80C30000u, 44u}, // dga -> Latn
+ {0x9CC30000u, 44u}, // dgh -> Latn
+ {0xA0C30000u, 44u}, // dgi -> Latn
{0xACC30000u, 1u}, // dgl -> Arab
- {0xC4C30000u, 40u}, // dgr -> Latn
- {0xE4C30000u, 40u}, // dgz -> Latn
- {0x81030000u, 40u}, // dia -> Latn
- {0x91230000u, 40u}, // dje -> Latn
- {0xA5A30000u, 40u}, // dnj -> Latn
- {0x85C30000u, 40u}, // dob -> Latn
+ {0xC4C30000u, 44u}, // dgr -> Latn
+ {0xE4C30000u, 44u}, // dgz -> Latn
+ {0x81030000u, 44u}, // dia -> Latn
+ {0x91230000u, 44u}, // dje -> Latn
+ {0xA5A30000u, 44u}, // dnj -> Latn
+ {0x85C30000u, 44u}, // dob -> Latn
{0xA1C30000u, 1u}, // doi -> Arab
- {0xBDC30000u, 40u}, // dop -> Latn
- {0xD9C30000u, 40u}, // dow -> Latn
- {0xA2230000u, 40u}, // dri -> Latn
- {0xCA230000u, 18u}, // drs -> Ethi
- {0x86430000u, 40u}, // dsb -> Latn
- {0xB2630000u, 40u}, // dtm -> Latn
- {0xBE630000u, 40u}, // dtp -> Latn
- {0xCA630000u, 40u}, // dts -> Latn
- {0xE2630000u, 16u}, // dty -> Deva
- {0x82830000u, 40u}, // dua -> Latn
- {0x8A830000u, 40u}, // duc -> Latn
- {0x8E830000u, 40u}, // dud -> Latn
- {0x9A830000u, 40u}, // dug -> Latn
- {0x64760000u, 79u}, // dv -> Thaa
- {0x82A30000u, 40u}, // dva -> Latn
- {0xDAC30000u, 40u}, // dww -> Latn
- {0xBB030000u, 40u}, // dyo -> Latn
- {0xD3030000u, 40u}, // dyu -> Latn
- {0x647A0000u, 81u}, // dz -> Tibt
- {0x9B230000u, 40u}, // dzg -> Latn
- {0xD0240000u, 40u}, // ebu -> Latn
- {0x65650000u, 40u}, // ee -> Latn
- {0xA0A40000u, 40u}, // efi -> Latn
- {0xACC40000u, 40u}, // egl -> Latn
- {0xE0C40000u, 17u}, // egy -> Egyp
- {0xE1440000u, 32u}, // eky -> Kali
- {0x656C0000u, 21u}, // el -> Grek
- {0x81840000u, 40u}, // ema -> Latn
- {0xA1840000u, 40u}, // emi -> Latn
- {0x656E0000u, 40u}, // en -> Latn
- {0x656E5841u, 87u}, // en-XA -> ~~~A
- {0xB5A40000u, 40u}, // enn -> Latn
- {0xC1A40000u, 40u}, // enq -> Latn
- {0x656F0000u, 40u}, // eo -> Latn
- {0xA2240000u, 40u}, // eri -> Latn
- {0x65730000u, 40u}, // es -> Latn
- {0xD2440000u, 40u}, // esu -> Latn
- {0x65740000u, 40u}, // et -> Latn
- {0xC6640000u, 40u}, // etr -> Latn
- {0xCE640000u, 30u}, // ett -> Ital
- {0xD2640000u, 40u}, // etu -> Latn
- {0xDE640000u, 40u}, // etx -> Latn
- {0x65750000u, 40u}, // eu -> Latn
- {0xBAC40000u, 40u}, // ewo -> Latn
- {0xCEE40000u, 40u}, // ext -> Latn
+ {0xBDC30000u, 44u}, // dop -> Latn
+ {0xD9C30000u, 44u}, // dow -> Latn
+ {0x9E230000u, 54u}, // drh -> Mong
+ {0xA2230000u, 44u}, // dri -> Latn
+ {0xCA230000u, 19u}, // drs -> Ethi
+ {0x86430000u, 44u}, // dsb -> Latn
+ {0xB2630000u, 44u}, // dtm -> Latn
+ {0xBE630000u, 44u}, // dtp -> Latn
+ {0xCA630000u, 44u}, // dts -> Latn
+ {0xE2630000u, 17u}, // dty -> Deva
+ {0x82830000u, 44u}, // dua -> Latn
+ {0x8A830000u, 44u}, // duc -> Latn
+ {0x8E830000u, 44u}, // dud -> Latn
+ {0x9A830000u, 44u}, // dug -> Latn
+ {0x64760000u, 86u}, // dv -> Thaa
+ {0x82A30000u, 44u}, // dva -> Latn
+ {0xDAC30000u, 44u}, // dww -> Latn
+ {0xBB030000u, 44u}, // dyo -> Latn
+ {0xD3030000u, 44u}, // dyu -> Latn
+ {0x647A0000u, 88u}, // dz -> Tibt
+ {0x9B230000u, 44u}, // dzg -> Latn
+ {0xD0240000u, 44u}, // ebu -> Latn
+ {0x65650000u, 44u}, // ee -> Latn
+ {0xA0A40000u, 44u}, // efi -> Latn
+ {0xACC40000u, 44u}, // egl -> Latn
+ {0xE0C40000u, 18u}, // egy -> Egyp
+ {0x81440000u, 44u}, // eka -> Latn
+ {0xE1440000u, 36u}, // eky -> Kali
+ {0x656C0000u, 24u}, // el -> Grek
+ {0x81840000u, 44u}, // ema -> Latn
+ {0xA1840000u, 44u}, // emi -> Latn
+ {0x656E0000u, 44u}, // en -> Latn
+ {0x656E5841u, 95u}, // en-XA -> ~~~A
+ {0xB5A40000u, 44u}, // enn -> Latn
+ {0xC1A40000u, 44u}, // enq -> Latn
+ {0x656F0000u, 44u}, // eo -> Latn
+ {0xA2240000u, 44u}, // eri -> Latn
+ {0x65730000u, 44u}, // es -> Latn
+ {0x9A440000u, 22u}, // esg -> Gonm
+ {0xD2440000u, 44u}, // esu -> Latn
+ {0x65740000u, 44u}, // et -> Latn
+ {0xC6640000u, 44u}, // etr -> Latn
+ {0xCE640000u, 34u}, // ett -> Ital
+ {0xD2640000u, 44u}, // etu -> Latn
+ {0xDE640000u, 44u}, // etx -> Latn
+ {0x65750000u, 44u}, // eu -> Latn
+ {0xBAC40000u, 44u}, // ewo -> Latn
+ {0xCEE40000u, 44u}, // ext -> Latn
{0x66610000u, 1u}, // fa -> Arab
- {0x80050000u, 40u}, // faa -> Latn
- {0x84050000u, 40u}, // fab -> Latn
- {0x98050000u, 40u}, // fag -> Latn
- {0xA0050000u, 40u}, // fai -> Latn
- {0xB4050000u, 40u}, // fan -> Latn
- {0x66660000u, 40u}, // ff -> Latn
- {0xA0A50000u, 40u}, // ffi -> Latn
- {0xB0A50000u, 40u}, // ffm -> Latn
- {0x66690000u, 40u}, // fi -> Latn
+ {0x80050000u, 44u}, // faa -> Latn
+ {0x84050000u, 44u}, // fab -> Latn
+ {0x98050000u, 44u}, // fag -> Latn
+ {0xA0050000u, 44u}, // fai -> Latn
+ {0xB4050000u, 44u}, // fan -> Latn
+ {0x66660000u, 44u}, // ff -> Latn
+ {0xA0A50000u, 44u}, // ffi -> Latn
+ {0xB0A50000u, 44u}, // ffm -> Latn
+ {0x66690000u, 44u}, // fi -> Latn
{0x81050000u, 1u}, // fia -> Arab
- {0xAD050000u, 40u}, // fil -> Latn
- {0xCD050000u, 40u}, // fit -> Latn
- {0x666A0000u, 40u}, // fj -> Latn
- {0xC5650000u, 40u}, // flr -> Latn
- {0xBD850000u, 40u}, // fmp -> Latn
- {0x666F0000u, 40u}, // fo -> Latn
- {0x8DC50000u, 40u}, // fod -> Latn
- {0xB5C50000u, 40u}, // fon -> Latn
- {0xC5C50000u, 40u}, // for -> Latn
- {0x91E50000u, 40u}, // fpe -> Latn
- {0xCA050000u, 40u}, // fqs -> Latn
- {0x66720000u, 40u}, // fr -> Latn
- {0x8A250000u, 40u}, // frc -> Latn
- {0xBE250000u, 40u}, // frp -> Latn
- {0xC6250000u, 40u}, // frr -> Latn
- {0xCA250000u, 40u}, // frs -> Latn
+ {0xAD050000u, 44u}, // fil -> Latn
+ {0xCD050000u, 44u}, // fit -> Latn
+ {0x666A0000u, 44u}, // fj -> Latn
+ {0xC5650000u, 44u}, // flr -> Latn
+ {0xBD850000u, 44u}, // fmp -> Latn
+ {0x666F0000u, 44u}, // fo -> Latn
+ {0x8DC50000u, 44u}, // fod -> Latn
+ {0xB5C50000u, 44u}, // fon -> Latn
+ {0xC5C50000u, 44u}, // for -> Latn
+ {0x91E50000u, 44u}, // fpe -> Latn
+ {0xCA050000u, 44u}, // fqs -> Latn
+ {0x66720000u, 44u}, // fr -> Latn
+ {0x8A250000u, 44u}, // frc -> Latn
+ {0xBE250000u, 44u}, // frp -> Latn
+ {0xC6250000u, 44u}, // frr -> Latn
+ {0xCA250000u, 44u}, // frs -> Latn
{0x86850000u, 1u}, // fub -> Arab
- {0x8E850000u, 40u}, // fud -> Latn
- {0x92850000u, 40u}, // fue -> Latn
- {0x96850000u, 40u}, // fuf -> Latn
- {0x9E850000u, 40u}, // fuh -> Latn
- {0xC2850000u, 40u}, // fuq -> Latn
- {0xC6850000u, 40u}, // fur -> Latn
- {0xD6850000u, 40u}, // fuv -> Latn
- {0xE2850000u, 40u}, // fuy -> Latn
- {0xC6A50000u, 40u}, // fvr -> Latn
- {0x66790000u, 40u}, // fy -> Latn
- {0x67610000u, 40u}, // ga -> Latn
- {0x80060000u, 40u}, // gaa -> Latn
- {0x94060000u, 40u}, // gaf -> Latn
- {0x98060000u, 40u}, // gag -> Latn
- {0x9C060000u, 40u}, // gah -> Latn
- {0xA4060000u, 40u}, // gaj -> Latn
- {0xB0060000u, 40u}, // gam -> Latn
- {0xB4060000u, 24u}, // gan -> Hans
- {0xD8060000u, 40u}, // gaw -> Latn
- {0xE0060000u, 40u}, // gay -> Latn
- {0x94260000u, 40u}, // gbf -> Latn
- {0xB0260000u, 16u}, // gbm -> Deva
- {0xE0260000u, 40u}, // gby -> Latn
+ {0x8E850000u, 44u}, // fud -> Latn
+ {0x92850000u, 44u}, // fue -> Latn
+ {0x96850000u, 44u}, // fuf -> Latn
+ {0x9E850000u, 44u}, // fuh -> Latn
+ {0xC2850000u, 44u}, // fuq -> Latn
+ {0xC6850000u, 44u}, // fur -> Latn
+ {0xD6850000u, 44u}, // fuv -> Latn
+ {0xE2850000u, 44u}, // fuy -> Latn
+ {0xC6A50000u, 44u}, // fvr -> Latn
+ {0x66790000u, 44u}, // fy -> Latn
+ {0x67610000u, 44u}, // ga -> Latn
+ {0x80060000u, 44u}, // gaa -> Latn
+ {0x94060000u, 44u}, // gaf -> Latn
+ {0x98060000u, 44u}, // gag -> Latn
+ {0x9C060000u, 44u}, // gah -> Latn
+ {0xA4060000u, 44u}, // gaj -> Latn
+ {0xB0060000u, 44u}, // gam -> Latn
+ {0xB4060000u, 27u}, // gan -> Hans
+ {0xD8060000u, 44u}, // gaw -> Latn
+ {0xE0060000u, 44u}, // gay -> Latn
+ {0x80260000u, 44u}, // gba -> Latn
+ {0x94260000u, 44u}, // gbf -> Latn
+ {0xB0260000u, 17u}, // gbm -> Deva
+ {0xE0260000u, 44u}, // gby -> Latn
{0xE4260000u, 1u}, // gbz -> Arab
- {0xC4460000u, 40u}, // gcr -> Latn
- {0x67640000u, 40u}, // gd -> Latn
- {0x90660000u, 40u}, // gde -> Latn
- {0xB4660000u, 40u}, // gdn -> Latn
- {0xC4660000u, 40u}, // gdr -> Latn
- {0x84860000u, 40u}, // geb -> Latn
- {0xA4860000u, 40u}, // gej -> Latn
- {0xAC860000u, 40u}, // gel -> Latn
- {0xE4860000u, 18u}, // gez -> Ethi
- {0xA8A60000u, 40u}, // gfk -> Latn
- {0xB4C60000u, 16u}, // ggn -> Deva
- {0xC8E60000u, 40u}, // ghs -> Latn
- {0xAD060000u, 40u}, // gil -> Latn
- {0xB1060000u, 40u}, // gim -> Latn
+ {0xC4460000u, 44u}, // gcr -> Latn
+ {0x67640000u, 44u}, // gd -> Latn
+ {0x90660000u, 44u}, // gde -> Latn
+ {0xB4660000u, 44u}, // gdn -> Latn
+ {0xC4660000u, 44u}, // gdr -> Latn
+ {0x84860000u, 44u}, // geb -> Latn
+ {0xA4860000u, 44u}, // gej -> Latn
+ {0xAC860000u, 44u}, // gel -> Latn
+ {0xE4860000u, 19u}, // gez -> Ethi
+ {0xA8A60000u, 44u}, // gfk -> Latn
+ {0xB4C60000u, 17u}, // ggn -> Deva
+ {0xC8E60000u, 44u}, // ghs -> Latn
+ {0xAD060000u, 44u}, // gil -> Latn
+ {0xB1060000u, 44u}, // gim -> Latn
{0xA9260000u, 1u}, // gjk -> Arab
- {0xB5260000u, 40u}, // gjn -> Latn
+ {0xB5260000u, 44u}, // gjn -> Latn
{0xD1260000u, 1u}, // gju -> Arab
- {0xB5460000u, 40u}, // gkn -> Latn
- {0xBD460000u, 40u}, // gkp -> Latn
- {0x676C0000u, 40u}, // gl -> Latn
+ {0xB5460000u, 44u}, // gkn -> Latn
+ {0xBD460000u, 44u}, // gkp -> Latn
+ {0x676C0000u, 44u}, // gl -> Latn
{0xA9660000u, 1u}, // glk -> Arab
- {0xB1860000u, 40u}, // gmm -> Latn
- {0xD5860000u, 18u}, // gmv -> Ethi
- {0x676E0000u, 40u}, // gn -> Latn
- {0x8DA60000u, 40u}, // gnd -> Latn
- {0x99A60000u, 40u}, // gng -> Latn
- {0x8DC60000u, 40u}, // god -> Latn
- {0x95C60000u, 18u}, // gof -> Ethi
- {0xA1C60000u, 40u}, // goi -> Latn
- {0xB1C60000u, 16u}, // gom -> Deva
- {0xB5C60000u, 77u}, // gon -> Telu
- {0xC5C60000u, 40u}, // gor -> Latn
- {0xC9C60000u, 40u}, // gos -> Latn
- {0xCDC60000u, 20u}, // got -> Goth
- {0x8A260000u, 14u}, // grc -> Cprt
+ {0xB1860000u, 44u}, // gmm -> Latn
+ {0xD5860000u, 19u}, // gmv -> Ethi
+ {0x676E0000u, 44u}, // gn -> Latn
+ {0x8DA60000u, 44u}, // gnd -> Latn
+ {0x99A60000u, 44u}, // gng -> Latn
+ {0x8DC60000u, 44u}, // god -> Latn
+ {0x95C60000u, 19u}, // gof -> Ethi
+ {0xA1C60000u, 44u}, // goi -> Latn
+ {0xB1C60000u, 17u}, // gom -> Deva
+ {0xB5C60000u, 84u}, // gon -> Telu
+ {0xC5C60000u, 44u}, // gor -> Latn
+ {0xC9C60000u, 44u}, // gos -> Latn
+ {0xCDC60000u, 23u}, // got -> Goth
+ {0x86260000u, 44u}, // grb -> Latn
+ {0x8A260000u, 15u}, // grc -> Cprt
{0xCE260000u, 7u}, // grt -> Beng
- {0xDA260000u, 40u}, // grw -> Latn
- {0xDA460000u, 40u}, // gsw -> Latn
- {0x67750000u, 22u}, // gu -> Gujr
- {0x86860000u, 40u}, // gub -> Latn
- {0x8A860000u, 40u}, // guc -> Latn
- {0x8E860000u, 40u}, // gud -> Latn
- {0xC6860000u, 40u}, // gur -> Latn
- {0xDA860000u, 40u}, // guw -> Latn
- {0xDE860000u, 40u}, // gux -> Latn
- {0xE6860000u, 40u}, // guz -> Latn
- {0x67760000u, 40u}, // gv -> Latn
- {0x96A60000u, 40u}, // gvf -> Latn
- {0xC6A60000u, 16u}, // gvr -> Deva
- {0xCAA60000u, 40u}, // gvs -> Latn
+ {0xDA260000u, 44u}, // grw -> Latn
+ {0xDA460000u, 44u}, // gsw -> Latn
+ {0x67750000u, 25u}, // gu -> Gujr
+ {0x86860000u, 44u}, // gub -> Latn
+ {0x8A860000u, 44u}, // guc -> Latn
+ {0x8E860000u, 44u}, // gud -> Latn
+ {0xC6860000u, 44u}, // gur -> Latn
+ {0xDA860000u, 44u}, // guw -> Latn
+ {0xDE860000u, 44u}, // gux -> Latn
+ {0xE6860000u, 44u}, // guz -> Latn
+ {0x67760000u, 44u}, // gv -> Latn
+ {0x96A60000u, 44u}, // gvf -> Latn
+ {0xC6A60000u, 17u}, // gvr -> Deva
+ {0xCAA60000u, 44u}, // gvs -> Latn
{0x8AC60000u, 1u}, // gwc -> Arab
- {0xA2C60000u, 40u}, // gwi -> Latn
+ {0xA2C60000u, 44u}, // gwi -> Latn
{0xCEC60000u, 1u}, // gwt -> Arab
- {0xA3060000u, 40u}, // gyi -> Latn
- {0x68610000u, 40u}, // ha -> Latn
+ {0xA3060000u, 44u}, // gyi -> Latn
+ {0x68610000u, 44u}, // ha -> Latn
{0x6861434Du, 1u}, // ha-CM -> Arab
{0x68615344u, 1u}, // ha-SD -> Arab
- {0x98070000u, 40u}, // hag -> Latn
- {0xA8070000u, 24u}, // hak -> Hans
- {0xB0070000u, 40u}, // ham -> Latn
- {0xD8070000u, 40u}, // haw -> Latn
+ {0x98070000u, 44u}, // hag -> Latn
+ {0xA8070000u, 27u}, // hak -> Hans
+ {0xB0070000u, 44u}, // ham -> Latn
+ {0xD8070000u, 44u}, // haw -> Latn
{0xE4070000u, 1u}, // haz -> Arab
- {0x84270000u, 40u}, // hbb -> Latn
- {0xE0670000u, 18u}, // hdy -> Ethi
- {0x68650000u, 27u}, // he -> Hebr
- {0xE0E70000u, 40u}, // hhy -> Latn
- {0x68690000u, 16u}, // hi -> Deva
- {0x81070000u, 40u}, // hia -> Latn
- {0x95070000u, 40u}, // hif -> Latn
- {0x99070000u, 40u}, // hig -> Latn
- {0x9D070000u, 40u}, // hih -> Latn
- {0xAD070000u, 40u}, // hil -> Latn
- {0x81670000u, 40u}, // hla -> Latn
- {0xD1670000u, 28u}, // hlu -> Hluw
- {0x8D870000u, 62u}, // hmd -> Plrd
- {0xCD870000u, 40u}, // hmt -> Latn
+ {0x84270000u, 44u}, // hbb -> Latn
+ {0xE0670000u, 19u}, // hdy -> Ethi
+ {0x68650000u, 30u}, // he -> Hebr
+ {0xE0E70000u, 44u}, // hhy -> Latn
+ {0x68690000u, 17u}, // hi -> Deva
+ {0x81070000u, 44u}, // hia -> Latn
+ {0x95070000u, 44u}, // hif -> Latn
+ {0x99070000u, 44u}, // hig -> Latn
+ {0x9D070000u, 44u}, // hih -> Latn
+ {0xAD070000u, 44u}, // hil -> Latn
+ {0x81670000u, 44u}, // hla -> Latn
+ {0xD1670000u, 31u}, // hlu -> Hluw
+ {0x8D870000u, 67u}, // hmd -> Plrd
+ {0xCD870000u, 44u}, // hmt -> Latn
{0x8DA70000u, 1u}, // hnd -> Arab
- {0x91A70000u, 16u}, // hne -> Deva
- {0xA5A70000u, 29u}, // hnj -> Hmng
- {0xB5A70000u, 40u}, // hnn -> Latn
+ {0x91A70000u, 17u}, // hne -> Deva
+ {0xA5A70000u, 32u}, // hnj -> Hmng
+ {0xB5A70000u, 44u}, // hnn -> Latn
{0xB9A70000u, 1u}, // hno -> Arab
- {0x686F0000u, 40u}, // ho -> Latn
- {0x89C70000u, 16u}, // hoc -> Deva
- {0xA5C70000u, 16u}, // hoj -> Deva
- {0xCDC70000u, 40u}, // hot -> Latn
- {0x68720000u, 40u}, // hr -> Latn
- {0x86470000u, 40u}, // hsb -> Latn
- {0xB6470000u, 24u}, // hsn -> Hans
- {0x68740000u, 40u}, // ht -> Latn
- {0x68750000u, 40u}, // hu -> Latn
- {0xA2870000u, 40u}, // hui -> Latn
+ {0x686F0000u, 44u}, // ho -> Latn
+ {0x89C70000u, 17u}, // hoc -> Deva
+ {0xA5C70000u, 17u}, // hoj -> Deva
+ {0xCDC70000u, 44u}, // hot -> Latn
+ {0x68720000u, 44u}, // hr -> Latn
+ {0x86470000u, 44u}, // hsb -> Latn
+ {0xB6470000u, 27u}, // hsn -> Hans
+ {0x68740000u, 44u}, // ht -> Latn
+ {0x68750000u, 44u}, // hu -> Latn
+ {0xA2870000u, 44u}, // hui -> Latn
{0x68790000u, 3u}, // hy -> Armn
- {0x687A0000u, 40u}, // hz -> Latn
- {0x69610000u, 40u}, // ia -> Latn
- {0xB4080000u, 40u}, // ian -> Latn
- {0xC4080000u, 40u}, // iar -> Latn
- {0x80280000u, 40u}, // iba -> Latn
- {0x84280000u, 40u}, // ibb -> Latn
- {0xE0280000u, 40u}, // iby -> Latn
- {0x80480000u, 40u}, // ica -> Latn
- {0x9C480000u, 40u}, // ich -> Latn
- {0x69640000u, 40u}, // id -> Latn
- {0x8C680000u, 40u}, // idd -> Latn
- {0xA0680000u, 40u}, // idi -> Latn
- {0xD0680000u, 40u}, // idu -> Latn
- {0x69670000u, 40u}, // ig -> Latn
- {0x84C80000u, 40u}, // igb -> Latn
- {0x90C80000u, 40u}, // ige -> Latn
- {0x69690000u, 86u}, // ii -> Yiii
- {0xA5280000u, 40u}, // ijj -> Latn
- {0x696B0000u, 40u}, // ik -> Latn
- {0xA9480000u, 40u}, // ikk -> Latn
- {0xCD480000u, 40u}, // ikt -> Latn
- {0xD9480000u, 40u}, // ikw -> Latn
- {0xDD480000u, 40u}, // ikx -> Latn
- {0xB9680000u, 40u}, // ilo -> Latn
- {0xB9880000u, 40u}, // imo -> Latn
- {0x696E0000u, 40u}, // in -> Latn
- {0x9DA80000u, 15u}, // inh -> Cyrl
- {0xD1C80000u, 40u}, // iou -> Latn
- {0xA2280000u, 40u}, // iri -> Latn
- {0x69730000u, 40u}, // is -> Latn
- {0x69740000u, 40u}, // it -> Latn
- {0x69750000u, 9u}, // iu -> Cans
- {0x69770000u, 27u}, // iw -> Hebr
- {0xB2C80000u, 40u}, // iwm -> Latn
- {0xCAC80000u, 40u}, // iws -> Latn
- {0x9F280000u, 40u}, // izh -> Latn
- {0xA3280000u, 40u}, // izi -> Latn
- {0x6A610000u, 31u}, // ja -> Jpan
- {0x84090000u, 40u}, // jab -> Latn
- {0xB0090000u, 40u}, // jam -> Latn
- {0xD0290000u, 40u}, // jbu -> Latn
- {0xB4890000u, 40u}, // jen -> Latn
- {0xA8C90000u, 40u}, // jgk -> Latn
- {0xB8C90000u, 40u}, // jgo -> Latn
- {0x6A690000u, 27u}, // ji -> Hebr
- {0x85090000u, 40u}, // jib -> Latn
- {0x89890000u, 40u}, // jmc -> Latn
- {0xAD890000u, 16u}, // jml -> Deva
- {0x82290000u, 40u}, // jra -> Latn
- {0xCE890000u, 40u}, // jut -> Latn
- {0x6A760000u, 40u}, // jv -> Latn
- {0x6A770000u, 40u}, // jw -> Latn
- {0x6B610000u, 19u}, // ka -> Geor
- {0x800A0000u, 15u}, // kaa -> Cyrl
- {0x840A0000u, 40u}, // kab -> Latn
- {0x880A0000u, 40u}, // kac -> Latn
- {0x8C0A0000u, 40u}, // kad -> Latn
- {0xA00A0000u, 40u}, // kai -> Latn
- {0xA40A0000u, 40u}, // kaj -> Latn
- {0xB00A0000u, 40u}, // kam -> Latn
- {0xB80A0000u, 40u}, // kao -> Latn
- {0x8C2A0000u, 15u}, // kbd -> Cyrl
- {0xB02A0000u, 40u}, // kbm -> Latn
- {0xBC2A0000u, 40u}, // kbp -> Latn
- {0xC02A0000u, 40u}, // kbq -> Latn
- {0xDC2A0000u, 40u}, // kbx -> Latn
+ {0x687A0000u, 44u}, // hz -> Latn
+ {0x69610000u, 44u}, // ia -> Latn
+ {0xB4080000u, 44u}, // ian -> Latn
+ {0xC4080000u, 44u}, // iar -> Latn
+ {0x80280000u, 44u}, // iba -> Latn
+ {0x84280000u, 44u}, // ibb -> Latn
+ {0xE0280000u, 44u}, // iby -> Latn
+ {0x80480000u, 44u}, // ica -> Latn
+ {0x9C480000u, 44u}, // ich -> Latn
+ {0x69640000u, 44u}, // id -> Latn
+ {0x8C680000u, 44u}, // idd -> Latn
+ {0xA0680000u, 44u}, // idi -> Latn
+ {0xD0680000u, 44u}, // idu -> Latn
+ {0x90A80000u, 44u}, // ife -> Latn
+ {0x69670000u, 44u}, // ig -> Latn
+ {0x84C80000u, 44u}, // igb -> Latn
+ {0x90C80000u, 44u}, // ige -> Latn
+ {0x69690000u, 94u}, // ii -> Yiii
+ {0xA5280000u, 44u}, // ijj -> Latn
+ {0x696B0000u, 44u}, // ik -> Latn
+ {0xA9480000u, 44u}, // ikk -> Latn
+ {0xCD480000u, 44u}, // ikt -> Latn
+ {0xD9480000u, 44u}, // ikw -> Latn
+ {0xDD480000u, 44u}, // ikx -> Latn
+ {0xB9680000u, 44u}, // ilo -> Latn
+ {0xB9880000u, 44u}, // imo -> Latn
+ {0x696E0000u, 44u}, // in -> Latn
+ {0x9DA80000u, 16u}, // inh -> Cyrl
+ {0x696F0000u, 44u}, // io -> Latn
+ {0xD1C80000u, 44u}, // iou -> Latn
+ {0xA2280000u, 44u}, // iri -> Latn
+ {0x69730000u, 44u}, // is -> Latn
+ {0x69740000u, 44u}, // it -> Latn
+ {0x69750000u, 10u}, // iu -> Cans
+ {0x69770000u, 30u}, // iw -> Hebr
+ {0xB2C80000u, 44u}, // iwm -> Latn
+ {0xCAC80000u, 44u}, // iws -> Latn
+ {0x9F280000u, 44u}, // izh -> Latn
+ {0xA3280000u, 44u}, // izi -> Latn
+ {0x6A610000u, 35u}, // ja -> Jpan
+ {0x84090000u, 44u}, // jab -> Latn
+ {0xB0090000u, 44u}, // jam -> Latn
+ {0xB8290000u, 44u}, // jbo -> Latn
+ {0xD0290000u, 44u}, // jbu -> Latn
+ {0xB4890000u, 44u}, // jen -> Latn
+ {0xA8C90000u, 44u}, // jgk -> Latn
+ {0xB8C90000u, 44u}, // jgo -> Latn
+ {0x6A690000u, 30u}, // ji -> Hebr
+ {0x85090000u, 44u}, // jib -> Latn
+ {0x89890000u, 44u}, // jmc -> Latn
+ {0xAD890000u, 17u}, // jml -> Deva
+ {0x82290000u, 44u}, // jra -> Latn
+ {0xCE890000u, 44u}, // jut -> Latn
+ {0x6A760000u, 44u}, // jv -> Latn
+ {0x6A770000u, 44u}, // jw -> Latn
+ {0x6B610000u, 20u}, // ka -> Geor
+ {0x800A0000u, 16u}, // kaa -> Cyrl
+ {0x840A0000u, 44u}, // kab -> Latn
+ {0x880A0000u, 44u}, // kac -> Latn
+ {0x8C0A0000u, 44u}, // kad -> Latn
+ {0xA00A0000u, 44u}, // kai -> Latn
+ {0xA40A0000u, 44u}, // kaj -> Latn
+ {0xB00A0000u, 44u}, // kam -> Latn
+ {0xB80A0000u, 44u}, // kao -> Latn
+ {0x8C2A0000u, 16u}, // kbd -> Cyrl
+ {0xB02A0000u, 44u}, // kbm -> Latn
+ {0xBC2A0000u, 44u}, // kbp -> Latn
+ {0xC02A0000u, 44u}, // kbq -> Latn
+ {0xDC2A0000u, 44u}, // kbx -> Latn
{0xE02A0000u, 1u}, // kby -> Arab
- {0x984A0000u, 40u}, // kcg -> Latn
- {0xA84A0000u, 40u}, // kck -> Latn
- {0xAC4A0000u, 40u}, // kcl -> Latn
- {0xCC4A0000u, 40u}, // kct -> Latn
- {0x906A0000u, 40u}, // kde -> Latn
+ {0x984A0000u, 44u}, // kcg -> Latn
+ {0xA84A0000u, 44u}, // kck -> Latn
+ {0xAC4A0000u, 44u}, // kcl -> Latn
+ {0xCC4A0000u, 44u}, // kct -> Latn
+ {0x906A0000u, 44u}, // kde -> Latn
{0x9C6A0000u, 1u}, // kdh -> Arab
- {0xAC6A0000u, 40u}, // kdl -> Latn
- {0xCC6A0000u, 80u}, // kdt -> Thai
- {0x808A0000u, 40u}, // kea -> Latn
- {0xB48A0000u, 40u}, // ken -> Latn
- {0xE48A0000u, 40u}, // kez -> Latn
- {0xB8AA0000u, 40u}, // kfo -> Latn
- {0xC4AA0000u, 16u}, // kfr -> Deva
- {0xE0AA0000u, 16u}, // kfy -> Deva
- {0x6B670000u, 40u}, // kg -> Latn
- {0x90CA0000u, 40u}, // kge -> Latn
- {0x94CA0000u, 40u}, // kgf -> Latn
- {0xBCCA0000u, 40u}, // kgp -> Latn
- {0x80EA0000u, 40u}, // kha -> Latn
- {0x84EA0000u, 73u}, // khb -> Talu
- {0xB4EA0000u, 16u}, // khn -> Deva
- {0xC0EA0000u, 40u}, // khq -> Latn
- {0xC8EA0000u, 40u}, // khs -> Latn
- {0xCCEA0000u, 52u}, // kht -> Mymr
+ {0xAC6A0000u, 44u}, // kdl -> Latn
+ {0xCC6A0000u, 87u}, // kdt -> Thai
+ {0x808A0000u, 44u}, // kea -> Latn
+ {0xB48A0000u, 44u}, // ken -> Latn
+ {0xE48A0000u, 44u}, // kez -> Latn
+ {0xB8AA0000u, 44u}, // kfo -> Latn
+ {0xC4AA0000u, 17u}, // kfr -> Deva
+ {0xE0AA0000u, 17u}, // kfy -> Deva
+ {0x6B670000u, 44u}, // kg -> Latn
+ {0x90CA0000u, 44u}, // kge -> Latn
+ {0x94CA0000u, 44u}, // kgf -> Latn
+ {0xBCCA0000u, 44u}, // kgp -> Latn
+ {0x80EA0000u, 44u}, // kha -> Latn
+ {0x84EA0000u, 80u}, // khb -> Talu
+ {0xB4EA0000u, 17u}, // khn -> Deva
+ {0xC0EA0000u, 44u}, // khq -> Latn
+ {0xC8EA0000u, 44u}, // khs -> Latn
+ {0xCCEA0000u, 56u}, // kht -> Mymr
{0xD8EA0000u, 1u}, // khw -> Arab
- {0xE4EA0000u, 40u}, // khz -> Latn
- {0x6B690000u, 40u}, // ki -> Latn
- {0xA50A0000u, 40u}, // kij -> Latn
- {0xD10A0000u, 40u}, // kiu -> Latn
- {0xD90A0000u, 40u}, // kiw -> Latn
- {0x6B6A0000u, 40u}, // kj -> Latn
- {0x8D2A0000u, 40u}, // kjd -> Latn
- {0x992A0000u, 39u}, // kjg -> Laoo
- {0xC92A0000u, 40u}, // kjs -> Latn
- {0xE12A0000u, 40u}, // kjy -> Latn
- {0x6B6B0000u, 15u}, // kk -> Cyrl
+ {0xE4EA0000u, 44u}, // khz -> Latn
+ {0x6B690000u, 44u}, // ki -> Latn
+ {0xA50A0000u, 44u}, // kij -> Latn
+ {0xD10A0000u, 44u}, // kiu -> Latn
+ {0xD90A0000u, 44u}, // kiw -> Latn
+ {0x6B6A0000u, 44u}, // kj -> Latn
+ {0x8D2A0000u, 44u}, // kjd -> Latn
+ {0x992A0000u, 43u}, // kjg -> Laoo
+ {0xC92A0000u, 44u}, // kjs -> Latn
+ {0xE12A0000u, 44u}, // kjy -> Latn
+ {0x6B6B0000u, 16u}, // kk -> Cyrl
{0x6B6B4146u, 1u}, // kk-AF -> Arab
{0x6B6B434Eu, 1u}, // kk-CN -> Arab
{0x6B6B4952u, 1u}, // kk-IR -> Arab
{0x6B6B4D4Eu, 1u}, // kk-MN -> Arab
- {0x894A0000u, 40u}, // kkc -> Latn
- {0xA54A0000u, 40u}, // kkj -> Latn
- {0x6B6C0000u, 40u}, // kl -> Latn
- {0xB56A0000u, 40u}, // kln -> Latn
- {0xC16A0000u, 40u}, // klq -> Latn
- {0xCD6A0000u, 40u}, // klt -> Latn
- {0xDD6A0000u, 40u}, // klx -> Latn
- {0x6B6D0000u, 35u}, // km -> Khmr
- {0x858A0000u, 40u}, // kmb -> Latn
- {0x9D8A0000u, 40u}, // kmh -> Latn
- {0xB98A0000u, 40u}, // kmo -> Latn
- {0xC98A0000u, 40u}, // kms -> Latn
- {0xD18A0000u, 40u}, // kmu -> Latn
- {0xD98A0000u, 40u}, // kmw -> Latn
- {0x6B6E0000u, 36u}, // kn -> Knda
- {0xBDAA0000u, 40u}, // knp -> Latn
- {0x6B6F0000u, 37u}, // ko -> Kore
- {0xA1CA0000u, 15u}, // koi -> Cyrl
- {0xA9CA0000u, 16u}, // kok -> Deva
- {0xADCA0000u, 40u}, // kol -> Latn
- {0xC9CA0000u, 40u}, // kos -> Latn
- {0xE5CA0000u, 40u}, // koz -> Latn
- {0x91EA0000u, 40u}, // kpe -> Latn
- {0x95EA0000u, 40u}, // kpf -> Latn
- {0xB9EA0000u, 40u}, // kpo -> Latn
- {0xC5EA0000u, 40u}, // kpr -> Latn
- {0xDDEA0000u, 40u}, // kpx -> Latn
- {0x860A0000u, 40u}, // kqb -> Latn
- {0x960A0000u, 40u}, // kqf -> Latn
- {0xCA0A0000u, 40u}, // kqs -> Latn
- {0xE20A0000u, 18u}, // kqy -> Ethi
- {0x8A2A0000u, 15u}, // krc -> Cyrl
- {0xA22A0000u, 40u}, // kri -> Latn
- {0xA62A0000u, 40u}, // krj -> Latn
- {0xAE2A0000u, 40u}, // krl -> Latn
- {0xCA2A0000u, 40u}, // krs -> Latn
- {0xD22A0000u, 16u}, // kru -> Deva
+ {0x894A0000u, 44u}, // kkc -> Latn
+ {0xA54A0000u, 44u}, // kkj -> Latn
+ {0x6B6C0000u, 44u}, // kl -> Latn
+ {0xB56A0000u, 44u}, // kln -> Latn
+ {0xC16A0000u, 44u}, // klq -> Latn
+ {0xCD6A0000u, 44u}, // klt -> Latn
+ {0xDD6A0000u, 44u}, // klx -> Latn
+ {0x6B6D0000u, 39u}, // km -> Khmr
+ {0x858A0000u, 44u}, // kmb -> Latn
+ {0x9D8A0000u, 44u}, // kmh -> Latn
+ {0xB98A0000u, 44u}, // kmo -> Latn
+ {0xC98A0000u, 44u}, // kms -> Latn
+ {0xD18A0000u, 44u}, // kmu -> Latn
+ {0xD98A0000u, 44u}, // kmw -> Latn
+ {0x6B6E0000u, 40u}, // kn -> Knda
+ {0x95AA0000u, 44u}, // knf -> Latn
+ {0xBDAA0000u, 44u}, // knp -> Latn
+ {0x6B6F0000u, 41u}, // ko -> Kore
+ {0xA1CA0000u, 16u}, // koi -> Cyrl
+ {0xA9CA0000u, 17u}, // kok -> Deva
+ {0xADCA0000u, 44u}, // kol -> Latn
+ {0xC9CA0000u, 44u}, // kos -> Latn
+ {0xE5CA0000u, 44u}, // koz -> Latn
+ {0x91EA0000u, 44u}, // kpe -> Latn
+ {0x95EA0000u, 44u}, // kpf -> Latn
+ {0xB9EA0000u, 44u}, // kpo -> Latn
+ {0xC5EA0000u, 44u}, // kpr -> Latn
+ {0xDDEA0000u, 44u}, // kpx -> Latn
+ {0x860A0000u, 44u}, // kqb -> Latn
+ {0x960A0000u, 44u}, // kqf -> Latn
+ {0xCA0A0000u, 44u}, // kqs -> Latn
+ {0xE20A0000u, 19u}, // kqy -> Ethi
+ {0x6B720000u, 44u}, // kr -> Latn
+ {0x8A2A0000u, 16u}, // krc -> Cyrl
+ {0xA22A0000u, 44u}, // kri -> Latn
+ {0xA62A0000u, 44u}, // krj -> Latn
+ {0xAE2A0000u, 44u}, // krl -> Latn
+ {0xCA2A0000u, 44u}, // krs -> Latn
+ {0xD22A0000u, 17u}, // kru -> Deva
{0x6B730000u, 1u}, // ks -> Arab
- {0x864A0000u, 40u}, // ksb -> Latn
- {0x8E4A0000u, 40u}, // ksd -> Latn
- {0x964A0000u, 40u}, // ksf -> Latn
- {0x9E4A0000u, 40u}, // ksh -> Latn
- {0xA64A0000u, 40u}, // ksj -> Latn
- {0xC64A0000u, 40u}, // ksr -> Latn
- {0x866A0000u, 18u}, // ktb -> Ethi
- {0xB26A0000u, 40u}, // ktm -> Latn
- {0xBA6A0000u, 40u}, // kto -> Latn
- {0x6B750000u, 40u}, // ku -> Latn
+ {0x864A0000u, 44u}, // ksb -> Latn
+ {0x8E4A0000u, 44u}, // ksd -> Latn
+ {0x964A0000u, 44u}, // ksf -> Latn
+ {0x9E4A0000u, 44u}, // ksh -> Latn
+ {0xA64A0000u, 44u}, // ksj -> Latn
+ {0xC64A0000u, 44u}, // ksr -> Latn
+ {0x866A0000u, 19u}, // ktb -> Ethi
+ {0xB26A0000u, 44u}, // ktm -> Latn
+ {0xBA6A0000u, 44u}, // kto -> Latn
+ {0xC66A0000u, 44u}, // ktr -> Latn
+ {0x6B750000u, 44u}, // ku -> Latn
{0x6B754952u, 1u}, // ku-IR -> Arab
{0x6B754C42u, 1u}, // ku-LB -> Arab
- {0x868A0000u, 40u}, // kub -> Latn
- {0x8E8A0000u, 40u}, // kud -> Latn
- {0x928A0000u, 40u}, // kue -> Latn
- {0xA68A0000u, 40u}, // kuj -> Latn
- {0xB28A0000u, 15u}, // kum -> Cyrl
- {0xB68A0000u, 40u}, // kun -> Latn
- {0xBE8A0000u, 40u}, // kup -> Latn
- {0xCA8A0000u, 40u}, // kus -> Latn
- {0x6B760000u, 15u}, // kv -> Cyrl
- {0x9AAA0000u, 40u}, // kvg -> Latn
- {0xC6AA0000u, 40u}, // kvr -> Latn
+ {0x868A0000u, 44u}, // kub -> Latn
+ {0x8E8A0000u, 44u}, // kud -> Latn
+ {0x928A0000u, 44u}, // kue -> Latn
+ {0xA68A0000u, 44u}, // kuj -> Latn
+ {0xB28A0000u, 16u}, // kum -> Cyrl
+ {0xB68A0000u, 44u}, // kun -> Latn
+ {0xBE8A0000u, 44u}, // kup -> Latn
+ {0xCA8A0000u, 44u}, // kus -> Latn
+ {0x6B760000u, 16u}, // kv -> Cyrl
+ {0x9AAA0000u, 44u}, // kvg -> Latn
+ {0xC6AA0000u, 44u}, // kvr -> Latn
{0xDEAA0000u, 1u}, // kvx -> Arab
- {0x6B770000u, 40u}, // kw -> Latn
- {0xA6CA0000u, 40u}, // kwj -> Latn
- {0xBACA0000u, 40u}, // kwo -> Latn
- {0x82EA0000u, 40u}, // kxa -> Latn
- {0x8AEA0000u, 18u}, // kxc -> Ethi
- {0xB2EA0000u, 80u}, // kxm -> Thai
+ {0x6B770000u, 44u}, // kw -> Latn
+ {0xA6CA0000u, 44u}, // kwj -> Latn
+ {0xBACA0000u, 44u}, // kwo -> Latn
+ {0xC2CA0000u, 44u}, // kwq -> Latn
+ {0x82EA0000u, 44u}, // kxa -> Latn
+ {0x8AEA0000u, 19u}, // kxc -> Ethi
+ {0x92EA0000u, 44u}, // kxe -> Latn
+ {0xB2EA0000u, 87u}, // kxm -> Thai
{0xBEEA0000u, 1u}, // kxp -> Arab
- {0xDAEA0000u, 40u}, // kxw -> Latn
- {0xE6EA0000u, 40u}, // kxz -> Latn
- {0x6B790000u, 15u}, // ky -> Cyrl
+ {0xDAEA0000u, 44u}, // kxw -> Latn
+ {0xE6EA0000u, 44u}, // kxz -> Latn
+ {0x6B790000u, 16u}, // ky -> Cyrl
{0x6B79434Eu, 1u}, // ky-CN -> Arab
- {0x6B795452u, 40u}, // ky-TR -> Latn
- {0x930A0000u, 40u}, // kye -> Latn
- {0xDF0A0000u, 40u}, // kyx -> Latn
- {0xC72A0000u, 40u}, // kzr -> Latn
- {0x6C610000u, 40u}, // la -> Latn
- {0x840B0000u, 42u}, // lab -> Lina
- {0x8C0B0000u, 27u}, // lad -> Hebr
- {0x980B0000u, 40u}, // lag -> Latn
+ {0x6B795452u, 44u}, // ky-TR -> Latn
+ {0x930A0000u, 44u}, // kye -> Latn
+ {0xDF0A0000u, 44u}, // kyx -> Latn
+ {0xA72A0000u, 44u}, // kzj -> Latn
+ {0xC72A0000u, 44u}, // kzr -> Latn
+ {0xCF2A0000u, 44u}, // kzt -> Latn
+ {0x6C610000u, 44u}, // la -> Latn
+ {0x840B0000u, 46u}, // lab -> Lina
+ {0x8C0B0000u, 30u}, // lad -> Hebr
+ {0x980B0000u, 44u}, // lag -> Latn
{0x9C0B0000u, 1u}, // lah -> Arab
- {0xA40B0000u, 40u}, // laj -> Latn
- {0xC80B0000u, 40u}, // las -> Latn
- {0x6C620000u, 40u}, // lb -> Latn
- {0x902B0000u, 15u}, // lbe -> Cyrl
- {0xD02B0000u, 40u}, // lbu -> Latn
- {0xD82B0000u, 40u}, // lbw -> Latn
- {0xB04B0000u, 40u}, // lcm -> Latn
- {0xBC4B0000u, 80u}, // lcp -> Thai
- {0x846B0000u, 40u}, // ldb -> Latn
- {0x8C8B0000u, 40u}, // led -> Latn
- {0x908B0000u, 40u}, // lee -> Latn
- {0xB08B0000u, 40u}, // lem -> Latn
- {0xBC8B0000u, 41u}, // lep -> Lepc
- {0xC08B0000u, 40u}, // leq -> Latn
- {0xD08B0000u, 40u}, // leu -> Latn
- {0xE48B0000u, 15u}, // lez -> Cyrl
- {0x6C670000u, 40u}, // lg -> Latn
- {0x98CB0000u, 40u}, // lgg -> Latn
- {0x6C690000u, 40u}, // li -> Latn
- {0x810B0000u, 40u}, // lia -> Latn
- {0x8D0B0000u, 40u}, // lid -> Latn
- {0x950B0000u, 16u}, // lif -> Deva
- {0x990B0000u, 40u}, // lig -> Latn
- {0x9D0B0000u, 40u}, // lih -> Latn
- {0xA50B0000u, 40u}, // lij -> Latn
- {0xC90B0000u, 43u}, // lis -> Lisu
- {0xBD2B0000u, 40u}, // ljp -> Latn
+ {0xA40B0000u, 44u}, // laj -> Latn
+ {0xC80B0000u, 44u}, // las -> Latn
+ {0x6C620000u, 44u}, // lb -> Latn
+ {0x902B0000u, 16u}, // lbe -> Cyrl
+ {0xD02B0000u, 44u}, // lbu -> Latn
+ {0xD82B0000u, 44u}, // lbw -> Latn
+ {0xB04B0000u, 44u}, // lcm -> Latn
+ {0xBC4B0000u, 87u}, // lcp -> Thai
+ {0x846B0000u, 44u}, // ldb -> Latn
+ {0x8C8B0000u, 44u}, // led -> Latn
+ {0x908B0000u, 44u}, // lee -> Latn
+ {0xB08B0000u, 44u}, // lem -> Latn
+ {0xBC8B0000u, 45u}, // lep -> Lepc
+ {0xC08B0000u, 44u}, // leq -> Latn
+ {0xD08B0000u, 44u}, // leu -> Latn
+ {0xE48B0000u, 16u}, // lez -> Cyrl
+ {0x6C670000u, 44u}, // lg -> Latn
+ {0x98CB0000u, 44u}, // lgg -> Latn
+ {0x6C690000u, 44u}, // li -> Latn
+ {0x810B0000u, 44u}, // lia -> Latn
+ {0x8D0B0000u, 44u}, // lid -> Latn
+ {0x950B0000u, 17u}, // lif -> Deva
+ {0x990B0000u, 44u}, // lig -> Latn
+ {0x9D0B0000u, 44u}, // lih -> Latn
+ {0xA50B0000u, 44u}, // lij -> Latn
+ {0xC90B0000u, 47u}, // lis -> Lisu
+ {0xBD2B0000u, 44u}, // ljp -> Latn
{0xA14B0000u, 1u}, // lki -> Arab
- {0xCD4B0000u, 40u}, // lkt -> Latn
- {0x916B0000u, 40u}, // lle -> Latn
- {0xB56B0000u, 40u}, // lln -> Latn
- {0xB58B0000u, 77u}, // lmn -> Telu
- {0xB98B0000u, 40u}, // lmo -> Latn
- {0xBD8B0000u, 40u}, // lmp -> Latn
- {0x6C6E0000u, 40u}, // ln -> Latn
- {0xC9AB0000u, 40u}, // lns -> Latn
- {0xD1AB0000u, 40u}, // lnu -> Latn
- {0x6C6F0000u, 39u}, // lo -> Laoo
- {0xA5CB0000u, 40u}, // loj -> Latn
- {0xA9CB0000u, 40u}, // lok -> Latn
- {0xADCB0000u, 40u}, // lol -> Latn
- {0xC5CB0000u, 40u}, // lor -> Latn
- {0xC9CB0000u, 40u}, // los -> Latn
- {0xE5CB0000u, 40u}, // loz -> Latn
+ {0xCD4B0000u, 44u}, // lkt -> Latn
+ {0x916B0000u, 44u}, // lle -> Latn
+ {0xB56B0000u, 44u}, // lln -> Latn
+ {0xB58B0000u, 84u}, // lmn -> Telu
+ {0xB98B0000u, 44u}, // lmo -> Latn
+ {0xBD8B0000u, 44u}, // lmp -> Latn
+ {0x6C6E0000u, 44u}, // ln -> Latn
+ {0xC9AB0000u, 44u}, // lns -> Latn
+ {0xD1AB0000u, 44u}, // lnu -> Latn
+ {0x6C6F0000u, 43u}, // lo -> Laoo
+ {0xA5CB0000u, 44u}, // loj -> Latn
+ {0xA9CB0000u, 44u}, // lok -> Latn
+ {0xADCB0000u, 44u}, // lol -> Latn
+ {0xC5CB0000u, 44u}, // lor -> Latn
+ {0xC9CB0000u, 44u}, // los -> Latn
+ {0xE5CB0000u, 44u}, // loz -> Latn
{0x8A2B0000u, 1u}, // lrc -> Arab
- {0x6C740000u, 40u}, // lt -> Latn
- {0x9A6B0000u, 40u}, // ltg -> Latn
- {0x6C750000u, 40u}, // lu -> Latn
- {0x828B0000u, 40u}, // lua -> Latn
- {0xBA8B0000u, 40u}, // luo -> Latn
- {0xE28B0000u, 40u}, // luy -> Latn
+ {0x6C740000u, 44u}, // lt -> Latn
+ {0x9A6B0000u, 44u}, // ltg -> Latn
+ {0x6C750000u, 44u}, // lu -> Latn
+ {0x828B0000u, 44u}, // lua -> Latn
+ {0xBA8B0000u, 44u}, // luo -> Latn
+ {0xE28B0000u, 44u}, // luy -> Latn
{0xE68B0000u, 1u}, // luz -> Arab
- {0x6C760000u, 40u}, // lv -> Latn
- {0xAECB0000u, 80u}, // lwl -> Thai
- {0x9F2B0000u, 24u}, // lzh -> Hans
- {0xE72B0000u, 40u}, // lzz -> Latn
- {0x8C0C0000u, 40u}, // mad -> Latn
- {0x940C0000u, 40u}, // maf -> Latn
- {0x980C0000u, 16u}, // mag -> Deva
- {0xA00C0000u, 16u}, // mai -> Deva
- {0xA80C0000u, 40u}, // mak -> Latn
- {0xB40C0000u, 40u}, // man -> Latn
- {0xB40C474Eu, 54u}, // man-GN -> Nkoo
- {0xC80C0000u, 40u}, // mas -> Latn
- {0xD80C0000u, 40u}, // maw -> Latn
- {0xE40C0000u, 40u}, // maz -> Latn
- {0x9C2C0000u, 40u}, // mbh -> Latn
- {0xB82C0000u, 40u}, // mbo -> Latn
- {0xC02C0000u, 40u}, // mbq -> Latn
- {0xD02C0000u, 40u}, // mbu -> Latn
- {0xD82C0000u, 40u}, // mbw -> Latn
- {0xA04C0000u, 40u}, // mci -> Latn
- {0xBC4C0000u, 40u}, // mcp -> Latn
- {0xC04C0000u, 40u}, // mcq -> Latn
- {0xC44C0000u, 40u}, // mcr -> Latn
- {0xD04C0000u, 40u}, // mcu -> Latn
- {0x806C0000u, 40u}, // mda -> Latn
+ {0x6C760000u, 44u}, // lv -> Latn
+ {0xAECB0000u, 87u}, // lwl -> Thai
+ {0x9F2B0000u, 27u}, // lzh -> Hans
+ {0xE72B0000u, 44u}, // lzz -> Latn
+ {0x8C0C0000u, 44u}, // mad -> Latn
+ {0x940C0000u, 44u}, // maf -> Latn
+ {0x980C0000u, 17u}, // mag -> Deva
+ {0xA00C0000u, 17u}, // mai -> Deva
+ {0xA80C0000u, 44u}, // mak -> Latn
+ {0xB40C0000u, 44u}, // man -> Latn
+ {0xB40C474Eu, 58u}, // man-GN -> Nkoo
+ {0xC80C0000u, 44u}, // mas -> Latn
+ {0xD80C0000u, 44u}, // maw -> Latn
+ {0xE40C0000u, 44u}, // maz -> Latn
+ {0x9C2C0000u, 44u}, // mbh -> Latn
+ {0xB82C0000u, 44u}, // mbo -> Latn
+ {0xC02C0000u, 44u}, // mbq -> Latn
+ {0xD02C0000u, 44u}, // mbu -> Latn
+ {0xD82C0000u, 44u}, // mbw -> Latn
+ {0xA04C0000u, 44u}, // mci -> Latn
+ {0xBC4C0000u, 44u}, // mcp -> Latn
+ {0xC04C0000u, 44u}, // mcq -> Latn
+ {0xC44C0000u, 44u}, // mcr -> Latn
+ {0xD04C0000u, 44u}, // mcu -> Latn
+ {0x806C0000u, 44u}, // mda -> Latn
{0x906C0000u, 1u}, // mde -> Arab
- {0x946C0000u, 15u}, // mdf -> Cyrl
- {0x9C6C0000u, 40u}, // mdh -> Latn
- {0xA46C0000u, 40u}, // mdj -> Latn
- {0xC46C0000u, 40u}, // mdr -> Latn
- {0xDC6C0000u, 18u}, // mdx -> Ethi
- {0x8C8C0000u, 40u}, // med -> Latn
- {0x908C0000u, 40u}, // mee -> Latn
- {0xA88C0000u, 40u}, // mek -> Latn
- {0xB48C0000u, 40u}, // men -> Latn
- {0xC48C0000u, 40u}, // mer -> Latn
- {0xCC8C0000u, 40u}, // met -> Latn
- {0xD08C0000u, 40u}, // meu -> Latn
+ {0x946C0000u, 16u}, // mdf -> Cyrl
+ {0x9C6C0000u, 44u}, // mdh -> Latn
+ {0xA46C0000u, 44u}, // mdj -> Latn
+ {0xC46C0000u, 44u}, // mdr -> Latn
+ {0xDC6C0000u, 19u}, // mdx -> Ethi
+ {0x8C8C0000u, 44u}, // med -> Latn
+ {0x908C0000u, 44u}, // mee -> Latn
+ {0xA88C0000u, 44u}, // mek -> Latn
+ {0xB48C0000u, 44u}, // men -> Latn
+ {0xC48C0000u, 44u}, // mer -> Latn
+ {0xCC8C0000u, 44u}, // met -> Latn
+ {0xD08C0000u, 44u}, // meu -> Latn
{0x80AC0000u, 1u}, // mfa -> Arab
- {0x90AC0000u, 40u}, // mfe -> Latn
- {0xB4AC0000u, 40u}, // mfn -> Latn
- {0xB8AC0000u, 40u}, // mfo -> Latn
- {0xC0AC0000u, 40u}, // mfq -> Latn
- {0x6D670000u, 40u}, // mg -> Latn
- {0x9CCC0000u, 40u}, // mgh -> Latn
- {0xACCC0000u, 40u}, // mgl -> Latn
- {0xB8CC0000u, 40u}, // mgo -> Latn
- {0xBCCC0000u, 16u}, // mgp -> Deva
- {0xE0CC0000u, 40u}, // mgy -> Latn
- {0x6D680000u, 40u}, // mh -> Latn
- {0xA0EC0000u, 40u}, // mhi -> Latn
- {0xACEC0000u, 40u}, // mhl -> Latn
- {0x6D690000u, 40u}, // mi -> Latn
- {0x950C0000u, 40u}, // mif -> Latn
- {0xB50C0000u, 40u}, // min -> Latn
- {0xC90C0000u, 26u}, // mis -> Hatr
- {0xD90C0000u, 40u}, // miw -> Latn
- {0x6D6B0000u, 15u}, // mk -> Cyrl
+ {0x90AC0000u, 44u}, // mfe -> Latn
+ {0xB4AC0000u, 44u}, // mfn -> Latn
+ {0xB8AC0000u, 44u}, // mfo -> Latn
+ {0xC0AC0000u, 44u}, // mfq -> Latn
+ {0x6D670000u, 44u}, // mg -> Latn
+ {0x9CCC0000u, 44u}, // mgh -> Latn
+ {0xACCC0000u, 44u}, // mgl -> Latn
+ {0xB8CC0000u, 44u}, // mgo -> Latn
+ {0xBCCC0000u, 17u}, // mgp -> Deva
+ {0xE0CC0000u, 44u}, // mgy -> Latn
+ {0x6D680000u, 44u}, // mh -> Latn
+ {0xA0EC0000u, 44u}, // mhi -> Latn
+ {0xACEC0000u, 44u}, // mhl -> Latn
+ {0x6D690000u, 44u}, // mi -> Latn
+ {0x950C0000u, 44u}, // mif -> Latn
+ {0xB50C0000u, 44u}, // min -> Latn
+ {0xC90C0000u, 29u}, // mis -> Hatr
+ {0xD90C0000u, 44u}, // miw -> Latn
+ {0x6D6B0000u, 16u}, // mk -> Cyrl
{0xA14C0000u, 1u}, // mki -> Arab
- {0xAD4C0000u, 40u}, // mkl -> Latn
- {0xBD4C0000u, 40u}, // mkp -> Latn
- {0xD94C0000u, 40u}, // mkw -> Latn
- {0x6D6C0000u, 49u}, // ml -> Mlym
- {0x916C0000u, 40u}, // mle -> Latn
- {0xBD6C0000u, 40u}, // mlp -> Latn
- {0xC96C0000u, 40u}, // mls -> Latn
- {0xB98C0000u, 40u}, // mmo -> Latn
- {0xD18C0000u, 40u}, // mmu -> Latn
- {0xDD8C0000u, 40u}, // mmx -> Latn
- {0x6D6E0000u, 15u}, // mn -> Cyrl
- {0x6D6E434Eu, 50u}, // mn-CN -> Mong
- {0x81AC0000u, 40u}, // mna -> Latn
- {0x95AC0000u, 40u}, // mnf -> Latn
+ {0xAD4C0000u, 44u}, // mkl -> Latn
+ {0xBD4C0000u, 44u}, // mkp -> Latn
+ {0xD94C0000u, 44u}, // mkw -> Latn
+ {0x6D6C0000u, 53u}, // ml -> Mlym
+ {0x916C0000u, 44u}, // mle -> Latn
+ {0xBD6C0000u, 44u}, // mlp -> Latn
+ {0xC96C0000u, 44u}, // mls -> Latn
+ {0xB98C0000u, 44u}, // mmo -> Latn
+ {0xD18C0000u, 44u}, // mmu -> Latn
+ {0xDD8C0000u, 44u}, // mmx -> Latn
+ {0x6D6E0000u, 16u}, // mn -> Cyrl
+ {0x6D6E434Eu, 54u}, // mn-CN -> Mong
+ {0x81AC0000u, 44u}, // mna -> Latn
+ {0x95AC0000u, 44u}, // mnf -> Latn
{0xA1AC0000u, 7u}, // mni -> Beng
- {0xD9AC0000u, 52u}, // mnw -> Mymr
- {0x81CC0000u, 40u}, // moa -> Latn
- {0x91CC0000u, 40u}, // moe -> Latn
- {0x9DCC0000u, 40u}, // moh -> Latn
- {0xC9CC0000u, 40u}, // mos -> Latn
- {0xDDCC0000u, 40u}, // mox -> Latn
- {0xBDEC0000u, 40u}, // mpp -> Latn
- {0xC9EC0000u, 40u}, // mps -> Latn
- {0xCDEC0000u, 40u}, // mpt -> Latn
- {0xDDEC0000u, 40u}, // mpx -> Latn
- {0xAE0C0000u, 40u}, // mql -> Latn
- {0x6D720000u, 16u}, // mr -> Deva
- {0x8E2C0000u, 16u}, // mrd -> Deva
- {0xA62C0000u, 15u}, // mrj -> Cyrl
- {0xBA2C0000u, 51u}, // mro -> Mroo
- {0x6D730000u, 40u}, // ms -> Latn
+ {0xD9AC0000u, 56u}, // mnw -> Mymr
+ {0x6D6F0000u, 44u}, // mo -> Latn
+ {0x81CC0000u, 44u}, // moa -> Latn
+ {0x91CC0000u, 44u}, // moe -> Latn
+ {0x9DCC0000u, 44u}, // moh -> Latn
+ {0xC9CC0000u, 44u}, // mos -> Latn
+ {0xDDCC0000u, 44u}, // mox -> Latn
+ {0xBDEC0000u, 44u}, // mpp -> Latn
+ {0xC9EC0000u, 44u}, // mps -> Latn
+ {0xCDEC0000u, 44u}, // mpt -> Latn
+ {0xDDEC0000u, 44u}, // mpx -> Latn
+ {0xAE0C0000u, 44u}, // mql -> Latn
+ {0x6D720000u, 17u}, // mr -> Deva
+ {0x8E2C0000u, 17u}, // mrd -> Deva
+ {0xA62C0000u, 16u}, // mrj -> Cyrl
+ {0xBA2C0000u, 55u}, // mro -> Mroo
+ {0x6D730000u, 44u}, // ms -> Latn
{0x6D734343u, 1u}, // ms-CC -> Arab
{0x6D734944u, 1u}, // ms-ID -> Arab
- {0x6D740000u, 40u}, // mt -> Latn
- {0x8A6C0000u, 40u}, // mtc -> Latn
- {0x966C0000u, 40u}, // mtf -> Latn
- {0xA26C0000u, 40u}, // mti -> Latn
- {0xC66C0000u, 16u}, // mtr -> Deva
- {0x828C0000u, 40u}, // mua -> Latn
- {0xC68C0000u, 40u}, // mur -> Latn
- {0xCA8C0000u, 40u}, // mus -> Latn
- {0x82AC0000u, 40u}, // mva -> Latn
- {0xB6AC0000u, 40u}, // mvn -> Latn
+ {0x6D740000u, 44u}, // mt -> Latn
+ {0x8A6C0000u, 44u}, // mtc -> Latn
+ {0x966C0000u, 44u}, // mtf -> Latn
+ {0xA26C0000u, 44u}, // mti -> Latn
+ {0xC66C0000u, 17u}, // mtr -> Deva
+ {0x828C0000u, 44u}, // mua -> Latn
+ {0xC68C0000u, 44u}, // mur -> Latn
+ {0xCA8C0000u, 44u}, // mus -> Latn
+ {0x82AC0000u, 44u}, // mva -> Latn
+ {0xB6AC0000u, 44u}, // mvn -> Latn
{0xE2AC0000u, 1u}, // mvy -> Arab
- {0xAACC0000u, 40u}, // mwk -> Latn
- {0xC6CC0000u, 16u}, // mwr -> Deva
- {0xD6CC0000u, 40u}, // mwv -> Latn
- {0x8AEC0000u, 40u}, // mxc -> Latn
- {0xB2EC0000u, 40u}, // mxm -> Latn
- {0x6D790000u, 52u}, // my -> Mymr
- {0xAB0C0000u, 40u}, // myk -> Latn
- {0xB30C0000u, 18u}, // mym -> Ethi
- {0xD70C0000u, 15u}, // myv -> Cyrl
- {0xDB0C0000u, 40u}, // myw -> Latn
- {0xDF0C0000u, 40u}, // myx -> Latn
- {0xE70C0000u, 46u}, // myz -> Mand
- {0xAB2C0000u, 40u}, // mzk -> Latn
- {0xB32C0000u, 40u}, // mzm -> Latn
+ {0xAACC0000u, 44u}, // mwk -> Latn
+ {0xC6CC0000u, 17u}, // mwr -> Deva
+ {0xD6CC0000u, 44u}, // mwv -> Latn
+ {0xDACC0000u, 33u}, // mww -> Hmnp
+ {0x8AEC0000u, 44u}, // mxc -> Latn
+ {0xB2EC0000u, 44u}, // mxm -> Latn
+ {0x6D790000u, 56u}, // my -> Mymr
+ {0xAB0C0000u, 44u}, // myk -> Latn
+ {0xB30C0000u, 19u}, // mym -> Ethi
+ {0xD70C0000u, 16u}, // myv -> Cyrl
+ {0xDB0C0000u, 44u}, // myw -> Latn
+ {0xDF0C0000u, 44u}, // myx -> Latn
+ {0xE70C0000u, 50u}, // myz -> Mand
+ {0xAB2C0000u, 44u}, // mzk -> Latn
+ {0xB32C0000u, 44u}, // mzm -> Latn
{0xB72C0000u, 1u}, // mzn -> Arab
- {0xBF2C0000u, 40u}, // mzp -> Latn
- {0xDB2C0000u, 40u}, // mzw -> Latn
- {0xE72C0000u, 40u}, // mzz -> Latn
- {0x6E610000u, 40u}, // na -> Latn
- {0x880D0000u, 40u}, // nac -> Latn
- {0x940D0000u, 40u}, // naf -> Latn
- {0xA80D0000u, 40u}, // nak -> Latn
- {0xB40D0000u, 24u}, // nan -> Hans
- {0xBC0D0000u, 40u}, // nap -> Latn
- {0xC00D0000u, 40u}, // naq -> Latn
- {0xC80D0000u, 40u}, // nas -> Latn
- {0x6E620000u, 40u}, // nb -> Latn
- {0x804D0000u, 40u}, // nca -> Latn
- {0x904D0000u, 40u}, // nce -> Latn
- {0x944D0000u, 40u}, // ncf -> Latn
- {0x9C4D0000u, 40u}, // nch -> Latn
- {0xB84D0000u, 40u}, // nco -> Latn
- {0xD04D0000u, 40u}, // ncu -> Latn
- {0x6E640000u, 40u}, // nd -> Latn
- {0x886D0000u, 40u}, // ndc -> Latn
- {0xC86D0000u, 40u}, // nds -> Latn
- {0x6E650000u, 16u}, // ne -> Deva
- {0x848D0000u, 40u}, // neb -> Latn
- {0xD88D0000u, 16u}, // new -> Deva
- {0xDC8D0000u, 40u}, // nex -> Latn
- {0xC4AD0000u, 40u}, // nfr -> Latn
- {0x6E670000u, 40u}, // ng -> Latn
- {0x80CD0000u, 40u}, // nga -> Latn
- {0x84CD0000u, 40u}, // ngb -> Latn
- {0xACCD0000u, 40u}, // ngl -> Latn
- {0x84ED0000u, 40u}, // nhb -> Latn
- {0x90ED0000u, 40u}, // nhe -> Latn
- {0xD8ED0000u, 40u}, // nhw -> Latn
- {0x950D0000u, 40u}, // nif -> Latn
- {0xA10D0000u, 40u}, // nii -> Latn
- {0xA50D0000u, 40u}, // nij -> Latn
- {0xB50D0000u, 40u}, // nin -> Latn
- {0xD10D0000u, 40u}, // niu -> Latn
- {0xE10D0000u, 40u}, // niy -> Latn
- {0xE50D0000u, 40u}, // niz -> Latn
- {0xB92D0000u, 40u}, // njo -> Latn
- {0x994D0000u, 40u}, // nkg -> Latn
- {0xB94D0000u, 40u}, // nko -> Latn
- {0x6E6C0000u, 40u}, // nl -> Latn
- {0x998D0000u, 40u}, // nmg -> Latn
- {0xE58D0000u, 40u}, // nmz -> Latn
- {0x6E6E0000u, 40u}, // nn -> Latn
- {0x95AD0000u, 40u}, // nnf -> Latn
- {0x9DAD0000u, 40u}, // nnh -> Latn
- {0xA9AD0000u, 40u}, // nnk -> Latn
- {0xB1AD0000u, 40u}, // nnm -> Latn
- {0x6E6F0000u, 40u}, // no -> Latn
- {0x8DCD0000u, 38u}, // nod -> Lana
- {0x91CD0000u, 16u}, // noe -> Deva
- {0xB5CD0000u, 64u}, // non -> Runr
- {0xBDCD0000u, 40u}, // nop -> Latn
- {0xD1CD0000u, 40u}, // nou -> Latn
- {0xBA0D0000u, 54u}, // nqo -> Nkoo
- {0x6E720000u, 40u}, // nr -> Latn
- {0x862D0000u, 40u}, // nrb -> Latn
- {0xAA4D0000u, 9u}, // nsk -> Cans
- {0xB64D0000u, 40u}, // nsn -> Latn
- {0xBA4D0000u, 40u}, // nso -> Latn
- {0xCA4D0000u, 40u}, // nss -> Latn
- {0xB26D0000u, 40u}, // ntm -> Latn
- {0xC66D0000u, 40u}, // ntr -> Latn
- {0xA28D0000u, 40u}, // nui -> Latn
- {0xBE8D0000u, 40u}, // nup -> Latn
- {0xCA8D0000u, 40u}, // nus -> Latn
- {0xD68D0000u, 40u}, // nuv -> Latn
- {0xDE8D0000u, 40u}, // nux -> Latn
- {0x6E760000u, 40u}, // nv -> Latn
- {0x86CD0000u, 40u}, // nwb -> Latn
- {0xC2ED0000u, 40u}, // nxq -> Latn
- {0xC6ED0000u, 40u}, // nxr -> Latn
- {0x6E790000u, 40u}, // ny -> Latn
- {0xB30D0000u, 40u}, // nym -> Latn
- {0xB70D0000u, 40u}, // nyn -> Latn
- {0xA32D0000u, 40u}, // nzi -> Latn
- {0x6F630000u, 40u}, // oc -> Latn
- {0x88CE0000u, 40u}, // ogc -> Latn
- {0xC54E0000u, 40u}, // okr -> Latn
- {0xD54E0000u, 40u}, // okv -> Latn
- {0x6F6D0000u, 40u}, // om -> Latn
- {0x99AE0000u, 40u}, // ong -> Latn
- {0xB5AE0000u, 40u}, // onn -> Latn
- {0xC9AE0000u, 40u}, // ons -> Latn
- {0xB1EE0000u, 40u}, // opm -> Latn
- {0x6F720000u, 57u}, // or -> Orya
- {0xBA2E0000u, 40u}, // oro -> Latn
+ {0xBF2C0000u, 44u}, // mzp -> Latn
+ {0xDB2C0000u, 44u}, // mzw -> Latn
+ {0xE72C0000u, 44u}, // mzz -> Latn
+ {0x6E610000u, 44u}, // na -> Latn
+ {0x880D0000u, 44u}, // nac -> Latn
+ {0x940D0000u, 44u}, // naf -> Latn
+ {0xA80D0000u, 44u}, // nak -> Latn
+ {0xB40D0000u, 27u}, // nan -> Hans
+ {0xBC0D0000u, 44u}, // nap -> Latn
+ {0xC00D0000u, 44u}, // naq -> Latn
+ {0xC80D0000u, 44u}, // nas -> Latn
+ {0x6E620000u, 44u}, // nb -> Latn
+ {0x804D0000u, 44u}, // nca -> Latn
+ {0x904D0000u, 44u}, // nce -> Latn
+ {0x944D0000u, 44u}, // ncf -> Latn
+ {0x9C4D0000u, 44u}, // nch -> Latn
+ {0xB84D0000u, 44u}, // nco -> Latn
+ {0xD04D0000u, 44u}, // ncu -> Latn
+ {0x6E640000u, 44u}, // nd -> Latn
+ {0x886D0000u, 44u}, // ndc -> Latn
+ {0xC86D0000u, 44u}, // nds -> Latn
+ {0x6E650000u, 17u}, // ne -> Deva
+ {0x848D0000u, 44u}, // neb -> Latn
+ {0xD88D0000u, 17u}, // new -> Deva
+ {0xDC8D0000u, 44u}, // nex -> Latn
+ {0xC4AD0000u, 44u}, // nfr -> Latn
+ {0x6E670000u, 44u}, // ng -> Latn
+ {0x80CD0000u, 44u}, // nga -> Latn
+ {0x84CD0000u, 44u}, // ngb -> Latn
+ {0xACCD0000u, 44u}, // ngl -> Latn
+ {0x84ED0000u, 44u}, // nhb -> Latn
+ {0x90ED0000u, 44u}, // nhe -> Latn
+ {0xD8ED0000u, 44u}, // nhw -> Latn
+ {0x950D0000u, 44u}, // nif -> Latn
+ {0xA10D0000u, 44u}, // nii -> Latn
+ {0xA50D0000u, 44u}, // nij -> Latn
+ {0xB50D0000u, 44u}, // nin -> Latn
+ {0xD10D0000u, 44u}, // niu -> Latn
+ {0xE10D0000u, 44u}, // niy -> Latn
+ {0xE50D0000u, 44u}, // niz -> Latn
+ {0xB92D0000u, 44u}, // njo -> Latn
+ {0x994D0000u, 44u}, // nkg -> Latn
+ {0xB94D0000u, 44u}, // nko -> Latn
+ {0x6E6C0000u, 44u}, // nl -> Latn
+ {0x998D0000u, 44u}, // nmg -> Latn
+ {0xE58D0000u, 44u}, // nmz -> Latn
+ {0x6E6E0000u, 44u}, // nn -> Latn
+ {0x95AD0000u, 44u}, // nnf -> Latn
+ {0x9DAD0000u, 44u}, // nnh -> Latn
+ {0xA9AD0000u, 44u}, // nnk -> Latn
+ {0xB1AD0000u, 44u}, // nnm -> Latn
+ {0xBDAD0000u, 91u}, // nnp -> Wcho
+ {0x6E6F0000u, 44u}, // no -> Latn
+ {0x8DCD0000u, 42u}, // nod -> Lana
+ {0x91CD0000u, 17u}, // noe -> Deva
+ {0xB5CD0000u, 69u}, // non -> Runr
+ {0xBDCD0000u, 44u}, // nop -> Latn
+ {0xD1CD0000u, 44u}, // nou -> Latn
+ {0xBA0D0000u, 58u}, // nqo -> Nkoo
+ {0x6E720000u, 44u}, // nr -> Latn
+ {0x862D0000u, 44u}, // nrb -> Latn
+ {0xAA4D0000u, 10u}, // nsk -> Cans
+ {0xB64D0000u, 44u}, // nsn -> Latn
+ {0xBA4D0000u, 44u}, // nso -> Latn
+ {0xCA4D0000u, 44u}, // nss -> Latn
+ {0xB26D0000u, 44u}, // ntm -> Latn
+ {0xC66D0000u, 44u}, // ntr -> Latn
+ {0xA28D0000u, 44u}, // nui -> Latn
+ {0xBE8D0000u, 44u}, // nup -> Latn
+ {0xCA8D0000u, 44u}, // nus -> Latn
+ {0xD68D0000u, 44u}, // nuv -> Latn
+ {0xDE8D0000u, 44u}, // nux -> Latn
+ {0x6E760000u, 44u}, // nv -> Latn
+ {0x86CD0000u, 44u}, // nwb -> Latn
+ {0xC2ED0000u, 44u}, // nxq -> Latn
+ {0xC6ED0000u, 44u}, // nxr -> Latn
+ {0x6E790000u, 44u}, // ny -> Latn
+ {0xB30D0000u, 44u}, // nym -> Latn
+ {0xB70D0000u, 44u}, // nyn -> Latn
+ {0xA32D0000u, 44u}, // nzi -> Latn
+ {0x6F630000u, 44u}, // oc -> Latn
+ {0x88CE0000u, 44u}, // ogc -> Latn
+ {0xC54E0000u, 44u}, // okr -> Latn
+ {0xD54E0000u, 44u}, // okv -> Latn
+ {0x6F6D0000u, 44u}, // om -> Latn
+ {0x99AE0000u, 44u}, // ong -> Latn
+ {0xB5AE0000u, 44u}, // onn -> Latn
+ {0xC9AE0000u, 44u}, // ons -> Latn
+ {0xB1EE0000u, 44u}, // opm -> Latn
+ {0x6F720000u, 62u}, // or -> Orya
+ {0xBA2E0000u, 44u}, // oro -> Latn
{0xD22E0000u, 1u}, // oru -> Arab
- {0x6F730000u, 15u}, // os -> Cyrl
- {0x824E0000u, 58u}, // osa -> Osge
+ {0x6F730000u, 16u}, // os -> Cyrl
+ {0x824E0000u, 63u}, // osa -> Osge
{0x826E0000u, 1u}, // ota -> Arab
- {0xAA6E0000u, 56u}, // otk -> Orkh
- {0xB32E0000u, 40u}, // ozm -> Latn
- {0x70610000u, 23u}, // pa -> Guru
+ {0xAA6E0000u, 61u}, // otk -> Orkh
+ {0xB32E0000u, 44u}, // ozm -> Latn
+ {0x70610000u, 26u}, // pa -> Guru
{0x7061504Bu, 1u}, // pa-PK -> Arab
- {0x980F0000u, 40u}, // pag -> Latn
- {0xAC0F0000u, 60u}, // pal -> Phli
- {0xB00F0000u, 40u}, // pam -> Latn
- {0xBC0F0000u, 40u}, // pap -> Latn
- {0xD00F0000u, 40u}, // pau -> Latn
- {0xA02F0000u, 40u}, // pbi -> Latn
- {0x8C4F0000u, 40u}, // pcd -> Latn
- {0xB04F0000u, 40u}, // pcm -> Latn
- {0x886F0000u, 40u}, // pdc -> Latn
- {0xCC6F0000u, 40u}, // pdt -> Latn
- {0x8C8F0000u, 40u}, // ped -> Latn
- {0xB88F0000u, 84u}, // peo -> Xpeo
- {0xDC8F0000u, 40u}, // pex -> Latn
- {0xACAF0000u, 40u}, // pfl -> Latn
+ {0x980F0000u, 44u}, // pag -> Latn
+ {0xAC0F0000u, 65u}, // pal -> Phli
+ {0xB00F0000u, 44u}, // pam -> Latn
+ {0xBC0F0000u, 44u}, // pap -> Latn
+ {0xD00F0000u, 44u}, // pau -> Latn
+ {0xA02F0000u, 44u}, // pbi -> Latn
+ {0x8C4F0000u, 44u}, // pcd -> Latn
+ {0xB04F0000u, 44u}, // pcm -> Latn
+ {0x886F0000u, 44u}, // pdc -> Latn
+ {0xCC6F0000u, 44u}, // pdt -> Latn
+ {0x8C8F0000u, 44u}, // ped -> Latn
+ {0xB88F0000u, 92u}, // peo -> Xpeo
+ {0xDC8F0000u, 44u}, // pex -> Latn
+ {0xACAF0000u, 44u}, // pfl -> Latn
{0xACEF0000u, 1u}, // phl -> Arab
- {0xB4EF0000u, 61u}, // phn -> Phnx
- {0xAD0F0000u, 40u}, // pil -> Latn
- {0xBD0F0000u, 40u}, // pip -> Latn
+ {0xB4EF0000u, 66u}, // phn -> Phnx
+ {0xAD0F0000u, 44u}, // pil -> Latn
+ {0xBD0F0000u, 44u}, // pip -> Latn
{0x814F0000u, 8u}, // pka -> Brah
- {0xB94F0000u, 40u}, // pko -> Latn
- {0x706C0000u, 40u}, // pl -> Latn
- {0x816F0000u, 40u}, // pla -> Latn
- {0xC98F0000u, 40u}, // pms -> Latn
- {0x99AF0000u, 40u}, // png -> Latn
- {0xB5AF0000u, 40u}, // pnn -> Latn
- {0xCDAF0000u, 21u}, // pnt -> Grek
- {0xB5CF0000u, 40u}, // pon -> Latn
- {0xB9EF0000u, 40u}, // ppo -> Latn
- {0x822F0000u, 34u}, // pra -> Khar
+ {0xB94F0000u, 44u}, // pko -> Latn
+ {0x706C0000u, 44u}, // pl -> Latn
+ {0x816F0000u, 44u}, // pla -> Latn
+ {0xC98F0000u, 44u}, // pms -> Latn
+ {0x99AF0000u, 44u}, // png -> Latn
+ {0xB5AF0000u, 44u}, // pnn -> Latn
+ {0xCDAF0000u, 24u}, // pnt -> Grek
+ {0xB5CF0000u, 44u}, // pon -> Latn
+ {0x81EF0000u, 17u}, // ppa -> Deva
+ {0xB9EF0000u, 44u}, // ppo -> Latn
+ {0x822F0000u, 38u}, // pra -> Khar
{0x8E2F0000u, 1u}, // prd -> Arab
- {0x9A2F0000u, 40u}, // prg -> Latn
+ {0x9A2F0000u, 44u}, // prg -> Latn
{0x70730000u, 1u}, // ps -> Arab
- {0xCA4F0000u, 40u}, // pss -> Latn
- {0x70740000u, 40u}, // pt -> Latn
- {0xBE6F0000u, 40u}, // ptp -> Latn
- {0xD28F0000u, 40u}, // puu -> Latn
- {0x82CF0000u, 40u}, // pwa -> Latn
- {0x71750000u, 40u}, // qu -> Latn
- {0x8A900000u, 40u}, // quc -> Latn
- {0x9A900000u, 40u}, // qug -> Latn
- {0xA0110000u, 40u}, // rai -> Latn
- {0xA4110000u, 16u}, // raj -> Deva
- {0xB8110000u, 40u}, // rao -> Latn
- {0x94510000u, 40u}, // rcf -> Latn
- {0xA4910000u, 40u}, // rej -> Latn
- {0xAC910000u, 40u}, // rel -> Latn
- {0xC8910000u, 40u}, // res -> Latn
- {0xB4D10000u, 40u}, // rgn -> Latn
+ {0xCA4F0000u, 44u}, // pss -> Latn
+ {0x70740000u, 44u}, // pt -> Latn
+ {0xBE6F0000u, 44u}, // ptp -> Latn
+ {0xD28F0000u, 44u}, // puu -> Latn
+ {0x82CF0000u, 44u}, // pwa -> Latn
+ {0x71750000u, 44u}, // qu -> Latn
+ {0x8A900000u, 44u}, // quc -> Latn
+ {0x9A900000u, 44u}, // qug -> Latn
+ {0xA0110000u, 44u}, // rai -> Latn
+ {0xA4110000u, 17u}, // raj -> Deva
+ {0xB8110000u, 44u}, // rao -> Latn
+ {0x94510000u, 44u}, // rcf -> Latn
+ {0xA4910000u, 44u}, // rej -> Latn
+ {0xAC910000u, 44u}, // rel -> Latn
+ {0xC8910000u, 44u}, // res -> Latn
+ {0xB4D10000u, 44u}, // rgn -> Latn
{0x98F10000u, 1u}, // rhg -> Arab
- {0x81110000u, 40u}, // ria -> Latn
- {0x95110000u, 78u}, // rif -> Tfng
- {0x95114E4Cu, 40u}, // rif-NL -> Latn
- {0xC9310000u, 16u}, // rjs -> Deva
+ {0x81110000u, 44u}, // ria -> Latn
+ {0x95110000u, 85u}, // rif -> Tfng
+ {0x95114E4Cu, 44u}, // rif-NL -> Latn
+ {0xC9310000u, 17u}, // rjs -> Deva
{0xCD510000u, 7u}, // rkt -> Beng
- {0x726D0000u, 40u}, // rm -> Latn
- {0x95910000u, 40u}, // rmf -> Latn
- {0xB9910000u, 40u}, // rmo -> Latn
+ {0x726D0000u, 44u}, // rm -> Latn
+ {0x95910000u, 44u}, // rmf -> Latn
+ {0xB9910000u, 44u}, // rmo -> Latn
{0xCD910000u, 1u}, // rmt -> Arab
- {0xD1910000u, 40u}, // rmu -> Latn
- {0x726E0000u, 40u}, // rn -> Latn
- {0x81B10000u, 40u}, // rna -> Latn
- {0x99B10000u, 40u}, // rng -> Latn
- {0x726F0000u, 40u}, // ro -> Latn
- {0x85D10000u, 40u}, // rob -> Latn
- {0x95D10000u, 40u}, // rof -> Latn
- {0xB9D10000u, 40u}, // roo -> Latn
- {0xBA310000u, 40u}, // rro -> Latn
- {0xB2710000u, 40u}, // rtm -> Latn
- {0x72750000u, 15u}, // ru -> Cyrl
- {0x92910000u, 15u}, // rue -> Cyrl
- {0x9A910000u, 40u}, // rug -> Latn
- {0x72770000u, 40u}, // rw -> Latn
- {0xAAD10000u, 40u}, // rwk -> Latn
- {0xBAD10000u, 40u}, // rwo -> Latn
- {0xD3110000u, 33u}, // ryu -> Kana
- {0x73610000u, 16u}, // sa -> Deva
- {0x94120000u, 40u}, // saf -> Latn
- {0x9C120000u, 15u}, // sah -> Cyrl
- {0xC0120000u, 40u}, // saq -> Latn
- {0xC8120000u, 40u}, // sas -> Latn
- {0xCC120000u, 40u}, // sat -> Latn
- {0xE4120000u, 67u}, // saz -> Saur
- {0x80320000u, 40u}, // sba -> Latn
- {0x90320000u, 40u}, // sbe -> Latn
- {0xBC320000u, 40u}, // sbp -> Latn
- {0x73630000u, 40u}, // sc -> Latn
- {0xA8520000u, 16u}, // sck -> Deva
+ {0xD1910000u, 44u}, // rmu -> Latn
+ {0x726E0000u, 44u}, // rn -> Latn
+ {0x81B10000u, 44u}, // rna -> Latn
+ {0x99B10000u, 44u}, // rng -> Latn
+ {0x726F0000u, 44u}, // ro -> Latn
+ {0x85D10000u, 44u}, // rob -> Latn
+ {0x95D10000u, 44u}, // rof -> Latn
+ {0xB9D10000u, 44u}, // roo -> Latn
+ {0xBA310000u, 44u}, // rro -> Latn
+ {0xB2710000u, 44u}, // rtm -> Latn
+ {0x72750000u, 16u}, // ru -> Cyrl
+ {0x92910000u, 16u}, // rue -> Cyrl
+ {0x9A910000u, 44u}, // rug -> Latn
+ {0x72770000u, 44u}, // rw -> Latn
+ {0xAAD10000u, 44u}, // rwk -> Latn
+ {0xBAD10000u, 44u}, // rwo -> Latn
+ {0xD3110000u, 37u}, // ryu -> Kana
+ {0x73610000u, 17u}, // sa -> Deva
+ {0x94120000u, 44u}, // saf -> Latn
+ {0x9C120000u, 16u}, // sah -> Cyrl
+ {0xC0120000u, 44u}, // saq -> Latn
+ {0xC8120000u, 44u}, // sas -> Latn
+ {0xCC120000u, 44u}, // sat -> Latn
+ {0xD4120000u, 44u}, // sav -> Latn
+ {0xE4120000u, 72u}, // saz -> Saur
+ {0x80320000u, 44u}, // sba -> Latn
+ {0x90320000u, 44u}, // sbe -> Latn
+ {0xBC320000u, 44u}, // sbp -> Latn
+ {0x73630000u, 44u}, // sc -> Latn
+ {0xA8520000u, 17u}, // sck -> Deva
{0xAC520000u, 1u}, // scl -> Arab
- {0xB4520000u, 40u}, // scn -> Latn
- {0xB8520000u, 40u}, // sco -> Latn
- {0xC8520000u, 40u}, // scs -> Latn
+ {0xB4520000u, 44u}, // scn -> Latn
+ {0xB8520000u, 44u}, // sco -> Latn
+ {0xC8520000u, 44u}, // scs -> Latn
{0x73640000u, 1u}, // sd -> Arab
- {0x88720000u, 40u}, // sdc -> Latn
+ {0x88720000u, 44u}, // sdc -> Latn
{0x9C720000u, 1u}, // sdh -> Arab
- {0x73650000u, 40u}, // se -> Latn
- {0x94920000u, 40u}, // sef -> Latn
- {0x9C920000u, 40u}, // seh -> Latn
- {0xA0920000u, 40u}, // sei -> Latn
- {0xC8920000u, 40u}, // ses -> Latn
- {0x73670000u, 40u}, // sg -> Latn
- {0x80D20000u, 55u}, // sga -> Ogam
- {0xC8D20000u, 40u}, // sgs -> Latn
- {0xD8D20000u, 18u}, // sgw -> Ethi
- {0xE4D20000u, 40u}, // sgz -> Latn
- {0x73680000u, 40u}, // sh -> Latn
- {0xA0F20000u, 78u}, // shi -> Tfng
- {0xA8F20000u, 40u}, // shk -> Latn
- {0xB4F20000u, 52u}, // shn -> Mymr
+ {0x73650000u, 44u}, // se -> Latn
+ {0x94920000u, 44u}, // sef -> Latn
+ {0x9C920000u, 44u}, // seh -> Latn
+ {0xA0920000u, 44u}, // sei -> Latn
+ {0xC8920000u, 44u}, // ses -> Latn
+ {0x73670000u, 44u}, // sg -> Latn
+ {0x80D20000u, 60u}, // sga -> Ogam
+ {0xC8D20000u, 44u}, // sgs -> Latn
+ {0xD8D20000u, 19u}, // sgw -> Ethi
+ {0xE4D20000u, 44u}, // sgz -> Latn
+ {0x73680000u, 44u}, // sh -> Latn
+ {0xA0F20000u, 85u}, // shi -> Tfng
+ {0xA8F20000u, 44u}, // shk -> Latn
+ {0xB4F20000u, 56u}, // shn -> Mymr
{0xD0F20000u, 1u}, // shu -> Arab
- {0x73690000u, 69u}, // si -> Sinh
- {0x8D120000u, 40u}, // sid -> Latn
- {0x99120000u, 40u}, // sig -> Latn
- {0xAD120000u, 40u}, // sil -> Latn
- {0xB1120000u, 40u}, // sim -> Latn
- {0xC5320000u, 40u}, // sjr -> Latn
- {0x736B0000u, 40u}, // sk -> Latn
- {0x89520000u, 40u}, // skc -> Latn
+ {0x73690000u, 74u}, // si -> Sinh
+ {0x8D120000u, 44u}, // sid -> Latn
+ {0x99120000u, 44u}, // sig -> Latn
+ {0xAD120000u, 44u}, // sil -> Latn
+ {0xB1120000u, 44u}, // sim -> Latn
+ {0xC5320000u, 44u}, // sjr -> Latn
+ {0x736B0000u, 44u}, // sk -> Latn
+ {0x89520000u, 44u}, // skc -> Latn
{0xC5520000u, 1u}, // skr -> Arab
- {0xC9520000u, 40u}, // sks -> Latn
- {0x736C0000u, 40u}, // sl -> Latn
- {0x8D720000u, 40u}, // sld -> Latn
- {0xA1720000u, 40u}, // sli -> Latn
- {0xAD720000u, 40u}, // sll -> Latn
- {0xE1720000u, 40u}, // sly -> Latn
- {0x736D0000u, 40u}, // sm -> Latn
- {0x81920000u, 40u}, // sma -> Latn
- {0xA5920000u, 40u}, // smj -> Latn
- {0xB5920000u, 40u}, // smn -> Latn
- {0xBD920000u, 65u}, // smp -> Samr
- {0xC1920000u, 40u}, // smq -> Latn
- {0xC9920000u, 40u}, // sms -> Latn
- {0x736E0000u, 40u}, // sn -> Latn
- {0x89B20000u, 40u}, // snc -> Latn
- {0xA9B20000u, 40u}, // snk -> Latn
- {0xBDB20000u, 40u}, // snp -> Latn
- {0xDDB20000u, 40u}, // snx -> Latn
- {0xE1B20000u, 40u}, // sny -> Latn
- {0x736F0000u, 40u}, // so -> Latn
- {0xA9D20000u, 40u}, // sok -> Latn
- {0xC1D20000u, 40u}, // soq -> Latn
- {0xD1D20000u, 80u}, // sou -> Thai
- {0xE1D20000u, 40u}, // soy -> Latn
- {0x8DF20000u, 40u}, // spd -> Latn
- {0xADF20000u, 40u}, // spl -> Latn
- {0xC9F20000u, 40u}, // sps -> Latn
- {0x73710000u, 40u}, // sq -> Latn
- {0x73720000u, 15u}, // sr -> Cyrl
- {0x73724D45u, 40u}, // sr-ME -> Latn
- {0x7372524Fu, 40u}, // sr-RO -> Latn
- {0x73725255u, 40u}, // sr-RU -> Latn
- {0x73725452u, 40u}, // sr-TR -> Latn
- {0x86320000u, 70u}, // srb -> Sora
- {0xB6320000u, 40u}, // srn -> Latn
- {0xC6320000u, 40u}, // srr -> Latn
- {0xDE320000u, 16u}, // srx -> Deva
- {0x73730000u, 40u}, // ss -> Latn
- {0x8E520000u, 40u}, // ssd -> Latn
- {0x9A520000u, 40u}, // ssg -> Latn
- {0xE2520000u, 40u}, // ssy -> Latn
- {0x73740000u, 40u}, // st -> Latn
- {0xAA720000u, 40u}, // stk -> Latn
- {0xC2720000u, 40u}, // stq -> Latn
- {0x73750000u, 40u}, // su -> Latn
- {0x82920000u, 40u}, // sua -> Latn
- {0x92920000u, 40u}, // sue -> Latn
- {0xAA920000u, 40u}, // suk -> Latn
- {0xC6920000u, 40u}, // sur -> Latn
- {0xCA920000u, 40u}, // sus -> Latn
- {0x73760000u, 40u}, // sv -> Latn
- {0x73770000u, 40u}, // sw -> Latn
+ {0xC9520000u, 44u}, // sks -> Latn
+ {0x736C0000u, 44u}, // sl -> Latn
+ {0x8D720000u, 44u}, // sld -> Latn
+ {0xA1720000u, 44u}, // sli -> Latn
+ {0xAD720000u, 44u}, // sll -> Latn
+ {0xE1720000u, 44u}, // sly -> Latn
+ {0x736D0000u, 44u}, // sm -> Latn
+ {0x81920000u, 44u}, // sma -> Latn
+ {0xA5920000u, 44u}, // smj -> Latn
+ {0xB5920000u, 44u}, // smn -> Latn
+ {0xBD920000u, 70u}, // smp -> Samr
+ {0xC1920000u, 44u}, // smq -> Latn
+ {0xC9920000u, 44u}, // sms -> Latn
+ {0x736E0000u, 44u}, // sn -> Latn
+ {0x89B20000u, 44u}, // snc -> Latn
+ {0xA9B20000u, 44u}, // snk -> Latn
+ {0xBDB20000u, 44u}, // snp -> Latn
+ {0xDDB20000u, 44u}, // snx -> Latn
+ {0xE1B20000u, 44u}, // sny -> Latn
+ {0x736F0000u, 44u}, // so -> Latn
+ {0x99D20000u, 75u}, // sog -> Sogd
+ {0xA9D20000u, 44u}, // sok -> Latn
+ {0xC1D20000u, 44u}, // soq -> Latn
+ {0xD1D20000u, 87u}, // sou -> Thai
+ {0xE1D20000u, 44u}, // soy -> Latn
+ {0x8DF20000u, 44u}, // spd -> Latn
+ {0xADF20000u, 44u}, // spl -> Latn
+ {0xC9F20000u, 44u}, // sps -> Latn
+ {0x73710000u, 44u}, // sq -> Latn
+ {0x73720000u, 16u}, // sr -> Cyrl
+ {0x73724D45u, 44u}, // sr-ME -> Latn
+ {0x7372524Fu, 44u}, // sr-RO -> Latn
+ {0x73725255u, 44u}, // sr-RU -> Latn
+ {0x73725452u, 44u}, // sr-TR -> Latn
+ {0x86320000u, 76u}, // srb -> Sora
+ {0xB6320000u, 44u}, // srn -> Latn
+ {0xC6320000u, 44u}, // srr -> Latn
+ {0xDE320000u, 17u}, // srx -> Deva
+ {0x73730000u, 44u}, // ss -> Latn
+ {0x8E520000u, 44u}, // ssd -> Latn
+ {0x9A520000u, 44u}, // ssg -> Latn
+ {0xE2520000u, 44u}, // ssy -> Latn
+ {0x73740000u, 44u}, // st -> Latn
+ {0xAA720000u, 44u}, // stk -> Latn
+ {0xC2720000u, 44u}, // stq -> Latn
+ {0x73750000u, 44u}, // su -> Latn
+ {0x82920000u, 44u}, // sua -> Latn
+ {0x92920000u, 44u}, // sue -> Latn
+ {0xAA920000u, 44u}, // suk -> Latn
+ {0xC6920000u, 44u}, // sur -> Latn
+ {0xCA920000u, 44u}, // sus -> Latn
+ {0x73760000u, 44u}, // sv -> Latn
+ {0x73770000u, 44u}, // sw -> Latn
{0x86D20000u, 1u}, // swb -> Arab
- {0x8AD20000u, 40u}, // swc -> Latn
- {0x9AD20000u, 40u}, // swg -> Latn
- {0xBED20000u, 40u}, // swp -> Latn
- {0xD6D20000u, 16u}, // swv -> Deva
- {0xB6F20000u, 40u}, // sxn -> Latn
- {0xDAF20000u, 40u}, // sxw -> Latn
+ {0x8AD20000u, 44u}, // swc -> Latn
+ {0x9AD20000u, 44u}, // swg -> Latn
+ {0xBED20000u, 44u}, // swp -> Latn
+ {0xD6D20000u, 17u}, // swv -> Deva
+ {0xB6F20000u, 44u}, // sxn -> Latn
+ {0xDAF20000u, 44u}, // sxw -> Latn
{0xAF120000u, 7u}, // syl -> Beng
- {0xC7120000u, 71u}, // syr -> Syrc
- {0xAF320000u, 40u}, // szl -> Latn
- {0x74610000u, 74u}, // ta -> Taml
- {0xA4130000u, 16u}, // taj -> Deva
- {0xAC130000u, 40u}, // tal -> Latn
- {0xB4130000u, 40u}, // tan -> Latn
- {0xC0130000u, 40u}, // taq -> Latn
- {0x88330000u, 40u}, // tbc -> Latn
- {0x8C330000u, 40u}, // tbd -> Latn
- {0x94330000u, 40u}, // tbf -> Latn
- {0x98330000u, 40u}, // tbg -> Latn
- {0xB8330000u, 40u}, // tbo -> Latn
- {0xD8330000u, 40u}, // tbw -> Latn
- {0xE4330000u, 40u}, // tbz -> Latn
- {0xA0530000u, 40u}, // tci -> Latn
- {0xE0530000u, 36u}, // tcy -> Knda
- {0x8C730000u, 72u}, // tdd -> Tale
- {0x98730000u, 16u}, // tdg -> Deva
- {0x9C730000u, 16u}, // tdh -> Deva
- {0x74650000u, 77u}, // te -> Telu
- {0x8C930000u, 40u}, // ted -> Latn
- {0xB0930000u, 40u}, // tem -> Latn
- {0xB8930000u, 40u}, // teo -> Latn
- {0xCC930000u, 40u}, // tet -> Latn
- {0xA0B30000u, 40u}, // tfi -> Latn
- {0x74670000u, 15u}, // tg -> Cyrl
+ {0xC7120000u, 78u}, // syr -> Syrc
+ {0xAF320000u, 44u}, // szl -> Latn
+ {0x74610000u, 81u}, // ta -> Taml
+ {0xA4130000u, 17u}, // taj -> Deva
+ {0xAC130000u, 44u}, // tal -> Latn
+ {0xB4130000u, 44u}, // tan -> Latn
+ {0xC0130000u, 44u}, // taq -> Latn
+ {0x88330000u, 44u}, // tbc -> Latn
+ {0x8C330000u, 44u}, // tbd -> Latn
+ {0x94330000u, 44u}, // tbf -> Latn
+ {0x98330000u, 44u}, // tbg -> Latn
+ {0xB8330000u, 44u}, // tbo -> Latn
+ {0xD8330000u, 44u}, // tbw -> Latn
+ {0xE4330000u, 44u}, // tbz -> Latn
+ {0xA0530000u, 44u}, // tci -> Latn
+ {0xE0530000u, 40u}, // tcy -> Knda
+ {0x8C730000u, 79u}, // tdd -> Tale
+ {0x98730000u, 17u}, // tdg -> Deva
+ {0x9C730000u, 17u}, // tdh -> Deva
+ {0xD0730000u, 44u}, // tdu -> Latn
+ {0x74650000u, 84u}, // te -> Telu
+ {0x8C930000u, 44u}, // ted -> Latn
+ {0xB0930000u, 44u}, // tem -> Latn
+ {0xB8930000u, 44u}, // teo -> Latn
+ {0xCC930000u, 44u}, // tet -> Latn
+ {0xA0B30000u, 44u}, // tfi -> Latn
+ {0x74670000u, 16u}, // tg -> Cyrl
{0x7467504Bu, 1u}, // tg-PK -> Arab
- {0x88D30000u, 40u}, // tgc -> Latn
- {0xB8D30000u, 40u}, // tgo -> Latn
- {0xD0D30000u, 40u}, // tgu -> Latn
- {0x74680000u, 80u}, // th -> Thai
- {0xACF30000u, 16u}, // thl -> Deva
- {0xC0F30000u, 16u}, // thq -> Deva
- {0xC4F30000u, 16u}, // thr -> Deva
- {0x74690000u, 18u}, // ti -> Ethi
- {0x95130000u, 40u}, // tif -> Latn
- {0x99130000u, 18u}, // tig -> Ethi
- {0xA9130000u, 40u}, // tik -> Latn
- {0xB1130000u, 40u}, // tim -> Latn
- {0xB9130000u, 40u}, // tio -> Latn
- {0xD5130000u, 40u}, // tiv -> Latn
- {0x746B0000u, 40u}, // tk -> Latn
- {0xAD530000u, 40u}, // tkl -> Latn
- {0xC5530000u, 40u}, // tkr -> Latn
- {0xCD530000u, 16u}, // tkt -> Deva
- {0x746C0000u, 40u}, // tl -> Latn
- {0x95730000u, 40u}, // tlf -> Latn
- {0xDD730000u, 40u}, // tlx -> Latn
- {0xE1730000u, 40u}, // tly -> Latn
- {0x9D930000u, 40u}, // tmh -> Latn
- {0xE1930000u, 40u}, // tmy -> Latn
- {0x746E0000u, 40u}, // tn -> Latn
- {0x9DB30000u, 40u}, // tnh -> Latn
- {0x746F0000u, 40u}, // to -> Latn
- {0x95D30000u, 40u}, // tof -> Latn
- {0x99D30000u, 40u}, // tog -> Latn
- {0xC1D30000u, 40u}, // toq -> Latn
- {0xA1F30000u, 40u}, // tpi -> Latn
- {0xB1F30000u, 40u}, // tpm -> Latn
- {0xE5F30000u, 40u}, // tpz -> Latn
- {0xBA130000u, 40u}, // tqo -> Latn
- {0x74720000u, 40u}, // tr -> Latn
- {0xD2330000u, 40u}, // tru -> Latn
- {0xD6330000u, 40u}, // trv -> Latn
+ {0x88D30000u, 44u}, // tgc -> Latn
+ {0xB8D30000u, 44u}, // tgo -> Latn
+ {0xD0D30000u, 44u}, // tgu -> Latn
+ {0x74680000u, 87u}, // th -> Thai
+ {0xACF30000u, 17u}, // thl -> Deva
+ {0xC0F30000u, 17u}, // thq -> Deva
+ {0xC4F30000u, 17u}, // thr -> Deva
+ {0x74690000u, 19u}, // ti -> Ethi
+ {0x95130000u, 44u}, // tif -> Latn
+ {0x99130000u, 19u}, // tig -> Ethi
+ {0xA9130000u, 44u}, // tik -> Latn
+ {0xB1130000u, 44u}, // tim -> Latn
+ {0xB9130000u, 44u}, // tio -> Latn
+ {0xD5130000u, 44u}, // tiv -> Latn
+ {0x746B0000u, 44u}, // tk -> Latn
+ {0xAD530000u, 44u}, // tkl -> Latn
+ {0xC5530000u, 44u}, // tkr -> Latn
+ {0xCD530000u, 17u}, // tkt -> Deva
+ {0x746C0000u, 44u}, // tl -> Latn
+ {0x95730000u, 44u}, // tlf -> Latn
+ {0xDD730000u, 44u}, // tlx -> Latn
+ {0xE1730000u, 44u}, // tly -> Latn
+ {0x9D930000u, 44u}, // tmh -> Latn
+ {0xE1930000u, 44u}, // tmy -> Latn
+ {0x746E0000u, 44u}, // tn -> Latn
+ {0x9DB30000u, 44u}, // tnh -> Latn
+ {0x746F0000u, 44u}, // to -> Latn
+ {0x95D30000u, 44u}, // tof -> Latn
+ {0x99D30000u, 44u}, // tog -> Latn
+ {0xC1D30000u, 44u}, // toq -> Latn
+ {0xA1F30000u, 44u}, // tpi -> Latn
+ {0xB1F30000u, 44u}, // tpm -> Latn
+ {0xE5F30000u, 44u}, // tpz -> Latn
+ {0xBA130000u, 44u}, // tqo -> Latn
+ {0x74720000u, 44u}, // tr -> Latn
+ {0xD2330000u, 44u}, // tru -> Latn
+ {0xD6330000u, 44u}, // trv -> Latn
{0xDA330000u, 1u}, // trw -> Arab
- {0x74730000u, 40u}, // ts -> Latn
- {0x8E530000u, 21u}, // tsd -> Grek
- {0x96530000u, 16u}, // tsf -> Deva
- {0x9A530000u, 40u}, // tsg -> Latn
- {0xA6530000u, 81u}, // tsj -> Tibt
- {0xDA530000u, 40u}, // tsw -> Latn
- {0x74740000u, 15u}, // tt -> Cyrl
- {0x8E730000u, 40u}, // ttd -> Latn
- {0x92730000u, 40u}, // tte -> Latn
- {0xA6730000u, 40u}, // ttj -> Latn
- {0xC6730000u, 40u}, // ttr -> Latn
- {0xCA730000u, 80u}, // tts -> Thai
- {0xCE730000u, 40u}, // ttt -> Latn
- {0x9E930000u, 40u}, // tuh -> Latn
- {0xAE930000u, 40u}, // tul -> Latn
- {0xB2930000u, 40u}, // tum -> Latn
- {0xC2930000u, 40u}, // tuq -> Latn
- {0x8EB30000u, 40u}, // tvd -> Latn
- {0xAEB30000u, 40u}, // tvl -> Latn
- {0xD2B30000u, 40u}, // tvu -> Latn
- {0x9ED30000u, 40u}, // twh -> Latn
- {0xC2D30000u, 40u}, // twq -> Latn
- {0x9AF30000u, 75u}, // txg -> Tang
- {0x74790000u, 40u}, // ty -> Latn
- {0x83130000u, 40u}, // tya -> Latn
- {0xD7130000u, 15u}, // tyv -> Cyrl
- {0xB3330000u, 40u}, // tzm -> Latn
- {0xD0340000u, 40u}, // ubu -> Latn
- {0xB0740000u, 15u}, // udm -> Cyrl
+ {0x74730000u, 44u}, // ts -> Latn
+ {0x8E530000u, 24u}, // tsd -> Grek
+ {0x96530000u, 17u}, // tsf -> Deva
+ {0x9A530000u, 44u}, // tsg -> Latn
+ {0xA6530000u, 88u}, // tsj -> Tibt
+ {0xDA530000u, 44u}, // tsw -> Latn
+ {0x74740000u, 16u}, // tt -> Cyrl
+ {0x8E730000u, 44u}, // ttd -> Latn
+ {0x92730000u, 44u}, // tte -> Latn
+ {0xA6730000u, 44u}, // ttj -> Latn
+ {0xC6730000u, 44u}, // ttr -> Latn
+ {0xCA730000u, 87u}, // tts -> Thai
+ {0xCE730000u, 44u}, // ttt -> Latn
+ {0x9E930000u, 44u}, // tuh -> Latn
+ {0xAE930000u, 44u}, // tul -> Latn
+ {0xB2930000u, 44u}, // tum -> Latn
+ {0xC2930000u, 44u}, // tuq -> Latn
+ {0x8EB30000u, 44u}, // tvd -> Latn
+ {0xAEB30000u, 44u}, // tvl -> Latn
+ {0xD2B30000u, 44u}, // tvu -> Latn
+ {0x9ED30000u, 44u}, // twh -> Latn
+ {0xC2D30000u, 44u}, // twq -> Latn
+ {0x9AF30000u, 82u}, // txg -> Tang
+ {0x74790000u, 44u}, // ty -> Latn
+ {0x83130000u, 44u}, // tya -> Latn
+ {0xD7130000u, 16u}, // tyv -> Cyrl
+ {0xB3330000u, 44u}, // tzm -> Latn
+ {0xD0340000u, 44u}, // ubu -> Latn
+ {0xB0740000u, 16u}, // udm -> Cyrl
{0x75670000u, 1u}, // ug -> Arab
- {0x75674B5Au, 15u}, // ug-KZ -> Cyrl
- {0x75674D4Eu, 15u}, // ug-MN -> Cyrl
- {0x80D40000u, 82u}, // uga -> Ugar
- {0x756B0000u, 15u}, // uk -> Cyrl
- {0xA1740000u, 40u}, // uli -> Latn
- {0x85940000u, 40u}, // umb -> Latn
+ {0x75674B5Au, 16u}, // ug-KZ -> Cyrl
+ {0x75674D4Eu, 16u}, // ug-MN -> Cyrl
+ {0x80D40000u, 89u}, // uga -> Ugar
+ {0x756B0000u, 16u}, // uk -> Cyrl
+ {0xA1740000u, 44u}, // uli -> Latn
+ {0x85940000u, 44u}, // umb -> Latn
{0xC5B40000u, 7u}, // unr -> Beng
- {0xC5B44E50u, 16u}, // unr-NP -> Deva
+ {0xC5B44E50u, 17u}, // unr-NP -> Deva
{0xDDB40000u, 7u}, // unx -> Beng
+ {0xA9D40000u, 44u}, // uok -> Latn
{0x75720000u, 1u}, // ur -> Arab
- {0xA2340000u, 40u}, // uri -> Latn
- {0xCE340000u, 40u}, // urt -> Latn
- {0xDA340000u, 40u}, // urw -> Latn
- {0x82540000u, 40u}, // usa -> Latn
- {0xC6740000u, 40u}, // utr -> Latn
- {0x9EB40000u, 40u}, // uvh -> Latn
- {0xAEB40000u, 40u}, // uvl -> Latn
- {0x757A0000u, 40u}, // uz -> Latn
+ {0xA2340000u, 44u}, // uri -> Latn
+ {0xCE340000u, 44u}, // urt -> Latn
+ {0xDA340000u, 44u}, // urw -> Latn
+ {0x82540000u, 44u}, // usa -> Latn
+ {0xC6740000u, 44u}, // utr -> Latn
+ {0x9EB40000u, 44u}, // uvh -> Latn
+ {0xAEB40000u, 44u}, // uvl -> Latn
+ {0x757A0000u, 44u}, // uz -> Latn
{0x757A4146u, 1u}, // uz-AF -> Arab
- {0x757A434Eu, 15u}, // uz-CN -> Cyrl
- {0x98150000u, 40u}, // vag -> Latn
- {0xA0150000u, 83u}, // vai -> Vaii
- {0xB4150000u, 40u}, // van -> Latn
- {0x76650000u, 40u}, // ve -> Latn
- {0x88950000u, 40u}, // vec -> Latn
- {0xBC950000u, 40u}, // vep -> Latn
- {0x76690000u, 40u}, // vi -> Latn
- {0x89150000u, 40u}, // vic -> Latn
- {0xD5150000u, 40u}, // viv -> Latn
- {0xC9750000u, 40u}, // vls -> Latn
- {0x95950000u, 40u}, // vmf -> Latn
- {0xD9950000u, 40u}, // vmw -> Latn
- {0x766F0000u, 40u}, // vo -> Latn
- {0xCDD50000u, 40u}, // vot -> Latn
- {0xBA350000u, 40u}, // vro -> Latn
- {0xB6950000u, 40u}, // vun -> Latn
- {0xCE950000u, 40u}, // vut -> Latn
- {0x77610000u, 40u}, // wa -> Latn
- {0x90160000u, 40u}, // wae -> Latn
- {0xA4160000u, 40u}, // waj -> Latn
- {0xAC160000u, 18u}, // wal -> Ethi
- {0xB4160000u, 40u}, // wan -> Latn
- {0xC4160000u, 40u}, // war -> Latn
- {0xBC360000u, 40u}, // wbp -> Latn
- {0xC0360000u, 77u}, // wbq -> Telu
- {0xC4360000u, 16u}, // wbr -> Deva
- {0xA0560000u, 40u}, // wci -> Latn
- {0xC4960000u, 40u}, // wer -> Latn
- {0xA0D60000u, 40u}, // wgi -> Latn
- {0x98F60000u, 40u}, // whg -> Latn
- {0x85160000u, 40u}, // wib -> Latn
- {0xD1160000u, 40u}, // wiu -> Latn
- {0xD5160000u, 40u}, // wiv -> Latn
- {0x81360000u, 40u}, // wja -> Latn
- {0xA1360000u, 40u}, // wji -> Latn
- {0xC9760000u, 40u}, // wls -> Latn
- {0xB9960000u, 40u}, // wmo -> Latn
- {0x89B60000u, 40u}, // wnc -> Latn
+ {0x757A434Eu, 16u}, // uz-CN -> Cyrl
+ {0x98150000u, 44u}, // vag -> Latn
+ {0xA0150000u, 90u}, // vai -> Vaii
+ {0xB4150000u, 44u}, // van -> Latn
+ {0x76650000u, 44u}, // ve -> Latn
+ {0x88950000u, 44u}, // vec -> Latn
+ {0xBC950000u, 44u}, // vep -> Latn
+ {0x76690000u, 44u}, // vi -> Latn
+ {0x89150000u, 44u}, // vic -> Latn
+ {0xD5150000u, 44u}, // viv -> Latn
+ {0xC9750000u, 44u}, // vls -> Latn
+ {0x95950000u, 44u}, // vmf -> Latn
+ {0xD9950000u, 44u}, // vmw -> Latn
+ {0x766F0000u, 44u}, // vo -> Latn
+ {0xCDD50000u, 44u}, // vot -> Latn
+ {0xBA350000u, 44u}, // vro -> Latn
+ {0xB6950000u, 44u}, // vun -> Latn
+ {0xCE950000u, 44u}, // vut -> Latn
+ {0x77610000u, 44u}, // wa -> Latn
+ {0x90160000u, 44u}, // wae -> Latn
+ {0xA4160000u, 44u}, // waj -> Latn
+ {0xAC160000u, 19u}, // wal -> Ethi
+ {0xB4160000u, 44u}, // wan -> Latn
+ {0xC4160000u, 44u}, // war -> Latn
+ {0xBC360000u, 44u}, // wbp -> Latn
+ {0xC0360000u, 84u}, // wbq -> Telu
+ {0xC4360000u, 17u}, // wbr -> Deva
+ {0xA0560000u, 44u}, // wci -> Latn
+ {0xC4960000u, 44u}, // wer -> Latn
+ {0xA0D60000u, 44u}, // wgi -> Latn
+ {0x98F60000u, 44u}, // whg -> Latn
+ {0x85160000u, 44u}, // wib -> Latn
+ {0xD1160000u, 44u}, // wiu -> Latn
+ {0xD5160000u, 44u}, // wiv -> Latn
+ {0x81360000u, 44u}, // wja -> Latn
+ {0xA1360000u, 44u}, // wji -> Latn
+ {0xC9760000u, 44u}, // wls -> Latn
+ {0xB9960000u, 44u}, // wmo -> Latn
+ {0x89B60000u, 44u}, // wnc -> Latn
{0xA1B60000u, 1u}, // wni -> Arab
- {0xD1B60000u, 40u}, // wnu -> Latn
- {0x776F0000u, 40u}, // wo -> Latn
- {0x85D60000u, 40u}, // wob -> Latn
- {0xC9D60000u, 40u}, // wos -> Latn
- {0xCA360000u, 40u}, // wrs -> Latn
- {0xAA560000u, 40u}, // wsk -> Latn
- {0xB2760000u, 16u}, // wtm -> Deva
- {0xD2960000u, 24u}, // wuu -> Hans
- {0xD6960000u, 40u}, // wuv -> Latn
- {0x82D60000u, 40u}, // wwa -> Latn
- {0xD4170000u, 40u}, // xav -> Latn
- {0xA0370000u, 40u}, // xbi -> Latn
- {0xC4570000u, 10u}, // xcr -> Cari
- {0xC8970000u, 40u}, // xes -> Latn
- {0x78680000u, 40u}, // xh -> Latn
- {0x81770000u, 40u}, // xla -> Latn
- {0x89770000u, 44u}, // xlc -> Lyci
- {0x8D770000u, 45u}, // xld -> Lydi
- {0x95970000u, 19u}, // xmf -> Geor
- {0xB5970000u, 47u}, // xmn -> Mani
- {0xC5970000u, 48u}, // xmr -> Merc
- {0x81B70000u, 53u}, // xna -> Narb
- {0xC5B70000u, 16u}, // xnr -> Deva
- {0x99D70000u, 40u}, // xog -> Latn
- {0xB5D70000u, 40u}, // xon -> Latn
- {0xC5F70000u, 63u}, // xpr -> Prti
- {0x86370000u, 40u}, // xrb -> Latn
- {0x82570000u, 66u}, // xsa -> Sarb
- {0xA2570000u, 40u}, // xsi -> Latn
- {0xB2570000u, 40u}, // xsm -> Latn
- {0xC6570000u, 16u}, // xsr -> Deva
- {0x92D70000u, 40u}, // xwe -> Latn
- {0xB0180000u, 40u}, // yam -> Latn
- {0xB8180000u, 40u}, // yao -> Latn
- {0xBC180000u, 40u}, // yap -> Latn
- {0xC8180000u, 40u}, // yas -> Latn
- {0xCC180000u, 40u}, // yat -> Latn
- {0xD4180000u, 40u}, // yav -> Latn
- {0xE0180000u, 40u}, // yay -> Latn
- {0xE4180000u, 40u}, // yaz -> Latn
- {0x80380000u, 40u}, // yba -> Latn
- {0x84380000u, 40u}, // ybb -> Latn
- {0xE0380000u, 40u}, // yby -> Latn
- {0xC4980000u, 40u}, // yer -> Latn
- {0xC4D80000u, 40u}, // ygr -> Latn
- {0xD8D80000u, 40u}, // ygw -> Latn
- {0x79690000u, 27u}, // yi -> Hebr
- {0xB9580000u, 40u}, // yko -> Latn
- {0x91780000u, 40u}, // yle -> Latn
- {0x99780000u, 40u}, // ylg -> Latn
- {0xAD780000u, 40u}, // yll -> Latn
- {0xAD980000u, 40u}, // yml -> Latn
- {0x796F0000u, 40u}, // yo -> Latn
- {0xB5D80000u, 40u}, // yon -> Latn
- {0x86380000u, 40u}, // yrb -> Latn
- {0x92380000u, 40u}, // yre -> Latn
- {0xAE380000u, 40u}, // yrl -> Latn
- {0xCA580000u, 40u}, // yss -> Latn
- {0x82980000u, 40u}, // yua -> Latn
- {0x92980000u, 25u}, // yue -> Hant
- {0x9298434Eu, 24u}, // yue-CN -> Hans
- {0xA6980000u, 40u}, // yuj -> Latn
- {0xCE980000u, 40u}, // yut -> Latn
- {0xDA980000u, 40u}, // yuw -> Latn
- {0x7A610000u, 40u}, // za -> Latn
- {0x98190000u, 40u}, // zag -> Latn
+ {0xD1B60000u, 44u}, // wnu -> Latn
+ {0x776F0000u, 44u}, // wo -> Latn
+ {0x85D60000u, 44u}, // wob -> Latn
+ {0xC9D60000u, 44u}, // wos -> Latn
+ {0xCA360000u, 44u}, // wrs -> Latn
+ {0x9A560000u, 21u}, // wsg -> Gong
+ {0xAA560000u, 44u}, // wsk -> Latn
+ {0xB2760000u, 17u}, // wtm -> Deva
+ {0xD2960000u, 27u}, // wuu -> Hans
+ {0xD6960000u, 44u}, // wuv -> Latn
+ {0x82D60000u, 44u}, // wwa -> Latn
+ {0xD4170000u, 44u}, // xav -> Latn
+ {0xA0370000u, 44u}, // xbi -> Latn
+ {0xC4570000u, 11u}, // xcr -> Cari
+ {0xC8970000u, 44u}, // xes -> Latn
+ {0x78680000u, 44u}, // xh -> Latn
+ {0x81770000u, 44u}, // xla -> Latn
+ {0x89770000u, 48u}, // xlc -> Lyci
+ {0x8D770000u, 49u}, // xld -> Lydi
+ {0x95970000u, 20u}, // xmf -> Geor
+ {0xB5970000u, 51u}, // xmn -> Mani
+ {0xC5970000u, 52u}, // xmr -> Merc
+ {0x81B70000u, 57u}, // xna -> Narb
+ {0xC5B70000u, 17u}, // xnr -> Deva
+ {0x99D70000u, 44u}, // xog -> Latn
+ {0xB5D70000u, 44u}, // xon -> Latn
+ {0xC5F70000u, 68u}, // xpr -> Prti
+ {0x86370000u, 44u}, // xrb -> Latn
+ {0x82570000u, 71u}, // xsa -> Sarb
+ {0xA2570000u, 44u}, // xsi -> Latn
+ {0xB2570000u, 44u}, // xsm -> Latn
+ {0xC6570000u, 17u}, // xsr -> Deva
+ {0x92D70000u, 44u}, // xwe -> Latn
+ {0xB0180000u, 44u}, // yam -> Latn
+ {0xB8180000u, 44u}, // yao -> Latn
+ {0xBC180000u, 44u}, // yap -> Latn
+ {0xC8180000u, 44u}, // yas -> Latn
+ {0xCC180000u, 44u}, // yat -> Latn
+ {0xD4180000u, 44u}, // yav -> Latn
+ {0xE0180000u, 44u}, // yay -> Latn
+ {0xE4180000u, 44u}, // yaz -> Latn
+ {0x80380000u, 44u}, // yba -> Latn
+ {0x84380000u, 44u}, // ybb -> Latn
+ {0xE0380000u, 44u}, // yby -> Latn
+ {0xC4980000u, 44u}, // yer -> Latn
+ {0xC4D80000u, 44u}, // ygr -> Latn
+ {0xD8D80000u, 44u}, // ygw -> Latn
+ {0x79690000u, 30u}, // yi -> Hebr
+ {0xB9580000u, 44u}, // yko -> Latn
+ {0x91780000u, 44u}, // yle -> Latn
+ {0x99780000u, 44u}, // ylg -> Latn
+ {0xAD780000u, 44u}, // yll -> Latn
+ {0xAD980000u, 44u}, // yml -> Latn
+ {0x796F0000u, 44u}, // yo -> Latn
+ {0xB5D80000u, 44u}, // yon -> Latn
+ {0x86380000u, 44u}, // yrb -> Latn
+ {0x92380000u, 44u}, // yre -> Latn
+ {0xAE380000u, 44u}, // yrl -> Latn
+ {0xCA580000u, 44u}, // yss -> Latn
+ {0x82980000u, 44u}, // yua -> Latn
+ {0x92980000u, 28u}, // yue -> Hant
+ {0x9298434Eu, 27u}, // yue-CN -> Hans
+ {0xA6980000u, 44u}, // yuj -> Latn
+ {0xCE980000u, 44u}, // yut -> Latn
+ {0xDA980000u, 44u}, // yuw -> Latn
+ {0x7A610000u, 44u}, // za -> Latn
+ {0x98190000u, 44u}, // zag -> Latn
{0xA4790000u, 1u}, // zdj -> Arab
- {0x80990000u, 40u}, // zea -> Latn
- {0x9CD90000u, 78u}, // zgh -> Tfng
- {0x7A680000u, 24u}, // zh -> Hans
- {0x7A684155u, 25u}, // zh-AU -> Hant
- {0x7A68424Eu, 25u}, // zh-BN -> Hant
- {0x7A684742u, 25u}, // zh-GB -> Hant
- {0x7A684746u, 25u}, // zh-GF -> Hant
- {0x7A68484Bu, 25u}, // zh-HK -> Hant
- {0x7A684944u, 25u}, // zh-ID -> Hant
- {0x7A684D4Fu, 25u}, // zh-MO -> Hant
- {0x7A684D59u, 25u}, // zh-MY -> Hant
- {0x7A685041u, 25u}, // zh-PA -> Hant
- {0x7A685046u, 25u}, // zh-PF -> Hant
- {0x7A685048u, 25u}, // zh-PH -> Hant
- {0x7A685352u, 25u}, // zh-SR -> Hant
- {0x7A685448u, 25u}, // zh-TH -> Hant
- {0x7A685457u, 25u}, // zh-TW -> Hant
- {0x7A685553u, 25u}, // zh-US -> Hant
- {0x7A68564Eu, 25u}, // zh-VN -> Hant
- {0x81190000u, 40u}, // zia -> Latn
- {0xB1790000u, 40u}, // zlm -> Latn
- {0xA1990000u, 40u}, // zmi -> Latn
- {0x91B90000u, 40u}, // zne -> Latn
- {0x7A750000u, 40u}, // zu -> Latn
- {0x83390000u, 40u}, // zza -> Latn
+ {0x80990000u, 44u}, // zea -> Latn
+ {0x9CD90000u, 85u}, // zgh -> Tfng
+ {0x7A680000u, 27u}, // zh -> Hans
+ {0x7A684155u, 28u}, // zh-AU -> Hant
+ {0x7A68424Eu, 28u}, // zh-BN -> Hant
+ {0x7A684742u, 28u}, // zh-GB -> Hant
+ {0x7A684746u, 28u}, // zh-GF -> Hant
+ {0x7A68484Bu, 28u}, // zh-HK -> Hant
+ {0x7A684944u, 28u}, // zh-ID -> Hant
+ {0x7A684D4Fu, 28u}, // zh-MO -> Hant
+ {0x7A684D59u, 28u}, // zh-MY -> Hant
+ {0x7A685041u, 28u}, // zh-PA -> Hant
+ {0x7A685046u, 28u}, // zh-PF -> Hant
+ {0x7A685048u, 28u}, // zh-PH -> Hant
+ {0x7A685352u, 28u}, // zh-SR -> Hant
+ {0x7A685448u, 28u}, // zh-TH -> Hant
+ {0x7A685457u, 28u}, // zh-TW -> Hant
+ {0x7A685553u, 28u}, // zh-US -> Hant
+ {0x7A68564Eu, 28u}, // zh-VN -> Hant
+ {0xDCF90000u, 59u}, // zhx -> Nshu
+ {0x81190000u, 44u}, // zia -> Latn
+ {0xB1790000u, 44u}, // zlm -> Latn
+ {0xA1990000u, 44u}, // zmi -> Latn
+ {0x91B90000u, 44u}, // zne -> Latn
+ {0x7A750000u, 44u}, // zu -> Latn
+ {0x83390000u, 44u}, // zza -> Latn
});
std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
@@ -1452,6 +1492,7 @@
0x904049444C61746ELLU, // ace_Latn_ID
0x9C4055474C61746ELLU, // ach_Latn_UG
0x806047484C61746ELLU, // ada_Latn_GH
+ 0xBC60425454696274LLU, // adp_Tibt_BT
0xE06052554379726CLLU, // ady_Cyrl_RU
0x6165495241767374LLU, // ae_Avst_IR
0x8480544E41726162LLU, // aeb_Arab_TN
@@ -1464,6 +1505,7 @@
0xCD6052554379726CLLU, // alt_Cyrl_RU
0x616D455445746869LLU, // am_Ethi_ET
0xB9804E474C61746ELLU, // amo_Latn_NG
+ 0x616E45534C61746ELLU, // an_Latn_ES
0xE5C049444C61746ELLU, // aoz_Latn_ID
0x8DE0544741726162LLU, // apd_Arab_TG
0x6172454741726162LLU, // ar_Arab_EG
@@ -1473,6 +1515,7 @@
0xB620434C4C61746ELLU, // arn_Latn_CL
0xBA20424F4C61746ELLU, // aro_Latn_BO
0xC220445A41726162LLU, // arq_Arab_DZ
+ 0xCA20534141726162LLU, // ars_Arab_SA
0xE2204D4141726162LLU, // ary_Arab_MA
0xE620454741726162LLU, // arz_Arab_EG
0x6173494E42656E67LLU, // as_Beng_IN
@@ -1510,13 +1553,13 @@
0xDCC154524772656BLLU, // bgx_Grek_TR
0x84E1494E44657661LLU, // bhb_Deva_IN
0xA0E1494E44657661LLU, // bhi_Deva_IN
- 0xA8E150484C61746ELLU, // bhk_Latn_PH
0xB8E1494E44657661LLU, // bho_Deva_IN
0x626956554C61746ELLU, // bi_Latn_VU
0xA90150484C61746ELLU, // bik_Latn_PH
0xB5014E474C61746ELLU, // bin_Latn_NG
0xA521494E44657661LLU, // bjj_Deva_IN
0xB52149444C61746ELLU, // bjn_Latn_ID
+ 0xCD21534E4C61746ELLU, // bjt_Latn_SN
0xB141434D4C61746ELLU, // bkm_Latn_CM
0xD14150484C61746ELLU, // bku_Latn_PH
0xCD61564E54617674LLU, // blt_Tavt_VN
@@ -1546,7 +1589,6 @@
0x93214D4C4C61746ELLU, // bze_Latn_ML
0x636145534C61746ELLU, // ca_Latn_ES
0x9C424E474C61746ELLU, // cch_Latn_NG
- 0xBC42494E42656E67LLU, // ccp_Beng_IN
0xBC42424443616B6DLLU, // ccp_Cakm_BD
0x636552554379726CLLU, // ce_Cyrl_RU
0x848250484C61746ELLU, // ceb_Latn_PH
@@ -1557,13 +1599,16 @@
0xB8E255534C61746ELLU, // cho_Latn_US
0xBCE243414C61746ELLU, // chp_Latn_CA
0xC4E2555343686572LLU, // chr_Cher_US
+ 0x890255534C61746ELLU, // cic_Latn_US
0x81224B4841726162LLU, // cja_Arab_KH
0xB122564E4368616DLLU, // cjm_Cham_VN
0x8542495141726162LLU, // ckb_Arab_IQ
+ 0x99824D4E536F796FLLU, // cmg_Soyo_MN
0x636F46524C61746ELLU, // co_Latn_FR
0xBDC24547436F7074LLU, // cop_Copt_EG
0xC9E250484C61746ELLU, // cps_Latn_PH
0x6372434143616E73LLU, // cr_Cans_CA
+ 0x9E2255414379726CLLU, // crh_Cyrl_UA
0xA622434143616E73LLU, // crj_Cans_CA
0xAA22434143616E73LLU, // crk_Cans_CA
0xAE22434143616E73LLU, // crl_Cans_CA
@@ -1588,6 +1633,7 @@
0x91234E454C61746ELLU, // dje_Latn_NE
0xA5A343494C61746ELLU, // dnj_Latn_CI
0xA1C3494E41726162LLU, // doi_Arab_IN
+ 0x9E23434E4D6F6E67LLU, // drh_Mong_CN
0x864344454C61746ELLU, // dsb_Latn_DE
0xB2634D4C4C61746ELLU, // dtm_Latn_ML
0xBE634D594C61746ELLU, // dtp_Latn_MY
@@ -1610,6 +1656,7 @@
0x657345534C61746ELLU, // es_Latn_ES
0x65734D584C61746ELLU, // es_Latn_MX
0x657355534C61746ELLU, // es_Latn_US
+ 0x9A44494E476F6E6DLLU, // esg_Gonm_IN
0xD24455534C61746ELLU, // esu_Latn_US
0x657445454C61746ELLU, // et_Latn_EE
0xCE6449544974616CLLU, // ett_Ital_IT
@@ -1700,10 +1747,10 @@
0x687548554C61746ELLU, // hu_Latn_HU
0x6879414D41726D6ELLU, // hy_Armn_AM
0x687A4E414C61746ELLU, // hz_Latn_NA
- 0x696146524C61746ELLU, // ia_Latn_FR
0x80284D594C61746ELLU, // iba_Latn_MY
0x84284E474C61746ELLU, // ibb_Latn_NG
0x696449444C61746ELLU, // id_Latn_ID
+ 0x90A854474C61746ELLU, // ife_Latn_TG
0x69674E474C61746ELLU, // ig_Latn_NG
0x6969434E59696969LLU, // ii_Yiii_CN
0x696B55534C61746ELLU, // ik_Latn_US
@@ -1764,6 +1811,7 @@
0x6B6D4B484B686D72LLU, // km_Khmr_KH
0x858A414F4C61746ELLU, // kmb_Latn_AO
0x6B6E494E4B6E6461LLU, // kn_Knda_IN
+ 0x95AA47574C61746ELLU, // knf_Latn_GW
0x6B6F4B524B6F7265LLU, // ko_Kore_KR
0xA1CA52554379726CLLU, // koi_Cyrl_RU
0xA9CA494E44657661LLU, // kok_Deva_IN
@@ -1778,6 +1826,7 @@
0x864A545A4C61746ELLU, // ksb_Latn_TZ
0x964A434D4C61746ELLU, // ksf_Latn_CM
0x9E4A44454C61746ELLU, // ksh_Latn_DE
+ 0xC66A4D594C61746ELLU, // ktr_Latn_MY
0x6B75495141726162LLU, // ku_Arab_IQ
0x6B7554524C61746ELLU, // ku_Latn_TR
0xB28A52554379726CLLU, // kum_Cyrl_RU
@@ -1790,6 +1839,8 @@
0x6B79434E41726162LLU, // ky_Arab_CN
0x6B794B474379726CLLU, // ky_Cyrl_KG
0x6B7954524C61746ELLU, // ky_Latn_TR
+ 0xA72A4D594C61746ELLU, // kzj_Latn_MY
+ 0xCF2A4D594C61746ELLU, // kzt_Latn_MY
0x6C6156414C61746ELLU, // la_Latn_VA
0x840B47524C696E61LLU, // lab_Lina_GR
0x8C0B494C48656272LLU, // lad_Hebr_IL
@@ -1854,6 +1905,7 @@
0x6D694E5A4C61746ELLU, // mi_Latn_NZ
0xB50C49444C61746ELLU, // min_Latn_ID
0xC90C495148617472LLU, // mis_Hatr_IQ
+ 0xC90C4E474D656466LLU, // mis_Medf_NG
0x6D6B4D4B4379726CLLU, // mk_Cyrl_MK
0x6D6C494E4D6C796DLLU, // ml_Mlym_IN
0xC96C53444C61746ELLU, // mls_Latn_SD
@@ -1861,6 +1913,7 @@
0x6D6E434E4D6F6E67LLU, // mn_Mong_CN
0xA1AC494E42656E67LLU, // mni_Beng_IN
0xD9AC4D4D4D796D72LLU, // mnw_Mymr_MM
+ 0x6D6F524F4C61746ELLU, // mo_Latn_RO
0x91CC43414C61746ELLU, // moe_Latn_CA
0x9DCC43414C61746ELLU, // moh_Latn_CA
0xC9CC42464C61746ELLU, // mos_Latn_BF
@@ -1877,6 +1930,7 @@
0xAACC4D4C4C61746ELLU, // mwk_Latn_ML
0xC6CC494E44657661LLU, // mwr_Deva_IN
0xD6CC49444C61746ELLU, // mwv_Latn_ID
+ 0xDACC5553486D6E70LLU, // mww_Hmnp_US
0x8AEC5A574C61746ELLU, // mxc_Latn_ZW
0x6D794D4D4D796D72LLU, // my_Mymr_MM
0xD70C52554379726CLLU, // myv_Cyrl_RU
@@ -1905,6 +1959,7 @@
0x998D434D4C61746ELLU, // nmg_Latn_CM
0x6E6E4E4F4C61746ELLU, // nn_Latn_NO
0x9DAD434D4C61746ELLU, // nnh_Latn_CM
+ 0xBDAD494E5763686FLLU, // nnp_Wcho_IN
0x6E6F4E4F4C61746ELLU, // no_Latn_NO
0x8DCD54484C616E61LLU, // nod_Lana_TH
0x91CD494E44657661LLU, // noe_Deva_IN
@@ -1947,6 +2002,7 @@
0xC98F49544C61746ELLU, // pms_Latn_IT
0xCDAF47524772656BLLU, // pnt_Grek_GR
0xB5CF464D4C61746ELLU, // pon_Latn_FM
+ 0x81EF494E44657661LLU, // ppa_Deva_IN
0x822F504B4B686172LLU, // pra_Khar_PK
0x8E2F495241726162LLU, // prd_Arab_IR
0x7073414641726162LLU, // ps_Arab_AF
@@ -1959,6 +2015,7 @@
0x945152454C61746ELLU, // rcf_Latn_RE
0xA49149444C61746ELLU, // rej_Latn_ID
0xB4D149544C61746ELLU, // rgn_Latn_IT
+ 0x98F14D4D41726162LLU, // rhg_Arab_MM
0x8111494E4C61746ELLU, // ria_Latn_IN
0x95114D4154666E67LLU, // rif_Tfng_MA
0xC9314E5044657661LLU, // rjs_Deva_NP
@@ -1986,6 +2043,7 @@
0xC0124B454C61746ELLU, // saq_Latn_KE
0xC81249444C61746ELLU, // sas_Latn_ID
0xCC12494E4C61746ELLU, // sat_Latn_IN
+ 0xD412534E4C61746ELLU, // sav_Latn_SN
0xE412494E53617572LLU, // saz_Saur_IN
0xBC32545A4C61746ELLU, // sbp_Latn_TZ
0x736349544C61746ELLU, // sc_Latn_IT
@@ -2025,6 +2083,7 @@
0x736E5A574C61746ELLU, // sn_Latn_ZW
0xA9B24D4C4C61746ELLU, // snk_Latn_ML
0x736F534F4C61746ELLU, // so_Latn_SO
+ 0x99D2555A536F6764LLU, // sog_Sogd_UZ
0xD1D2544854686169LLU, // sou_Thai_TH
0x7371414C4C61746ELLU, // sq_Latn_AL
0x737252534379726CLLU, // sr_Cyrl_RS
@@ -2057,6 +2116,7 @@
0x8C73434E54616C65LLU, // tdd_Tale_CN
0x98734E5044657661LLU, // tdg_Deva_NP
0x9C734E5044657661LLU, // tdh_Deva_NP
+ 0xD0734D594C61746ELLU, // tdu_Latn_MY
0x7465494E54656C75LLU, // te_Telu_IN
0xB093534C4C61746ELLU, // tem_Latn_SL
0xB89355474C61746ELLU, // teo_Latn_UG
@@ -2135,6 +2195,7 @@
0xC97657464C61746ELLU, // wls_Latn_WF
0xA1B64B4D41726162LLU, // wni_Arab_KM
0x776F534E4C61746ELLU, // wo_Latn_SN
+ 0x9A56494E476F6E67LLU, // wsg_Gong_IN
0xB276494E44657661LLU, // wtm_Deva_IN
0xD296434E48616E73LLU, // wuu_Hans_CN
0xD41742524C61746ELLU, // xav_Latn_BR
@@ -2169,6 +2230,7 @@
0x7A68545748616E62LLU, // zh_Hanb_TW
0x7A68434E48616E73LLU, // zh_Hans_CN
0x7A68545748616E74LLU, // zh_Hant_TW
+ 0xDCF9434E4E736875LLU, // zhx_Nshu_CN
0xB17954474C61746ELLU, // zlm_Latn_TG
0xA1994D594C61746ELLU, // zmi_Latn_MY
0x7A755A414C61746ELLU, // zu_Latn_ZA
@@ -2194,7 +2256,7 @@
{0x656E4154u, 0x656E80A1u}, // en-AT -> en-150
{0x656E4155u, 0x656E8400u}, // en-AU -> en-001
{0x656E4242u, 0x656E8400u}, // en-BB -> en-001
- {0x656E4245u, 0x656E8400u}, // en-BE -> en-001
+ {0x656E4245u, 0x656E80A1u}, // en-BE -> en-150
{0x656E424Du, 0x656E8400u}, // en-BM -> en-001
{0x656E4253u, 0x656E8400u}, // en-BS -> en-001
{0x656E4257u, 0x656E8400u}, // en-BW -> en-001
@@ -2285,6 +2347,7 @@
{0x65734152u, 0x6573A424u}, // es-AR -> es-419
{0x6573424Fu, 0x6573A424u}, // es-BO -> es-419
{0x65734252u, 0x6573A424u}, // es-BR -> es-419
+ {0x6573425Au, 0x6573A424u}, // es-BZ -> es-419
{0x6573434Cu, 0x6573A424u}, // es-CL -> es-419
{0x6573434Fu, 0x6573A424u}, // es-CO -> es-419
{0x65734352u, 0x6573A424u}, // es-CR -> es-419
@@ -2315,6 +2378,10 @@
{0x7074544Cu, 0x70745054u}, // pt-TL -> pt-PT
});
+const std::unordered_map<uint32_t, uint32_t> ___B_PARENTS({
+ {0x61725842u, 0x61729420u}, // ar-XB -> ar-015
+});
+
const struct {
const char script[4];
const std::unordered_map<uint32_t, uint32_t>* map;
@@ -2322,6 +2389,7 @@
{{'A', 'r', 'a', 'b'}, &ARAB_PARENTS},
{{'H', 'a', 'n', 't'}, &HANT_PARENTS},
{{'L', 'a', 't', 'n'}, &LATN_PARENTS},
+ {{'~', '~', '~', 'B'}, &___B_PARENTS},
};
const size_t MAX_PARENT_DEPTH = 3;
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 87b1467..8cffd6a 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -3,3 +3,4 @@
rtmitchell@google.com
per-file CursorWindow.cpp=omakoto@google.com
+per-file LocaleDataTables.cpp=vichang@google.com,tobiast@google.com,nikitai@google.com
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index ab4c9c2..ccb57f3 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -40,8 +40,9 @@
class OverlayStringPool : public ResStringPool {
public:
virtual ~OverlayStringPool();
- virtual const char16_t* stringAt(size_t idx, size_t* outLen) const;
- virtual const char* string8At(size_t idx, size_t* outLen) const;
+ const char16_t* stringAt(size_t idx, size_t* outLen) const override;
+ const char* string8At(size_t idx, size_t* outLen) const override;
+ size_t size() const override;
explicit OverlayStringPool(const LoadedIdmap* loaded_idmap);
private:
@@ -53,8 +54,8 @@
// resources to the resource id of corresponding target resources.
class OverlayDynamicRefTable : public DynamicRefTable {
public:
- virtual ~OverlayDynamicRefTable() = default;
- virtual status_t lookupResourceId(uint32_t* resId) const;
+ ~OverlayDynamicRefTable() override = default;
+ status_t lookupResourceId(uint32_t* resId) const override;
private:
explicit OverlayDynamicRefTable(const Idmap_data_header* data_header,
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index b20e657..35cebd4 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -39,7 +39,7 @@
namespace android {
constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
-constexpr const static uint32_t kIdmapCurrentVersion = 0x00000002u;
+constexpr const static uint32_t kIdmapCurrentVersion = 0x00000003u;
/**
* In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -520,7 +520,7 @@
ssize_t indexOfString(const char16_t* str, size_t strLen) const;
- size_t size() const;
+ virtual size_t size() const;
size_t styleCount() const;
size_t bytes() const;
const void* data() const;
@@ -1724,6 +1724,11 @@
uint8_t target_path[256];
uint8_t overlay_path[256];
+
+ uint32_t debug_info_size;
+ uint8_t debug_info[0];
+
+ size_t Size() const;
};
struct Idmap_data_header {
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index c594b8e..62e9866 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/overlay/overlay.idmap b/libs/androidfw/tests/data/overlay/overlay.idmap
index 27cf792f..3759ed6 100644
--- a/libs/androidfw/tests/data/overlay/overlay.idmap
+++ b/libs/androidfw/tests/data/overlay/overlay.idmap
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index f670cf9..d945fc4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -176,6 +176,7 @@
"hwui/AnimatedImageThread.cpp",
"hwui/Bitmap.cpp",
"hwui/Canvas.cpp",
+ "hwui/ImageDecoder.cpp",
"hwui/MinikinSkia.cpp",
"hwui/MinikinUtils.cpp",
"hwui/PaintImpl.cpp",
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 2ba6fbe..f4149b9 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -44,9 +44,7 @@
namespace android {
-// returns true if rowBytes * height can be represented by a positive int32_t value
-// and places that value in size.
-static bool computeAllocationSize(size_t rowBytes, int height, size_t* size) {
+bool Bitmap::computeAllocationSize(size_t rowBytes, int height, size_t* size) {
return 0 <= height && height <= std::numeric_limits<size_t>::max() &&
!__builtin_mul_overflow(rowBytes, (size_t)height, size) &&
*size <= std::numeric_limits<int32_t>::max();
@@ -66,7 +64,7 @@
// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
- if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) {
+ if (!Bitmap::computeAllocationSize(rowBytes, bitmap->height(), &size)) {
return nullptr;
}
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 00733c6..1cda046 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -138,6 +138,10 @@
return mPalette;
}
+ // returns true if rowBytes * height can be represented by a positive int32_t value
+ // and places that value in size.
+ static bool computeAllocationSize(size_t rowBytes, int height, size_t* size);
+
private:
static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
new file mode 100644
index 0000000..4f2027d
--- /dev/null
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -0,0 +1,213 @@
+/*
+ * 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 "ImageDecoder.h"
+
+#include <hwui/Bitmap.h>
+
+#include <SkAndroidCodec.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+
+using namespace android;
+
+ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker)
+ : mCodec(std::move(codec))
+ , mPeeker(std::move(peeker))
+ , mTargetSize(mCodec->getInfo().dimensions())
+ , mDecodeSize(mTargetSize)
+ , mOutColorType(mCodec->computeOutputColorType(kN32_SkColorType))
+ , mOutAlphaType(mCodec->getInfo().isOpaque() ?
+ kOpaque_SkAlphaType : kPremul_SkAlphaType)
+ , mOutColorSpace(mCodec->getInfo().refColorSpace())
+ , mSampleSize(1)
+{
+}
+
+bool ImageDecoder::setTargetSize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ return false;
+ }
+
+ auto info = SkImageInfo::Make(width, height, mOutColorType, mOutAlphaType);
+ size_t rowBytes = info.minRowBytes();
+ if (rowBytes == 0) {
+ // This would have overflowed.
+ return false;
+ }
+
+ size_t pixelMemorySize;
+ if (!Bitmap::computeAllocationSize(rowBytes, height, &pixelMemorySize)) {
+ return false;
+ }
+
+ if (mCropRect) {
+ if (mCropRect->right() > width || mCropRect->bottom() > height) {
+ return false;
+ }
+ }
+
+ SkISize targetSize = { width, height }, decodeSize = targetSize;
+ int sampleSize = mCodec->computeSampleSize(&decodeSize);
+
+ if (decodeSize != targetSize && mOutAlphaType == kUnpremul_SkAlphaType
+ && !mCodec->getInfo().isOpaque()) {
+ return false;
+ }
+
+ mTargetSize = targetSize;
+ mDecodeSize = decodeSize;
+ mSampleSize = sampleSize;
+ return true;
+}
+
+bool ImageDecoder::setCropRect(const SkIRect* crop) {
+ if (!crop) {
+ mCropRect.reset();
+ return true;
+ }
+
+ if (crop->left() >= crop->right() || crop->top() >= crop->bottom()) {
+ return false;
+ }
+
+ const auto& size = mTargetSize;
+ if (crop->left() < 0 || crop->top() < 0
+ || crop->right() > size.width() || crop->bottom() > size.height()) {
+ return false;
+ }
+
+ mCropRect.emplace(*crop);
+ return true;
+}
+
+bool ImageDecoder::setOutColorType(SkColorType colorType) {
+ switch (colorType) {
+ case kRGB_565_SkColorType:
+ if (!opaque()) {
+ return false;
+ }
+ break;
+ case kGray_8_SkColorType:
+ if (!gray()) {
+ return false;
+ }
+ mOutColorSpace = nullptr;
+ break;
+ case kN32_SkColorType:
+ break;
+ case kRGBA_F16_SkColorType:
+ break;
+ default:
+ return false;
+ }
+
+ mOutColorType = colorType;
+ return true;
+}
+
+bool ImageDecoder::setOutAlphaType(SkAlphaType alpha) {
+ switch (alpha) {
+ case kOpaque_SkAlphaType:
+ return opaque();
+ case kPremul_SkAlphaType:
+ if (opaque()) {
+ // Opaque can be treated as premul.
+ return true;
+ }
+ break;
+ case kUnpremul_SkAlphaType:
+ if (opaque()) {
+ // Opaque can be treated as unpremul.
+ return true;
+ }
+ if (mDecodeSize != mTargetSize) {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ mOutAlphaType = alpha;
+ return true;
+}
+
+void ImageDecoder::setOutColorSpace(sk_sp<SkColorSpace> colorSpace) {
+ mOutColorSpace = std::move(colorSpace);
+}
+
+SkImageInfo ImageDecoder::getOutputInfo() const {
+ SkISize size = mCropRect ? mCropRect->size() : mTargetSize;
+ return SkImageInfo::Make(size, mOutColorType, mOutAlphaType, mOutColorSpace);
+}
+
+bool ImageDecoder::opaque() const {
+ return mOutAlphaType == kOpaque_SkAlphaType;
+}
+
+bool ImageDecoder::gray() const {
+ return mCodec->getInfo().colorType() == kGray_8_SkColorType;
+}
+
+SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
+ void* decodePixels = pixels;
+ size_t decodeRowBytes = rowBytes;
+ auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, mOutAlphaType, mOutColorSpace);
+ // Used if we need a temporary before scaling or subsetting.
+ // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
+ SkBitmap tmp;
+ const bool scale = mDecodeSize != mTargetSize;
+ if (scale || mCropRect) {
+ if (!tmp.setInfo(decodeInfo)) {
+ return SkCodec::kInternalError;
+ }
+ if (!Bitmap::allocateHeapBitmap(&tmp)) {
+ return SkCodec::kInternalError;
+ }
+ decodePixels = tmp.getPixels();
+ decodeRowBytes = tmp.rowBytes();
+ }
+
+ SkAndroidCodec::AndroidOptions options;
+ options.fSampleSize = mSampleSize;
+ auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &options);
+
+ if (scale || mCropRect) {
+ SkBitmap scaledBm;
+ if (!scaledBm.installPixels(getOutputInfo(), pixels, rowBytes)) {
+ return SkCodec::kInternalError;
+ }
+
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc);
+ paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
+
+ SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
+ if (mCropRect) {
+ canvas.translate(-mCropRect->fLeft, -mCropRect->fTop);
+ }
+ if (scale) {
+ float scaleX = (float) mTargetSize.width() / mDecodeSize.width();
+ float scaleY = (float) mTargetSize.height() / mDecodeSize.height();
+ canvas.scale(scaleX, scaleY);
+ }
+
+ canvas.drawBitmap(tmp, 0.0f, 0.0f, &paint);
+ }
+
+ return result;
+}
+
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
new file mode 100644
index 0000000..b956f4a
--- /dev/null
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -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.
+ */
+#pragma once
+
+#include <SkCodec.h>
+#include <SkImageInfo.h>
+#include <SkPngChunkReader.h>
+#include <SkRect.h>
+#include <SkSize.h>
+#include <cutils/compiler.h>
+
+#include <optional>
+
+class SkAndroidCodec;
+
+namespace android {
+
+class ANDROID_API ImageDecoder {
+public:
+ std::unique_ptr<SkAndroidCodec> mCodec;
+ sk_sp<SkPngChunkReader> mPeeker;
+
+ ImageDecoder(std::unique_ptr<SkAndroidCodec> codec,
+ sk_sp<SkPngChunkReader> peeker = nullptr);
+
+ bool setTargetSize(int width, int height);
+ bool setCropRect(const SkIRect*);
+
+ bool setOutColorType(SkColorType outColorType);
+
+ bool setOutAlphaType(SkAlphaType outAlphaType);
+
+ void setOutColorSpace(sk_sp<SkColorSpace> cs);
+
+ // The size is the final size after scaling and cropping.
+ SkImageInfo getOutputInfo() const;
+ SkColorType getOutColorType() const { return mOutColorType; }
+ SkAlphaType getOutAlphaType() const { return mOutAlphaType; }
+
+ bool opaque() const;
+ bool gray() const;
+
+ SkCodec::Result decode(void* pixels, size_t rowBytes);
+
+private:
+ SkISize mTargetSize;
+ SkISize mDecodeSize;
+ SkColorType mOutColorType;
+ SkAlphaType mOutAlphaType;
+ sk_sp<SkColorSpace> mOutColorSpace;
+ int mSampleSize;
+ std::optional<SkIRect> mCropRect;
+
+ ImageDecoder(const ImageDecoder&) = delete;
+ ImageDecoder& operator=(const ImageDecoder&) = delete;
+};
+
+} // namespace android
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 9c84634..00ceb2d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -41,7 +41,7 @@
void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas,
const SkiaDisplayList& displayList,
- int nestLevel) {
+ int nestLevel) const {
LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
for (auto& child : displayList.mChildNodes) {
const RenderProperties& childProperties = child.getNodeProperties();
@@ -132,7 +132,7 @@
RenderNode& mNode;
};
-void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
+void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
RenderNode* renderNode = mRenderNode.get();
MarkDraw _marker{*canvas, *renderNode};
@@ -230,7 +230,14 @@
// we need to restrict the portion of the surface drawn to the size of the renderNode.
SkASSERT(renderNode->getLayerSurface()->width() >= bounds.width());
SkASSERT(renderNode->getLayerSurface()->height() >= bounds.height());
- canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot().get(), bounds,
+
+ // If SKP recording is active save an annotation that indicates this drawImageRect
+ // could also be rendered with the commands saved at ID associated with this node.
+ if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+ canvas->drawAnnotation(bounds, String8::format(
+ "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr);
+ }
+ canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds,
bounds, &paint);
if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) {
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index 6ba8e59..6c390c3 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -58,7 +58,7 @@
* projection receiver then all projected children (excluding direct children) will be drawn
* last. Any projected node not matching those requirements will not be drawn by this function.
*/
- void forceDraw(SkCanvas* canvas);
+ void forceDraw(SkCanvas* canvas) const;
/**
* Returns readonly render properties for this render node.
@@ -113,7 +113,7 @@
* @param nestLevel should be always 0. Used to track how far we are from the receiver.
*/
void drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
- int nestLevel = 0);
+ int nestLevel = 0) const;
/**
* Applies the rendering properties of a view onto a SkCanvas.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 0647977..11dc013 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -26,11 +26,11 @@
#include <SkPictureRecorder.h>
#include <SkSerialProcs.h>
#include "LightingInfo.h"
-#include "TreeInfo.h"
#include "VectorDrawable.h"
#include "thread/CommonPool.h"
#include "tools/SkSharingProc.h"
#include "utils/TraceUtils.h"
+#include "utils/String8.h"
#include <unistd.h>
@@ -90,58 +90,61 @@
// only schedule repaint if node still on layer - possible it may have been
// removed during a dropped frame, but layers may still remain scheduled so
// as not to lose info on what portion is damaged
- if (CC_LIKELY(layerNode->getLayerSurface() != nullptr)) {
- SkASSERT(layerNode->getLayerSurface());
- SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList();
- if (!displayList || displayList->isEmpty()) {
- ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
- return;
+ if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+ continue;
+ }
+ SkASSERT(layerNode->getLayerSurface());
+ SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList();
+ if (!displayList || displayList->isEmpty()) {
+ ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
+ return;
+ }
+
+ const Rect& layerDamage = layers.entries()[i].damage;
+
+ SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
+
+ int saveCount = layerCanvas->save();
+ SkASSERT(saveCount == 1);
+
+ layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
+
+ // TODO: put localized light center calculation and storage to a drawable related code.
+ // It does not seem right to store something localized in a global state
+ // fix here and in recordLayers
+ const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+ Vector3 transformedLightCenter(savedLightCenter);
+ // map current light center into RenderNode's coordinate space
+ layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+ LightingInfo::setLightCenterRaw(transformedLightCenter);
+
+ const RenderProperties& properties = layerNode->properties();
+ const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+ if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
+ return;
+ }
+
+ ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
+ bounds.height());
+
+ layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
+ layerCanvas->clear(SK_ColorTRANSPARENT);
+
+ RenderNodeDrawable root(layerNode, layerCanvas, false);
+ root.forceDraw(layerCanvas);
+ layerCanvas->restoreToCount(saveCount);
+
+ LightingInfo::setLightCenterRaw(savedLightCenter);
+
+ // cache the current context so that we can defer flushing it until
+ // either all the layers have been rendered or the context changes
+ GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
+ if (cachedContext.get() != currentContext) {
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers (context changed)");
+ cachedContext->flush();
}
-
- const Rect& layerDamage = layers.entries()[i].damage;
-
- SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
-
- int saveCount = layerCanvas->save();
- SkASSERT(saveCount == 1);
-
- layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
-
- // TODO: put localized light center calculation and storage to a drawable related code.
- // It does not seem right to store something localized in a global state
- const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
- Vector3 transformedLightCenter(savedLightCenter);
- // map current light center into RenderNode's coordinate space
- layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
- LightingInfo::setLightCenterRaw(transformedLightCenter);
-
- const RenderProperties& properties = layerNode->properties();
- const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
- if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
- return;
- }
-
- ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
- bounds.height());
-
- layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
- layerCanvas->clear(SK_ColorTRANSPARENT);
-
- RenderNodeDrawable root(layerNode, layerCanvas, false);
- root.forceDraw(layerCanvas);
- layerCanvas->restoreToCount(saveCount);
- LightingInfo::setLightCenterRaw(savedLightCenter);
-
- // cache the current context so that we can defer flushing it until
- // either all the layers have been rendered or the context changes
- GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
- if (cachedContext.get() != currentContext) {
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers (context changed)");
- cachedContext->flush();
- }
- cachedContext.reset(SkSafeRef(currentContext));
- }
+ cachedContext.reset(SkSafeRef(currentContext));
}
}
@@ -275,12 +278,60 @@
}
}
-SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
+// recurse through the rendernode's children, add any nodes which are layers to the queue.
+static void collectLayers(RenderNode* node, LayerUpdateQueue* layers) {
+ SkiaDisplayList* dl = (SkiaDisplayList*)node->getDisplayList();
+ if (dl) {
+ const auto& prop = node->properties();
+ if (node->hasLayer()) {
+ layers->enqueueLayerWithDamage(node, Rect(prop.getWidth(), prop.getHeight()));
+ }
+ // The way to recurse through rendernodes is to call this with a lambda.
+ dl->updateChildren([&](RenderNode* child) { collectLayers(child, layers); });
+ }
+}
+
+// record the provided layers to the provided canvas as self-contained skpictures.
+static void recordLayers(const LayerUpdateQueue& layers,
+ SkCanvas* mskpCanvas) {
+ const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+ // Record the commands to re-draw each dirty layer into an SkPicture
+ for (size_t i = 0; i < layers.entries().size(); i++) {
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
+ const Rect& layerDamage = layers.entries()[i].damage;
+ const RenderProperties& properties = layerNode->properties();
+
+ // Temporarily map current light center into RenderNode's coordinate space
+ Vector3 transformedLightCenter(savedLightCenter);
+ layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+ LightingInfo::setLightCenterRaw(transformedLightCenter);
+
+ SkPictureRecorder layerRec;
+ auto* recCanvas = layerRec.beginRecording(properties.getWidth(),
+ properties.getHeight());
+ // This is not recorded but still causes clipping.
+ recCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
+ RenderNodeDrawable root(layerNode, recCanvas, false);
+ root.forceDraw(recCanvas);
+ // Now write this picture into the SKP canvas with an annotation indicating what it is
+ mskpCanvas->drawAnnotation(layerDamage.toSkRect(), String8::format(
+ "OffscreenLayerDraw|%" PRId64, layerNode->uniqueId()).c_str(), nullptr);
+ mskpCanvas->drawPicture(layerRec.finishRecordingAsPicture());
+ }
+ LightingInfo::setLightCenterRaw(savedLightCenter);
+}
+
+SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface, RenderNode* root,
+ const LayerUpdateQueue& dirtyLayers) {
if (CC_LIKELY(!Properties::skpCaptureEnabled)) {
return surface->getCanvas(); // Bail out early when capture is not turned on.
}
// Note that shouldStartNewFileCapture tells us if this is the *first* frame of a capture.
+ bool firstFrameOfAnim = false;
if (shouldStartNewFileCapture() && mCaptureMode == CaptureMode::MultiFrameSKP) {
+ // set a reminder to record every layer near the end of this method, after we have set up
+ // the nway canvas.
+ firstFrameOfAnim = true;
if (!setupMultiFrameCapture()) {
return surface->getCanvas();
}
@@ -309,6 +360,20 @@
mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
mNwayCanvas->addCanvas(surface->getCanvas());
mNwayCanvas->addCanvas(pictureCanvas);
+
+ if (firstFrameOfAnim) {
+ // On the first frame of any mskp capture we want to record any layers that are needed in
+ // frame but may have been rendered offscreen before recording began.
+ // We do not maintain a list of all layers, since it isn't needed outside this rare,
+ // recording use case. Traverse the tree to find them and put them in this LayerUpdateQueue.
+ LayerUpdateQueue luq;
+ collectLayers(root, &luq);
+ recordLayers(luq, mNwayCanvas.get());
+ } else {
+ // on non-first frames, we record any normal layer draws (dirty regions)
+ recordLayers(dirtyLayers, mNwayCanvas.get());
+ }
+
return mNwayCanvas.get();
}
@@ -359,13 +424,13 @@
Properties::skpCaptureEnabled = true;
}
+ // Initialize the canvas for the current frame, that might be a recording canvas if SKP
+ // capture is enabled.
+ SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);
+
// draw all layers up front
renderLayersImpl(layers, opaque);
- // initialize the canvas for the current frame, that might be a recording canvas if SKP
- // capture is enabled.
- SkCanvas* canvas = tryCapture(surface.get());
-
renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
endCapture(surface.get());
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 7d575ad..af8414d 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -45,6 +45,8 @@
void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
bool opaque, const LightInfo& lightInfo) override;
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
ErrorHandler* errorHandler) override;
@@ -92,7 +94,7 @@
// Called every frame. Normally returns early with screen canvas.
// But when capture is enabled, returns an nwaycanvas where commands are also recorded.
- SkCanvas* tryCapture(SkSurface* surface);
+ SkCanvas* tryCapture(SkSurface* surface, RenderNode* root, const LayerUpdateQueue& dirtyLayers);
// Called at the end of every frame, closes the recording if necessary.
void endCapture(SkSurface* surface);
// Determine if a new file-based capture should be started.
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
index b0fad57d..901ffaa 100644
--- a/libs/services/Android.bp
+++ b/libs/services/Android.bp
@@ -22,17 +22,16 @@
"src/os/DropBoxManager.cpp",
"src/os/StatsDimensionsValue.cpp",
"src/os/StatsLogEventWrapper.cpp",
- "src/util/StatsEvent.cpp",
],
shared_libs: [
"libbinder",
- "liblog",
"libcutils",
+ "liblog",
"libutils",
],
header_libs: [
- "libbase_headers",
+ "libbase_headers",
],
aidl: {
include_dirs: ["frameworks/base/core/java/"],
diff --git a/libs/services/include/android/util/StatsEvent.h b/libs/services/include/android/util/StatsEvent.h
deleted file mode 100644
index 4863117..0000000
--- a/libs/services/include/android/util/StatsEvent.h
+++ /dev/null
@@ -1,43 +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.
- */
-#ifndef STATS_EVENT_H
-#define STATS_EVENT_H
-
-#include <binder/Parcel.h>
-#include <binder/Parcelable.h>
-#include <binder/Status.h>
-#include <vector>
-
-namespace android {
-namespace util {
-class StatsEvent : public android::Parcelable {
- public:
- StatsEvent();
-
- StatsEvent(StatsEvent&& in) = default;
-
- android::status_t writeToParcel(android::Parcel* out) const;
-
- android::status_t readFromParcel(const android::Parcel* in);
-
- private:
- int mAtomTag;
- std::vector<uint8_t> mBuffer;
-};
-} // Namespace util
-} // Namespace android
-
-#endif // STATS_ EVENT_H
\ No newline at end of file
diff --git a/libs/services/src/util/StatsEvent.cpp b/libs/services/src/util/StatsEvent.cpp
deleted file mode 100644
index 8b85791..0000000
--- a/libs/services/src/util/StatsEvent.cpp
+++ /dev/null
@@ -1,58 +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.
- */
-#include <android/util/StatsEvent.h>
-
-#include <binder/Parcel.h>
-#include <binder/Parcelable.h>
-#include <binder/Status.h>
-#include <vector>
-
-using android::Parcel;
-using android::Parcelable;
-using android::status_t;
-using std::vector;
-
-namespace android {
-namespace util {
-
-StatsEvent::StatsEvent(){};
-
-status_t StatsEvent::writeToParcel(Parcel* out) const {
- // Implement me if desired. We don't currently use this.
- ALOGE("Cannot do c++ StatsEvent.writeToParcel(); it is not implemented.");
- (void)out; // To prevent compile error of unused parameter 'out'
- return UNKNOWN_ERROR;
-};
-
-status_t StatsEvent::readFromParcel(const Parcel* in) {
- status_t res = OK;
- if (in == NULL) {
- ALOGE("statsd received parcel argument was NULL.");
- return BAD_VALUE;
- }
- if ((res = in->readInt32(&mAtomTag)) != OK) {
- ALOGE("statsd could not read atom tag from parcel");
- return res;
- }
- if ((res = in->readByteVector(&mBuffer)) != OK) {
- ALOGE("statsd could not read buffer from parcel");
- return res;
- }
- return NO_ERROR;
-};
-
-} // Namespace util
-} // Namespace android
diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java
index f3c2a16..8c40338 100644
--- a/location/java/android/location/Country.java
+++ b/location/java/android/location/Country.java
@@ -16,7 +16,7 @@
package android.location;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
diff --git a/location/java/android/location/CountryDetector.java b/location/java/android/location/CountryDetector.java
index ae13949..e344b82 100644
--- a/location/java/android/location/CountryDetector.java
+++ b/location/java/android/location/CountryDetector.java
@@ -16,10 +16,8 @@
package android.location;
-import java.util.HashMap;
-
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;
@@ -27,6 +25,8 @@
import android.os.RemoteException;
import android.util.Log;
+import java.util.HashMap;
+
/**
* This class provides access to the system country detector service. This
* service allows applications to obtain the country that the user is in.
diff --git a/location/java/android/location/CountryListener.java b/location/java/android/location/CountryListener.java
index 70a83c5..eb67205 100644
--- a/location/java/android/location/CountryListener.java
+++ b/location/java/android/location/CountryListener.java
@@ -16,7 +16,7 @@
package android.location;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* The listener for receiving the notification when the country is detected or
diff --git a/location/java/android/location/GeocoderParams.java b/location/java/android/location/GeocoderParams.java
index 45d92ee..1c6e9b6 100644
--- a/location/java/android/location/GeocoderParams.java
+++ b/location/java/android/location/GeocoderParams.java
@@ -16,7 +16,7 @@
package android.location;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/location/java/android/location/Geofence.java b/location/java/android/location/Geofence.java
index 9570b26..af57bfd 100644
--- a/location/java/android/location/Geofence.java
+++ b/location/java/android/location/Geofence.java
@@ -16,7 +16,7 @@
package android.location;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index db48ee7..7a12cee 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -19,7 +19,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.Bundle;
import android.os.Parcel;
diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java
index aa9dddc..8df0834 100644
--- a/location/java/android/location/LocationListener.java
+++ b/location/java/android/location/LocationListener.java
@@ -16,6 +16,7 @@
package android.location;
+import android.annotation.NonNull;
import android.os.Bundle;
/**
@@ -37,36 +38,32 @@
/**
* Called when the location has changed.
*
- * <p> There are no restrictions on the use of the supplied Location object.
- *
- * @param location The new location, as a Location object.
+ * @param location the updated location
*/
- void onLocationChanged(Location location);
+ void onLocationChanged(@NonNull Location location);
/**
- * This callback will never be invoked and providers can be considers as always in the
- * {@link LocationProvider#AVAILABLE} state.
+ * This callback will never be invoked on Android Q and above, and providers can be considered
+ * as always in the {@link LocationProvider#AVAILABLE} state.
*
- * @deprecated This callback will never be invoked.
+ * @deprecated This callback will never be invoked on Android Q and above.
*/
@Deprecated
- void onStatusChanged(String provider, int status, Bundle extras);
+ default void onStatusChanged(String provider, int status, Bundle extras) {}
/**
* Called when the provider is enabled by the user.
*
- * @param provider the name of the location provider associated with this
- * update.
+ * @param provider the name of the location provider that has become enabled
*/
- void onProviderEnabled(String provider);
+ default void onProviderEnabled(@NonNull String provider) {}
/**
* Called when the provider is disabled by the user. If requestLocationUpdates
* is called on an already disabled provider, this method is called
* immediately.
*
- * @param provider the name of the location provider associated with this
- * update.
+ * @param provider the name of the location provider that has become disabled
*/
- void onProviderDisabled(String provider);
+ default void onProviderDisabled(@NonNull String provider) {}
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 1f8c1d53..687535c3 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -31,9 +31,9 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -125,6 +125,7 @@
*
* @hide
*/
+ @TestApi
public static final String FUSED_PROVIDER = "fused";
/**
@@ -1837,12 +1838,9 @@
@Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
public @Nullable GpsStatus getGpsStatus(@Nullable GpsStatus status) {
- UnsupportedOperationException ex = new UnsupportedOperationException(
- "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead");
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.R) {
- throw ex;
- } else {
- Log.w(TAG, ex);
+ throw new UnsupportedOperationException(
+ "GpsStatus APIs not supported in S and above, use GnssStatus APIs instead");
}
GnssStatus gnssStatus = mGnssStatusListenerManager.getGnssStatus();
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index ca8f2ac..f3e4d81 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -22,7 +22,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.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index f9b2fe0..9846436 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -19,6 +19,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -37,8 +38,6 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.telephony.GsmAlphabet;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;
@@ -126,7 +125,7 @@
public static class GpsNiNotification
{
- @android.annotation.UnsupportedAppUsage
+ @android.compat.annotation.UnsupportedAppUsage
public GpsNiNotification() {
}
public int notificationId;
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java
index 155f788..c23f499 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/com/android/internal/location/ProviderRequest.java
@@ -16,7 +16,7 @@
package com.android.internal.location;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.location.LocationRequest;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/media/Android.bp b/media/Android.bp
index 1912930..a1365179 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -26,7 +26,8 @@
installable: true,
- // Make sure that the implementaion only relies on SDK or system APIs.
+ // TODO: build against stable API surface. Use core_platform for now to avoid
+ // link-check failure with exoplayer building against "current".
sdk_version: "core_platform",
libs: [
// The order matters. android_system_* library should come later.
@@ -77,20 +78,13 @@
path: "apex/java"
}
-metalava_updatable_media_args = " --error UnhiddenSystemApi " +
- "--hide RequiresPermission " +
- "--hide MissingPermission --hide BroadcastBehavior " +
- "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
- "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
- "--hide HiddenTypedefConstant --show-annotation android.annotation.SystemApi "
-
droidstubs {
name: "updatable-media-stubs",
srcs: [
":updatable-media-srcs",
":framework-media-annotation-srcs",
],
- args: metalava_updatable_media_args,
+ defaults: [ "framework-module-stubs-defaults-systemapi" ],
aidl: {
// TODO(b/135922046) remove this
include_dirs: ["frameworks/base/core/java"],
@@ -108,4 +102,69 @@
name: "framework_media_annotation",
srcs: [":framework-media-annotation-srcs"],
installable: false,
+ sdk_version: "core_current",
+}
+
+aidl_interface {
+ name: "audio_common-aidl",
+ local_include_dir: "java",
+ srcs: [
+ "java/android/media/audio/common/AudioChannelMask.aidl",
+ "java/android/media/audio/common/AudioConfig.aidl",
+ "java/android/media/audio/common/AudioFormat.aidl",
+ "java/android/media/audio/common/AudioOffloadInfo.aidl",
+ "java/android/media/audio/common/AudioStreamType.aidl",
+ "java/android/media/audio/common/AudioUsage.aidl",
+ ],
+ backend:
+ {
+ cpp: {
+ enabled: true,
+ },
+ java: {
+ // Already generated as part of the entire media java library.
+ enabled: false,
+ },
+ },
+}
+
+aidl_interface {
+ name: "soundtrigger_middleware-aidl",
+ local_include_dir: "java",
+ srcs: [
+ "java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl",
+ "java/android/media/soundtrigger_middleware/ModelParameter.aidl",
+ "java/android/media/soundtrigger_middleware/ModelParameterRange.aidl",
+ "java/android/media/soundtrigger_middleware/Phrase.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionConfig.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionEvent.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionMode.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionStatus.aidl",
+ "java/android/media/soundtrigger_middleware/SoundModel.aidl",
+ "java/android/media/soundtrigger_middleware/SoundModelType.aidl",
+ "java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
+ "java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl",
+ "java/android/media/soundtrigger_middleware/Status.aidl",
+ ],
+ backend:
+ {
+ cpp: {
+ enabled: true,
+ },
+ java: {
+ // Already generated as part of the entire media java library.
+ enabled: false,
+ },
+ ndk: {
+ // Not currently needed, and disabled because of b/146172425
+ enabled: false,
+ },
+ },
+ imports: [ "audio_common-aidl" ],
}
diff --git a/media/apex/java/android/media/MediaParser.java b/media/apex/java/android/media/MediaParser.java
index 8824269..ac66d1b 100644
--- a/media/apex/java/android/media/MediaParser.java
+++ b/media/apex/java/android/media/MediaParser.java
@@ -102,7 +102,8 @@
* public void onSeekMap(int i, @NonNull MediaFormat mediaFormat) { \/* Do nothing. *\/ }
*
* \@Override
- * public void onFormat(int i, @NonNull MediaFormat mediaFormat) {
+ * public void onTrackData(int i, @NonNull TrackData trackData) {
+ * MediaFormat mediaFormat = trackData.mediaFormat;
* if (videoTrackIndex == -1 && mediaFormat
* .getString(MediaFormat.KEY_MIME, \/* defaultValue= *\/ "").startsWith("video/")) {
* videoTrackIndex = i;
@@ -156,19 +157,29 @@
* <p>A {@link SeekPoint} is a position in the stream from which a player may successfully start
* playing media samples.
*/
- public interface SeekMap {
+ public static final class SeekMap {
/** Returned by {@link #getDurationUs()} when the duration is unknown. */
- int UNKNOWN_DURATION = Integer.MIN_VALUE;
+ public static final int UNKNOWN_DURATION = Integer.MIN_VALUE;
+
+ private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap;
+
+ private SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
+ mExoPlayerSeekMap = exoplayerSeekMap;
+ }
/** Returns whether seeking is supported. */
- boolean isSeekable();
+ public boolean isSeekable() {
+ return mExoPlayerSeekMap.isSeekable();
+ }
/**
* Returns the duration of the stream in microseconds or {@link #UNKNOWN_DURATION} if the
* duration is unknown.
*/
- long getDurationUs();
+ public long getDurationUs() {
+ return mExoPlayerSeekMap.getDurationUs();
+ }
/**
* Obtains {@link SeekPoint SeekPoints} for the specified seek time in microseconds.
@@ -184,7 +195,28 @@
* @return The corresponding {@link SeekPoint SeekPoints}.
*/
@NonNull
- Pair<SeekPoint, SeekPoint> getSeekPoints(long timeUs);
+ public Pair<SeekPoint, SeekPoint> getSeekPoints(long timeUs) {
+ SeekPoints seekPoints = mExoPlayerSeekMap.getSeekPoints(timeUs);
+ return new Pair<>(toSeekPoint(seekPoints.first), toSeekPoint(seekPoints.second));
+ }
+ }
+
+ /** Holds information associated with a track. */
+ public static final class TrackData {
+
+ /** Holds {@link MediaFormat} information for the track. */
+ @NonNull public final MediaFormat mediaFormat;
+
+ /**
+ * Holds {@link DrmInitData} necessary to acquire keys associated with the track, or null if
+ * the track has no encryption data.
+ */
+ @Nullable public final DrmInitData drmInitData;
+
+ private TrackData(MediaFormat mediaFormat, DrmInitData drmInitData) {
+ this.mediaFormat = mediaFormat;
+ this.drmInitData = drmInitData;
+ }
}
/** Defines a seek point in a media stream. */
@@ -295,12 +327,12 @@
void onTracksFound(int numberOfTracks);
/**
- * Called when the {@link MediaFormat} of the track is extracted from the stream.
+ * Called when new {@link TrackData} is extracted from the stream.
*
- * @param trackIndex The index of the track for which the {@link MediaFormat} was found.
- * @param format The extracted {@link MediaFormat}.
+ * @param trackIndex The index of the track for which the {@link TrackData} was extracted.
+ * @param trackData The extracted {@link TrackData}.
*/
- void onFormat(int trackIndex, @NonNull MediaFormat format);
+ void onTrackData(int trackIndex, @NonNull TrackData trackData);
/**
* Called to write sample data to the output.
@@ -647,7 +679,7 @@
@Override
public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
- mOutputConsumer.onSeekMap(new ExoToMediaParserSeekMapAdapter(exoplayerSeekMap));
+ mOutputConsumer.onSeekMap(new SeekMap(exoplayerSeekMap));
}
}
@@ -661,7 +693,10 @@
@Override
public void format(Format format) {
- mOutputConsumer.onFormat(mTrackIndex, toMediaFormat(format));
+ mOutputConsumer.onTrackData(
+ mTrackIndex,
+ new TrackData(
+ toMediaFormat(format), toFrameworkDrmInitData(format.drmInitData)));
}
@Override
@@ -764,32 +799,6 @@
Extractor createInstance();
}
- private static class ExoToMediaParserSeekMapAdapter implements SeekMap {
-
- private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap;
-
- private ExoToMediaParserSeekMapAdapter(
- com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) {
- mExoPlayerSeekMap = exoplayerSeekMap;
- }
-
- @Override
- public boolean isSeekable() {
- return mExoPlayerSeekMap.isSeekable();
- }
-
- @Override
- public long getDurationUs() {
- return mExoPlayerSeekMap.getDurationUs();
- }
-
- @Override
- public Pair<SeekPoint, SeekPoint> getSeekPoints(long timeUs) {
- SeekPoints seekPoints = mExoPlayerSeekMap.getSeekPoints(timeUs);
- return new Pair<>(toSeekPoint(seekPoints.first), toSeekPoint(seekPoints.second));
- }
- }
-
// Private static methods.
private static MediaFormat toMediaFormat(Format format) {
@@ -859,6 +868,12 @@
return 0;
}
+ private static DrmInitData toFrameworkDrmInitData(
+ com.google.android.exoplayer2.drm.DrmInitData drmInitData) {
+ // TODO: Implement.
+ return null;
+ }
+
private static MediaCodec.CryptoInfo toCryptoInfo(TrackOutput.CryptoData encryptionData) {
// TODO: Implement.
return null;
diff --git a/media/java/android/media/AmrInputStream.java b/media/java/android/media/AmrInputStream.java
index 5088798..3cb224d 100644
--- a/media/java/android/media/AmrInputStream.java
+++ b/media/java/android/media/AmrInputStream.java
@@ -16,14 +16,14 @@
package android.media;
-import java.io.InputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.media.MediaCodec.BufferInfo;
import android.util.Log;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
/**
* DO NOT USE
diff --git a/media/java/android/media/AsyncPlayer.java b/media/java/android/media/AsyncPlayer.java
index 8ac2655..c3dc118 100644
--- a/media/java/android/media/AsyncPlayer.java
+++ b/media/java/android/media/AsyncPlayer.java
@@ -17,9 +17,8 @@
package android.media;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.media.PlayerBase;
import android.net.Uri;
import android.os.PowerManager;
import android.os.SystemClock;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index c03e8e2..ece5335 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -18,8 +18,9 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.media.audiopolicy.AudioProductStrategy;
import android.os.Build;
import android.os.Bundle;
@@ -178,6 +179,13 @@
* utterances.
*/
public final static int USAGE_ASSISTANT = 16;
+ /**
+ * @hide
+ * Usage value to use for assistant voice interaction with remote caller on Cell and VoIP calls.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public static final int USAGE_CALL_ASSISTANT = 17;
/**
* IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
@@ -254,6 +262,7 @@
SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, SUPPRESSIBLE_MEDIA);
SUPPRESSIBLE_USAGES.put(USAGE_GAME, SUPPRESSIBLE_MEDIA);
SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT, SUPPRESSIBLE_MEDIA);
+ SUPPRESSIBLE_USAGES.put(USAGE_CALL_ASSISTANT, SUPPRESSIBLE_NEVER);
/** default volume assignment is STREAM_MUSIC, handle unknown usage as media */
SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN, SUPPRESSIBLE_MEDIA);
SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_SONIFICATION, SUPPRESSIBLE_SYSTEM);
@@ -682,6 +691,7 @@
case USAGE_GAME:
case USAGE_VIRTUAL_SOURCE:
case USAGE_ASSISTANT:
+ case USAGE_CALL_ASSISTANT:
mUsage = usage;
break;
default:
@@ -1135,6 +1145,8 @@
return new String("USAGE_GAME");
case USAGE_ASSISTANT:
return new String("USAGE_ASSISTANT");
+ case USAGE_CALL_ASSISTANT:
+ return new String("USAGE_CALL_ASSISTANT");
default:
return new String("unknown usage " + usage);
}
@@ -1238,6 +1250,7 @@
case USAGE_ASSISTANCE_SONIFICATION:
return AudioSystem.STREAM_SYSTEM;
case USAGE_VOICE_COMMUNICATION:
+ case USAGE_CALL_ASSISTANT:
return AudioSystem.STREAM_VOICE_CALL;
case USAGE_VOICE_COMMUNICATION_SIGNALLING:
return fromGetVolumeControlStream ?
@@ -1302,6 +1315,7 @@
USAGE_ASSISTANCE_SONIFICATION,
USAGE_GAME,
USAGE_ASSISTANT,
+ USAGE_CALL_ASSISTANT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {}
diff --git a/media/java/android/media/AudioDeviceAddress.java b/media/java/android/media/AudioDeviceAddress.java
index 415e77d..3d8fc37 100644
--- a/media/java/android/media/AudioDeviceAddress.java
+++ b/media/java/android/media/AudioDeviceAddress.java
@@ -72,10 +72,12 @@
private final @Role int mRole;
/**
+ * @hide
* Constructor from a valid {@link AudioDeviceInfo}
* @param deviceInfo the connected audio device from which to obtain the device-identifying
* type and address.
*/
+ @SystemApi
public AudioDeviceAddress(@NonNull AudioDeviceInfo deviceInfo) {
Objects.requireNonNull(deviceInfo);
mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT;
@@ -83,6 +85,14 @@
mAddress = deviceInfo.getAddress();
}
+ /**
+ * @hide
+ * Constructor from role, device type and address
+ * @param role indicates input or output role
+ * @param type the device type, as defined in {@link AudioDeviceInfo}
+ * @param address the address of the device, or an empty string for devices without one
+ */
+ @SystemApi
public AudioDeviceAddress(@Role int role, @AudioDeviceInfo.AudioDeviceType int type,
@NonNull String address) {
Objects.requireNonNull(address);
@@ -101,14 +111,38 @@
mAddress = address;
}
+ /*package*/ AudioDeviceAddress(int nativeType, @NonNull String address) {
+ mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT;
+ mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType);
+ mAddress = address;
+ }
+
+ /**
+ * @hide
+ * Returns the role of a device
+ * @return the role
+ */
+ @SystemApi
public @Role int getRole() {
return mRole;
}
+ /**
+ * @hide
+ * Returns the audio device type of a device
+ * @return the type, as defined in {@link AudioDeviceInfo}
+ */
+ @SystemApi
public @AudioDeviceInfo.AudioDeviceType int getType() {
return mType;
}
+ /**
+ * @hide
+ * Returns the address of the audio device, or an empty string for devices without one
+ * @return the device address
+ */
+ @SystemApi
public @NonNull String getAddress() {
return mAddress;
}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index a39bc51..8293b5f 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -155,7 +155,9 @@
TYPE_TV_TUNER }
)
@Retention(RetentionPolicy.SOURCE)
- public @interface AudioDeviceType {} /** @hide */
+ public @interface AudioDeviceType {}
+
+ /** @hide */
@IntDef(flag = false, prefix = "TYPE", value = {
TYPE_BUILTIN_MIC,
TYPE_BLUETOOTH_SCO,
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 62b18cb..51909db 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -16,8 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
-import android.media.AudioSystem;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* The AudioDevicePort is a specialized type of AudioPort
diff --git a/media/java/android/media/AudioDevicePortConfig.java b/media/java/android/media/AudioDevicePortConfig.java
index 0c647ea..51b8037 100644
--- a/media/java/android/media/AudioDevicePortConfig.java
+++ b/media/java/android/media/AudioDevicePortConfig.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* An AudioDevicePortConfig describes a possible configuration of an output or input device
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index f0787e9..7ff15df 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -20,7 +20,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -281,6 +281,8 @@
* metadata (object audio) over HDMI (e.g. Dolby Atmos content).
**/
public static final int ENCODING_DOLBY_MAT = 19;
+ /** Audio data format: OPUS compressed. */
+ public static final int ENCODING_OPUS = 20;
/** @hide */
public static String toLogFriendlyEncoding(int enc) {
@@ -323,6 +325,8 @@
return "ENCODING_E_AC3_JOC";
case ENCODING_DOLBY_MAT:
return "ENCODING_DOLBY_MAT";
+ case ENCODING_OPUS:
+ return "ENCODING_OPUS";
default :
return "invalid encoding " + enc;
}
@@ -548,6 +552,7 @@
case ENCODING_AC4:
case ENCODING_E_AC3_JOC:
case ENCODING_DOLBY_MAT:
+ case ENCODING_OPUS:
return true;
default:
return false;
@@ -576,6 +581,7 @@
case ENCODING_AC4:
case ENCODING_E_AC3_JOC:
case ENCODING_DOLBY_MAT:
+ case ENCODING_OPUS:
return true;
default:
return false;
@@ -607,6 +613,7 @@
case ENCODING_AC4:
case ENCODING_E_AC3_JOC:
case ENCODING_DOLBY_MAT:
+ case ENCODING_OPUS:
return false;
case ENCODING_INVALID:
default:
@@ -638,6 +645,7 @@
case ENCODING_AC4:
case ENCODING_E_AC3_JOC:
case ENCODING_DOLBY_MAT:
+ case ENCODING_OPUS:
return false;
case ENCODING_INVALID:
default:
@@ -917,6 +925,7 @@
case ENCODING_AC4:
case ENCODING_E_AC3_JOC:
case ENCODING_DOLBY_MAT:
+ case ENCODING_OPUS:
mEncoding = encoding;
break;
case ENCODING_INVALID:
@@ -1136,7 +1145,8 @@
ENCODING_AAC_XHE,
ENCODING_AC4,
ENCODING_E_AC3_JOC,
- ENCODING_DOLBY_MAT }
+ ENCODING_DOLBY_MAT,
+ ENCODING_OPUS }
)
@Retention(RetentionPolicy.SOURCE)
public @interface Encoding {}
diff --git a/media/java/android/media/AudioGain.java b/media/java/android/media/AudioGain.java
index dd129a2..cae1b59 100644
--- a/media/java/android/media/AudioGain.java
+++ b/media/java/android/media/AudioGain.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* The AudioGain describes a gain controller. Gain controllers are exposed by
diff --git a/media/java/android/media/AudioGainConfig.java b/media/java/android/media/AudioGainConfig.java
index f5ebef8..dfefa86 100644
--- a/media/java/android/media/AudioGainConfig.java
+++ b/media/java/android/media/AudioGainConfig.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* The AudioGainConfig is used by APIs setting or getting values on a given gain
diff --git a/media/java/android/media/AudioHandle.java b/media/java/android/media/AudioHandle.java
index 24f81f9..8fc834f 100644
--- a/media/java/android/media/AudioHandle.java
+++ b/media/java/android/media/AudioHandle.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* The AudioHandle is used by the audio framework implementation to
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index d552491..34ed5b3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -28,12 +28,12 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -78,6 +78,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -373,6 +374,10 @@
public static final int STREAM_TTS = AudioSystem.STREAM_TTS;
/** Used to identify the volume of audio streams for accessibility prompts */
public static final int STREAM_ACCESSIBILITY = AudioSystem.STREAM_ACCESSIBILITY;
+ /** @hide Used to identify the volume of audio streams for virtual assistant */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int STREAM_ASSISTANT = AudioSystem.STREAM_ASSISTANT;
/** Number of audio streams */
/**
@@ -1536,6 +1541,76 @@
}
//====================================================================
+ // Audio Product Strategy routing
+
+ /**
+ * @hide
+ * Set the preferred device for a given strategy, i.e. the audio routing to be used by
+ * this audio strategy. Note that the device may not be available at the time the preferred
+ * device is set, but it will be used once made available.
+ * <p>Use {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} to cancel setting
+ * this preference for this strategy.</p>
+ * @param strategy the audio strategy whose routing will be affected
+ * @param device the audio device to route to when available
+ * @return true if the operation was successful, false otherwise
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public boolean setPreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy,
+ @NonNull AudioDeviceAddress device) {
+ Objects.requireNonNull(strategy);
+ Objects.requireNonNull(device);
+ try {
+ final int status =
+ getService().setPreferredDeviceForStrategy(strategy.getId(), device);
+ return status == AudioSystem.SUCCESS;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Removes the preferred audio device previously set with
+ * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAddress)}.
+ * @param strategy the audio strategy whose routing will be affected
+ * @return true if the operation was successful, false otherwise (invalid strategy, or no
+ * device set for example)
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public boolean removePreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy) {
+ Objects.requireNonNull(strategy);
+ try {
+ final int status =
+ getService().removePreferredDeviceForStrategy(strategy.getId());
+ return status == AudioSystem.SUCCESS;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Return the preferred device for an audio strategy, previously set with
+ * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAddress)}
+ * @param strategy the strategy to query
+ * @return the preferred device for that strategy, or null if none was ever set or if the
+ * strategy is invalid
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @Nullable AudioDeviceAddress getPreferredDeviceForStrategy(
+ @NonNull AudioProductStrategy strategy) {
+ Objects.requireNonNull(strategy);
+ try {
+ return getService().getPreferredDeviceForStrategy(strategy.getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ //====================================================================
// Offload query
/**
* Returns whether offloaded playback of an audio format is supported on the device.
@@ -4962,6 +5037,14 @@
*/
public static final int GET_DEVICES_OUTPUTS = 0x0002;
+ /** @hide */
+ @IntDef(flag = true, prefix = "GET_DEVICES", value = {
+ GET_DEVICES_INPUTS,
+ GET_DEVICES_OUTPUTS }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AudioDeviceRole {}
+
/**
* Specifies to the {@link AudioManager#getDevices(int)} method to include both
* source and sink devices.
@@ -4994,7 +5077,7 @@
* @see #GET_DEVICES_ALL
* @return A (possibly zero-length) array of AudioDeviceInfo objects.
*/
- public AudioDeviceInfo[] getDevices(int flags) {
+ public AudioDeviceInfo[] getDevices(@AudioDeviceRole int flags) {
return getDevicesStatic(flags);
}
diff --git a/media/java/android/media/AudioMixPort.java b/media/java/android/media/AudioMixPort.java
index c4a5c4d..33d603f 100644
--- a/media/java/android/media/AudioMixPort.java
+++ b/media/java/android/media/AudioMixPort.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* The AudioMixPort is a specialized type of AudioPort
diff --git a/media/java/android/media/AudioMixPortConfig.java b/media/java/android/media/AudioMixPortConfig.java
index 315e46b..9d81206 100644
--- a/media/java/android/media/AudioMixPortConfig.java
+++ b/media/java/android/media/AudioMixPortConfig.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* An AudioMixPortConfig describes a possible configuration of an output or input mixer.
diff --git a/media/java/android/media/AudioPatch.java b/media/java/android/media/AudioPatch.java
index d1f8006..e5107d4 100644
--- a/media/java/android/media/AudioPatch.java
+++ b/media/java/android/media/AudioPatch.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index ab80b3a..515e9d0 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -400,6 +400,7 @@
* configurations
* @return true if active
*/
+ @SystemApi
public boolean isActive() {
switch (mPlayerState) {
case PLAYER_STATE_STARTED:
@@ -420,18 +421,7 @@
* @param pw
*/
public void dump(PrintWriter pw) {
- pw.println(" " + toLogFriendlyString(this));
- }
-
- /**
- * @hide
- */
- public static String toLogFriendlyString(AudioPlaybackConfiguration apc) {
- return new String("ID:" + apc.mPlayerIId
- + " -- type:" + toLogFriendlyPlayerType(apc.mPlayerType)
- + " -- u/pid:" + apc.mClientUid +"/" + apc.mClientPid
- + " -- state:" + toLogFriendlyPlayerState(apc.mPlayerState)
- + " -- attr:" + apc.mPlayerAttr);
+ pw.println(" " + this);
}
public static final @android.annotation.NonNull Parcelable.Creator<AudioPlaybackConfiguration> CREATOR
@@ -498,6 +488,15 @@
&& (mClientPid == that.mClientPid));
}
+ @Override
+ public String toString() {
+ return "AudioPlaybackConfiguration piid:" + mPlayerIId
+ + " type:" + toLogFriendlyPlayerType(mPlayerType)
+ + " u/pid:" + mClientUid + "/" + mClientPid
+ + " state:" + toLogFriendlyPlayerState(mPlayerState)
+ + " attr:" + mPlayerAttr;
+ }
+
//=====================================================================
// Inner class for corresponding IPlayer and its death monitoring
static final class IPlayerShell implements IBinder.DeathRecipient {
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index 83eb240..7c3ca24 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* An audio port is a node of the audio framework or hardware that can be connected to or
diff --git a/media/java/android/media/AudioPortConfig.java b/media/java/android/media/AudioPortConfig.java
index ac19bb1..16fb5b8 100644
--- a/media/java/android/media/AudioPortConfig.java
+++ b/media/java/android/media/AudioPortConfig.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* An AudioPortConfig contains a possible configuration of an audio port chosen
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index 6d9d626..14249cb 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 95afb09..fd3523d 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -23,8 +23,8 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.media.MediaRecorder.Source;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 874a215..5f32c0f 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.media.audiofx.AudioEffect;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index d64e4ef..e584add 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -19,12 +19,13 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.bluetooth.BluetoothCodecConfig;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
+import android.telephony.TelephonyManager;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -79,6 +80,8 @@
public static final int STREAM_TTS = 9;
/** Used to identify the volume of audio streams for accessibility prompts */
public static final int STREAM_ACCESSIBILITY = 10;
+ /** Used to identify the volume of audio streams for virtual assistant */
+ public static final int STREAM_ASSISTANT = 11;
/**
* @deprecated Use {@link #numStreamTypes() instead}
*/
@@ -91,7 +94,7 @@
private static native int native_get_FCC_8();
// Expose only the getter method publicly so we can change it in the future
- private static final int NUM_STREAM_TYPES = 11;
+ private static final int NUM_STREAM_TYPES = 12;
@UnsupportedAppUsage
public static final int getNumStreamTypes() { return NUM_STREAM_TYPES; }
@@ -106,7 +109,8 @@
"STREAM_SYSTEM_ENFORCED",
"STREAM_DTMF",
"STREAM_TTS",
- "STREAM_ACCESSIBILITY"
+ "STREAM_ACCESSIBILITY",
+ "STREAM_ASSISTANT"
};
/*
@@ -902,6 +906,18 @@
}
}
+ /**
+ * Returns a human readable name for a given device type
+ * @param device a native device type, NOT an AudioDeviceInfo type
+ * @return a string describing the device type
+ */
+ public static @NonNull String getDeviceName(int device) {
+ if ((device & DEVICE_BIT_IN) != 0) {
+ return getInputDeviceName(device);
+ }
+ return getOutputDeviceName(device);
+ }
+
// phone state, match audio_mode???
public static final int PHONE_STATE_OFFCALL = 0;
public static final int PHONE_STATE_RINGING = 1;
@@ -1173,6 +1189,48 @@
*/
public static native boolean isCallScreeningModeSupported();
+ // use case routing by product strategy
+
+ /**
+ * Sets the preferred device to use for a given audio strategy in the audio policy engine
+ * @param strategy the id of the strategy to configure
+ * @param device the device type and address to route to when available
+ * @return {@link #SUCCESS} if successfully set
+ */
+ public static int setPreferredDeviceForStrategy(
+ int strategy, @NonNull AudioDeviceAddress device) {
+ return setPreferredDeviceForStrategy(strategy,
+ AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
+ device.getAddress());
+ }
+ /**
+ * Set device routing per product strategy.
+ * @param strategy the id of the strategy to configure
+ * @param deviceType the native device type, NOT AudioDeviceInfo types
+ * @param deviceAddress the address of the device
+ * @return {@link #SUCCESS} if successfully set
+ */
+ private static native int setPreferredDeviceForStrategy(
+ int strategy, int deviceType, String deviceAddress);
+
+ /**
+ * Remove preferred routing for the strategy
+ * @param strategy the id of the strategy to configure
+ * @return {@link #SUCCESS} if successfully removed
+ */
+ public static native int removePreferredDeviceForStrategy(int strategy);
+
+ /**
+ * Query previously set preferred device for a strategy
+ * @param strategy the id of the strategy to query for
+ * @param device an array of size 1 that will contain the preferred device, or null if
+ * none was set
+ * @return {@link #SUCCESS} if there is a preferred device and it was successfully retrieved
+ * and written to the array
+ */
+ public static native int getPreferredDeviceForStrategy(int strategy,
+ AudioDeviceAddress[] device);
+
// Items shared with audio service
/**
@@ -1222,6 +1280,7 @@
5, // STREAM_DTMF
5, // STREAM_TTS
5, // STREAM_ACCESSIBILITY
+ 5, // STREAM_ASSISTANT
};
public static String streamToString(int stream) {
@@ -1246,7 +1305,8 @@
* </ul>
*/
public static int getPlatformType(Context context) {
- if (context.getResources().getBoolean(com.android.internal.R.bool.config_voice_capable)) {
+ if (((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
+ .isVoiceCapable()) {
return PLATFORM_VOICE;
} else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
return PLATFORM_TELEVISION;
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 7cd09de..0ced68ef 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -23,7 +23,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.Binder;
import android.os.Handler;
import android.os.HandlerThread;
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
index 963b1d1..e4bab74 100644
--- a/media/java/android/media/CamcorderProfile.java
+++ b/media/java/android/media/CamcorderProfile.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.os.Build;
diff --git a/media/java/android/media/DecoderCapabilities.java b/media/java/android/media/DecoderCapabilities.java
index df5e918..ebfc63b 100644
--- a/media/java/android/media/DecoderCapabilities.java
+++ b/media/java/android/media/DecoderCapabilities.java
@@ -16,9 +16,10 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
-import java.util.List;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.ArrayList;
+import java.util.List;
/**
* {@hide}
diff --git a/media/java/android/media/EncoderCapabilities.java b/media/java/android/media/EncoderCapabilities.java
index c09c5fa..67ce0f7 100644
--- a/media/java/android/media/EncoderCapabilities.java
+++ b/media/java/android/media/EncoderCapabilities.java
@@ -16,9 +16,10 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
-import java.util.List;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.ArrayList;
+import java.util.List;
/**
* The EncoderCapabilities class is used to retrieve the
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index ce4aac9..767b67b 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.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.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ef451ce..ad7335e 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -18,6 +18,7 @@
import android.bluetooth.BluetoothDevice;
import android.media.AudioAttributes;
+import android.media.AudioDeviceAddress;
import android.media.AudioFocusInfo;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
@@ -265,6 +266,12 @@
boolean isCallScreeningModeSupported();
+ int setPreferredDeviceForStrategy(in int strategy, in AudioDeviceAddress device);
+
+ int removePreferredDeviceForStrategy(in int strategy);
+
+ AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy);
+
// 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/Image.java b/media/java/android/media/Image.java
index 7ba122b..79b8611 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -16,14 +16,13 @@
package android.media;
-import java.nio.ByteBuffer;
-import java.lang.AutoCloseable;
-
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
+import java.nio.ByteBuffer;
+
/**
* <p>A single complete image buffer to use with a media source such as a
* {@link MediaCodec} or a
diff --git a/media/java/android/media/JetPlayer.java b/media/java/android/media/JetPlayer.java
index e85b3ff9..84ee09b 100644
--- a/media/java/android/media/JetPlayer.java
+++ b/media/java/android/media/JetPlayer.java
@@ -17,7 +17,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.os.Handler;
import android.os.Looper;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index d16a216..176bb37 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.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.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 91d644b..cbc9683 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -23,7 +23,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.SystemProperties;
import android.util.Log;
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 6523e30..13bd856 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -21,17 +21,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
-import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
-import android.os.Message;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.util.Log;
+
import dalvik.system.CloseGuard;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index c4eb031..9908e04 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.mtp.MtpConstants;
import libcore.content.type.MimeMap;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 8080f45..79b3886 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.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 java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -46,6 +46,8 @@
* <tr><th>Name</th><th>Value Type</th><th>Description</th></tr>
* <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
* <tr><td>{@link #KEY_MAX_INPUT_SIZE}</td><td>Integer</td><td>optional, maximum size of a buffer of input data</td></tr>
+ * <tr><td>{@link #KEY_PIXEL_ASPECT_RATIO_WIDTH}</td><td>Integer</td><td>optional, the pixel aspect ratio width</td></tr>
+ * <tr><td>{@link #KEY_PIXEL_ASPECT_RATIO_HEIGHT}</td><td>Integer</td><td>optional, the pixel aspect ratio height</td></tr>
* <tr><td>{@link #KEY_BIT_RATE}</td><td>Integer</td><td><b>encoder-only</b>, desired bitrate in bits/second</td></tr>
* </table>
*
@@ -99,6 +101,8 @@
* <tr><td>{@link #KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the maximum number of channels the decoder outputs.</td></tr>
* <tr><td>{@link #KEY_AAC_DRC_EFFECT_TYPE}</td><td>Integer</td><td><b>decoder-only</b>, optional, if content is AAC audio, specifies the MPEG-D DRC effect type to use.</td></tr>
* <tr><td>{@link #KEY_CHANNEL_MASK}</td><td>Integer</td><td>optional, a mask of audio channel assignments</td></tr>
+ * <tr><td>{@link #KEY_ENCODER_DELAY}</td><td>Integer</td><td>optional, the number of frames to trim from the start of the decoded audio stream.</td></tr>
+ * <tr><td>{@link #KEY_ENCODER_PADDING}</td><td>Integer</td><td>optional, the number of frames to trim from the end of the decoded audio stream.</td></tr>
* <tr><td>{@link #KEY_FLAC_COMPRESSION_LEVEL}</td><td>Integer</td><td><b>encoder-only</b>, optional, if content is FLAC audio, specifies the desired compression level.</td></tr>
* </table>
*
@@ -270,6 +274,18 @@
public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
/**
+ * A key describing the pixel aspect ratio width.
+ * The associated value is an integer
+ */
+ public static final String KEY_PIXEL_ASPECT_RATIO_WIDTH = "sar-width";
+
+ /**
+ * A key describing the pixel aspect ratio height.
+ * The associated value is an integer
+ */
+ public static final String KEY_PIXEL_ASPECT_RATIO_HEIGHT = "sar-height";
+
+ /**
* A key describing the average bitrate in bits/sec.
* The associated value is an integer
*/
@@ -569,6 +585,18 @@
public static final String KEY_CHANNEL_MASK = "channel-mask";
/**
+ * A key describing the number of frames to trim from the start of the decoded audio stream.
+ * The associated value is an integer.
+ */
+ public static final String KEY_ENCODER_DELAY = "encoder-delay";
+
+ /**
+ * A key describing the number of frames to trim from the end of the decoded audio stream.
+ * The associated value is an integer.
+ */
+ public static final String KEY_ENCODER_PADDING = "encoder-padding";
+
+ /**
* A key describing the AAC profile to be used (AAC audio formats only).
* Constants are declared in {@link android.media.MediaCodecInfo.CodecProfileLevel}.
*/
diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java
index 8ee929e..a17ff82 100644
--- a/media/java/android/media/MediaHTTPConnection.java
+++ b/media/java/android/media/MediaHTTPConnection.java
@@ -18,13 +18,14 @@
import static android.media.MediaPlayer.MEDIA_ERROR_UNSUPPORTED;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.NetworkUtils;
import android.os.IBinder;
import android.os.StrictMode;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
diff --git a/media/java/android/media/MediaHTTPService.java b/media/java/android/media/MediaHTTPService.java
index 97a0df7..3008067 100644
--- a/media/java/android/media/MediaHTTPService.java
+++ b/media/java/android/media/MediaHTTPService.java
@@ -17,7 +17,7 @@
package android.media;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.IBinder;
import android.util.Log;
diff --git a/media/java/android/media/MediaInserter.java b/media/java/android/media/MediaInserter.java
index 0749f58..ca7a01c 100644
--- a/media/java/android/media/MediaInserter.java
+++ b/media/java/android/media/MediaInserter.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.net.Uri;
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 8512dbe..a23191f 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -17,7 +17,7 @@
import android.annotation.NonNull;
import android.annotation.StringDef;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 5d2bdd7..7fca03c 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.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.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
new file mode 100644
index 0000000..88a8295
--- /dev/null
+++ b/media/java/android/media/MediaMetrics.java
@@ -0,0 +1,634 @@
+/*
+ * 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.TestApi;
+import android.os.Bundle;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * MediaMetrics is the Java interface to the MediaMetrics service.
+ *
+ * This is used to collect media statistics by the framework.
+ * It is not intended for direct application use.
+ *
+ * @hide
+ */
+public class MediaMetrics {
+ public static final String TAG = "MediaMetrics";
+
+ /**
+ * The TYPE constants below should match those in native MediaMetricsItem.h
+ */
+ private static final int TYPE_NONE = 0;
+ private static final int TYPE_INT32 = 1; // Java integer
+ private static final int TYPE_INT64 = 2; // Java long
+ private static final int TYPE_DOUBLE = 3; // Java double
+ private static final int TYPE_CSTRING = 4; // Java string
+ private static final int TYPE_RATE = 5; // Two longs, ignored in Java
+
+ // The charset used for encoding Strings to bytes.
+ private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8;
+
+ /**
+ * Item records properties and delivers to the MediaMetrics service
+ *
+ */
+ public static class Item {
+
+ /*
+ * MediaMetrics Item
+ *
+ * Creates a Byte String and sends to the MediaMetrics service.
+ * The Byte String serves as a compact form for logging data
+ * with low overhead for storage.
+ *
+ * The Byte String format is as follows:
+ *
+ * For Java
+ * int64 corresponds to long
+ * int32, uint32 corresponds to int
+ * uint16 corresponds to char
+ * uint8, int8 corresponds to byte
+ *
+ * For items transmitted from Java, uint8 and uint32 values are limited
+ * to INT8_MAX and INT32_MAX. This constrains the size of large items
+ * to 2GB, which is consistent with ByteBuffer max size. A native item
+ * can conceivably have size of 4GB.
+ *
+ * Physical layout of integers and doubles within the MediaMetrics byte string
+ * is in Native / host order, which is usually little endian.
+ *
+ * Note that primitive data (ints, doubles) within a Byte String has
+ * no extra padding or alignment requirements, like ByteBuffer.
+ *
+ * -- begin of item
+ * -- begin of header
+ * (uint32) item size: including the item size field
+ * (uint32) header size, including the item size and header size fields.
+ * (uint16) version: exactly 0
+ * (uint16) key size, that is key strlen + 1 for zero termination.
+ * (int8)+ key, a string which is 0 terminated (UTF-8).
+ * (int32) pid
+ * (int32) uid
+ * (int64) timestamp
+ * -- end of header
+ * -- begin body
+ * (uint32) number of properties
+ * -- repeat for number of properties
+ * (uint16) property size, including property size field itself
+ * (uint8) type of property
+ * (int8)+ key string, including 0 termination
+ * based on type of property (given above), one of:
+ * (int32)
+ * (int64)
+ * (double)
+ * (int8)+ for TYPE_CSTRING, including 0 termination
+ * (int64, int64) for rate
+ * -- end body
+ * -- end of item
+ *
+ * To record a MediaMetrics event, one creates a new item with an id,
+ * then use a series of puts to add properties
+ * and then a record() to send to the MediaMetrics service.
+ *
+ * The properties may not be unique, and putting a later property with
+ * the same name as an earlier property will overwrite the value and type
+ * of the prior property.
+ *
+ * The timestamp can only be recorded by a system service (and is ignored otherwise;
+ * the MediaMetrics service will fill in the timestamp as needed).
+ *
+ * The units of time are in SystemClock.elapsedRealtimeNanos().
+ *
+ * A clear() may be called to reset the properties to empty, the time to 0, but keep
+ * the other entries the same. This may be called after record().
+ * Additional properties may be added after calling record(). Changing the same property
+ * repeatedly is discouraged as - for this particular implementation - extra data
+ * is stored per change.
+ *
+ * new MediaMetrics.Item(mSomeId)
+ * .putString("event", "javaCreate")
+ * .putInt("value", intValue)
+ * .record();
+ */
+
+ /**
+ * Creates an Item with server added uid, time.
+ *
+ * This is the typical way to record a MediaMetrics item.
+ *
+ * @param key the Metrics ID associated with the item.
+ */
+ public Item(String key) {
+ this(key, -1 /* pid */, -1 /* uid */, 0 /* SystemClock.elapsedRealtimeNanos() */,
+ 2048 /* capacity */);
+ }
+
+ /**
+ * Creates an Item specifying pid, uid, time, and initial Item capacity.
+ *
+ * This might be used by a service to specify a different PID or UID for a client.
+ *
+ * @param key the Metrics ID associated with the item.
+ * An app may only set properties on an item which has already been
+ * logged previously by a service.
+ * @param pid the process ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param uid the user ID corresponding to the item.
+ * A value of -1 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill this in.
+ * @param timeNs the time when the item occurred (may be in the past).
+ * A value of 0 (or a record() from an app instead of a service) causes
+ * the MediaMetrics service to fill it in.
+ * Should be obtained from SystemClock.elapsedRealtimeNanos().
+ * @param capacity the anticipated size to use for the buffer.
+ * If the capacity is too small, the buffer will be resized to accommodate.
+ * This is amortized to copy data no more than twice.
+ */
+ public Item(String key, int pid, int uid, long timeNs, int capacity) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE - 1) {
+ throw new IllegalArgumentException("Key length too large");
+ }
+
+ // Version 0 - compute the header offsets here.
+ mHeaderSize = 4 + 4 + 2 + 2 + keyLength + 1 + 4 + 4 + 8; // see format above.
+ mPidOffset = mHeaderSize - 16;
+ mUidOffset = mHeaderSize - 12;
+ mTimeNsOffset = mHeaderSize - 8;
+ mPropertyCountOffset = mHeaderSize;
+ mPropertyStartOffset = mHeaderSize + 4;
+
+ mKey = key;
+ mBuffer = ByteBuffer.allocateDirect(
+ Math.max(capacity, mHeaderSize + MINIMUM_PAYLOAD_SIZE));
+
+ // Version 0 - fill the ByteBuffer with the header (some details updated later).
+ mBuffer.order(ByteOrder.nativeOrder())
+ .putInt((int) 0) // total size in bytes (filled in later)
+ .putInt((int) mHeaderSize) // size of header
+ .putChar((char) FORMAT_VERSION) // version
+ .putChar((char) (keyLength + 1)) // length, with zero termination
+ .put(keyBytes).put((byte) 0)
+ .putInt(pid)
+ .putInt(uid)
+ .putLong(timeNs);
+ if (mHeaderSize != mBuffer.position()) {
+ throw new IllegalStateException("Mismatched sizing");
+ }
+ mBuffer.putInt(0); // number of properties (to be later filled in by record()).
+ }
+
+ /**
+ * Sets the property with key to an integer (32 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putInt(String key, int value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 4 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT32)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putInt(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a long (64 bit) value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putLong(String key, long value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_INT64)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putLong(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a double value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putDouble(String key, double value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_DOUBLE)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .putDouble(value);
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the property with key to a String value.
+ *
+ * @param key
+ * @param value
+ * @return itself
+ */
+ public Item putString(String key, String value) {
+ final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+ final byte[] valueBytes = value.getBytes(MEDIAMETRICS_CHARSET);
+ final char propSize = (char) reserveProperty(keyBytes, valueBytes.length + 1);
+ final int estimatedFinalPosition = mBuffer.position() + propSize;
+ mBuffer.putChar(propSize)
+ .put((byte) TYPE_CSTRING)
+ .put(keyBytes).put((byte) 0) // key, zero terminated
+ .put(valueBytes).put((byte) 0); // value, zero term.
+ ++mPropertyCount;
+ if (mBuffer.position() != estimatedFinalPosition) {
+ throw new IllegalStateException("Final position " + mBuffer.position()
+ + " != estimatedFinalPosition " + estimatedFinalPosition);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the pid to the provided value.
+ *
+ * @param pid which can be -1 if the service is to fill it in from the calling info.
+ * @return itself
+ */
+ public Item setPid(int pid) {
+ mBuffer.putInt(mPidOffset, pid); // pid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the uid to the provided value.
+ *
+ * The UID represents the client associated with the property. This must be the UID
+ * of the application if it comes from the application client.
+ *
+ * Trusted services are allowed to set the uid for a client-related item.
+ *
+ * @param uid which can be -1 if the service is to fill it in from calling info.
+ * @return itself
+ */
+ public Item setUid(int uid) {
+ mBuffer.putInt(mUidOffset, uid); // uid location in byte string.
+ return this;
+ }
+
+ /**
+ * Sets the timestamp to the provided value.
+ *
+ * The time is referenced by the Boottime obtained by SystemClock.elapsedRealtimeNanos().
+ * This should be associated with the occurrence of the event. It is recommended that
+ * the event be registered immediately when it occurs, and no later than 500ms
+ * (and certainly not in the future).
+ *
+ * @param timeNs which can be 0 if the service is to fill it in at the time of call.
+ * @return itself
+ */
+ public Item setTimestamp(long timeNs) {
+ mBuffer.putLong(mTimeNsOffset, timeNs); // time location in byte string.
+ return this;
+ }
+
+ /**
+ * Clears the properties and resets the time to 0.
+ *
+ * No other values are changed.
+ *
+ * @return itself
+ */
+ public Item clear() {
+ mBuffer.position(mPropertyStartOffset);
+ mBuffer.limit(mBuffer.capacity());
+ mBuffer.putLong(mTimeNsOffset, 0); // reset time.
+ mPropertyCount = 0;
+ return this;
+ }
+
+ /**
+ * Sends the item to the MediaMetrics service.
+ *
+ * The item properties are unchanged, hence record() may be called more than once
+ * to send the same item twice. Also, record() may be called without any properties.
+ *
+ * @return true if successful.
+ */
+ public boolean record() {
+ updateHeader();
+ return native_submit_bytebuffer(mBuffer, mBuffer.limit()) >= 0;
+ }
+
+ /**
+ * Converts the Item to a Bundle.
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @return a Bundle with the keys set according to data in the Item's buffer.
+ */
+ @TestApi
+ public Bundle toBundle() {
+ updateHeader();
+
+ final ByteBuffer buffer = mBuffer.duplicate();
+ buffer.order(ByteOrder.nativeOrder()) // restore order property
+ .flip(); // convert from write buffer to read buffer
+
+ return toBundle(buffer);
+ }
+
+ // The following constants are used for tests to extract
+ // the content of the Bundle for CTS testing.
+ @TestApi
+ public static final String BUNDLE_TOTAL_SIZE = "_totalSize";
+ @TestApi
+ public static final String BUNDLE_HEADER_SIZE = "_headerSize";
+ @TestApi
+ public static final String BUNDLE_VERSION = "_version";
+ @TestApi
+ public static final String BUNDLE_KEY_SIZE = "_keySize";
+ @TestApi
+ public static final String BUNDLE_KEY = "_key";
+ @TestApi
+ public static final String BUNDLE_PID = "_pid";
+ @TestApi
+ public static final String BUNDLE_UID = "_uid";
+ @TestApi
+ public static final String BUNDLE_TIMESTAMP = "_timestamp";
+ @TestApi
+ public static final String BUNDLE_PROPERTY_COUNT = "_propertyCount";
+
+ /**
+ * Converts a buffer contents to a bundle
+ *
+ * This is primarily used as a test API for CTS.
+ *
+ * @param buffer contains the byte data serialized according to the byte string version.
+ * @return a Bundle with the keys set according to data in the buffer.
+ */
+ @TestApi
+ public static Bundle toBundle(ByteBuffer buffer) {
+ final Bundle bundle = new Bundle();
+
+ final int totalSize = buffer.getInt();
+ final int headerSize = buffer.getInt();
+ final char version = buffer.getChar();
+ final char keySize = buffer.getChar(); // includes zero termination, i.e. keyLength + 1
+
+ if (totalSize < 0 || headerSize < 0) {
+ throw new IllegalArgumentException("Item size cannot be > " + Integer.MAX_VALUE);
+ }
+ final String key;
+ if (keySize > 0) {
+ key = getStringFromBuffer(buffer, keySize);
+ } else {
+ throw new IllegalArgumentException("Illegal null key");
+ }
+
+ final int pid = buffer.getInt();
+ final int uid = buffer.getInt();
+ final long timestamp = buffer.getLong();
+
+ // Verify header size (depending on version).
+ final int headerRead = buffer.position();
+ if (version == 0) {
+ if (headerRead != headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " != headerSize:" + headerSize);
+ }
+ } else {
+ // future versions should only increase header size
+ // by adding to the end.
+ if (headerRead > headerSize) {
+ throw new IllegalArgumentException(
+ "Item key:" + key
+ + " headerRead:" + headerRead + " > headerSize:" + headerSize);
+ } else if (headerRead < headerSize) {
+ buffer.position(headerSize);
+ }
+ }
+
+ // Body always starts with properties.
+ final int propertyCount = buffer.getInt();
+ if (propertyCount < 0) {
+ throw new IllegalArgumentException(
+ "Cannot have more than " + Integer.MAX_VALUE + " properties");
+ }
+ bundle.putInt(BUNDLE_TOTAL_SIZE, totalSize);
+ bundle.putInt(BUNDLE_HEADER_SIZE, headerSize);
+ bundle.putChar(BUNDLE_VERSION, version);
+ bundle.putChar(BUNDLE_KEY_SIZE, keySize);
+ bundle.putString(BUNDLE_KEY, key);
+ bundle.putInt(BUNDLE_PID, pid);
+ bundle.putInt(BUNDLE_UID, uid);
+ bundle.putLong(BUNDLE_TIMESTAMP, timestamp);
+ bundle.putInt(BUNDLE_PROPERTY_COUNT, propertyCount);
+
+ for (int i = 0; i < propertyCount; ++i) {
+ final int initialBufferPosition = buffer.position();
+ final char propSize = buffer.getChar();
+ final byte type = buffer.get();
+
+ // Log.d(TAG, "(" + i + ") propSize:" + ((int)propSize) + " type:" + type);
+ final String propKey = getStringFromBuffer(buffer);
+ switch (type) {
+ case TYPE_INT32:
+ bundle.putInt(propKey, buffer.getInt());
+ break;
+ case TYPE_INT64:
+ bundle.putLong(propKey, buffer.getLong());
+ break;
+ case TYPE_DOUBLE:
+ bundle.putDouble(propKey, buffer.getDouble());
+ break;
+ case TYPE_CSTRING:
+ bundle.putString(propKey, getStringFromBuffer(buffer));
+ break;
+ case TYPE_NONE:
+ break; // ignore on Java side
+ case TYPE_RATE:
+ buffer.getLong(); // consume the first int64_t of rate
+ buffer.getLong(); // consume the second int64_t of rate
+ break; // ignore on Java side
+ default:
+ // These are unsupported types for version 0
+ // We ignore them if the version is greater than 0.
+ if (version == 0) {
+ throw new IllegalArgumentException(
+ "Property " + propKey + " has unsupported type " + type);
+ }
+ buffer.position(initialBufferPosition + propSize); // advance and skip
+ break;
+ }
+ final int deltaPosition = buffer.position() - initialBufferPosition;
+ if (deltaPosition != propSize) {
+ throw new IllegalArgumentException("propSize:" + propSize
+ + " != deltaPosition:" + deltaPosition);
+ }
+ }
+
+ final int finalPosition = buffer.position();
+ if (finalPosition != totalSize) {
+ throw new IllegalArgumentException("totalSize:" + totalSize
+ + " != finalPosition:" + finalPosition);
+ }
+ return bundle;
+ }
+
+ // Version 0 byte offsets for the header.
+ private static final int FORMAT_VERSION = 0;
+ private static final int TOTAL_SIZE_OFFSET = 0;
+ private static final int HEADER_SIZE_OFFSET = 4;
+ private static final int MINIMUM_PAYLOAD_SIZE = 4;
+ private final int mPidOffset; // computed in constructor
+ private final int mUidOffset; // computed in constructor
+ private final int mTimeNsOffset; // computed in constructor
+ private final int mPropertyCountOffset; // computed in constructor
+ private final int mPropertyStartOffset; // computed in constructor
+ private final int mHeaderSize; // computed in constructor
+
+ private final String mKey;
+
+ private ByteBuffer mBuffer; // may be reallocated if capacity is insufficient.
+ private int mPropertyCount = 0; // overflow not checked (mBuffer would overflow first).
+
+ private int reserveProperty(byte[] keyBytes, int payloadSize) {
+ final int keyLength = keyBytes.length;
+ if (keyLength > Character.MAX_VALUE) {
+ throw new IllegalStateException("property key too long "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET));
+ }
+ if (payloadSize > Character.MAX_VALUE) {
+ throw new IllegalStateException("payload too large " + payloadSize);
+ }
+
+ // See the byte string property format above.
+ final int size = 2 /* length */
+ + 1 /* type */
+ + keyLength + 1 /* key length with zero termination */
+ + payloadSize; /* payload size */
+
+ if (size > Character.MAX_VALUE) {
+ throw new IllegalStateException("Item property "
+ + new String(keyBytes, MEDIAMETRICS_CHARSET) + " is too large to send");
+ }
+
+ if (mBuffer.remaining() < size) {
+ int newCapacity = mBuffer.position() + size;
+ if (newCapacity > Integer.MAX_VALUE >> 1) {
+ throw new IllegalStateException(
+ "Item memory requirements too large: " + newCapacity);
+ }
+ newCapacity <<= 1;
+ ByteBuffer buffer = ByteBuffer.allocateDirect(newCapacity);
+ buffer.order(ByteOrder.nativeOrder());
+
+ // Copy data from old buffer to new buffer.
+ mBuffer.flip();
+ buffer.put(mBuffer);
+
+ // set buffer to new buffer
+ mBuffer = buffer;
+ }
+ return size;
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer) {
+ return getStringFromBuffer(buffer, Integer.MAX_VALUE);
+ }
+
+ // Used for test
+ private static String getStringFromBuffer(ByteBuffer buffer, int size) {
+ int i = buffer.position();
+ int limit = buffer.limit();
+ if (size < Integer.MAX_VALUE - i && i + size < limit) {
+ limit = i + size;
+ }
+ for (; i < limit; ++i) {
+ if (buffer.get(i) == 0) {
+ final int newPosition = i + 1;
+ if (size != Integer.MAX_VALUE && newPosition - buffer.position() != size) {
+ throw new IllegalArgumentException("chars consumed at " + i + ": "
+ + (newPosition - buffer.position()) + " != size: " + size);
+ }
+ final String found;
+ if (buffer.hasArray()) {
+ found = new String(
+ buffer.array(), buffer.position() + buffer.arrayOffset(),
+ i - buffer.position(), MEDIAMETRICS_CHARSET);
+ buffer.position(newPosition);
+ } else {
+ final byte[] array = new byte[i - buffer.position()];
+ buffer.get(array);
+ found = new String(array, MEDIAMETRICS_CHARSET);
+ buffer.get(); // remove 0.
+ }
+ return found;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No zero termination found in string position: "
+ + buffer.position() + " end: " + i);
+ }
+
+ /**
+ * May be called multiple times - just makes the header consistent with the current
+ * properties written.
+ */
+ private void updateHeader() {
+ // Buffer sized properly in constructor.
+ mBuffer.putInt(TOTAL_SIZE_OFFSET, mBuffer.position()) // set total length
+ .putInt(mPropertyCountOffset, (char) mPropertyCount); // set number of properties
+ }
+ }
+
+ private static native int native_submit_bytebuffer(@NonNull ByteBuffer buffer, int length);
+}
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index 0fb392b..58c73b5 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -18,8 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.media.MediaCodec;
+import android.compat.annotation.UnsupportedAppUsage;
import android.media.MediaCodec.BufferInfo;
import dalvik.system.CloseGuard;
@@ -677,10 +676,9 @@
throw new IllegalArgumentException("bufferInfo must not be null");
}
if (bufferInfo.size < 0 || bufferInfo.offset < 0
- || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity()
- || bufferInfo.presentationTimeUs < 0) {
+ || (bufferInfo.offset + bufferInfo.size) > byteBuf.capacity()) {
throw new IllegalArgumentException("bufferInfo must specify a" +
- " valid buffer offset, size and presentation time");
+ " valid buffer offset and size");
}
if (mNativeObject == 0) {
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 7d107dd..71c97534 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -19,8 +19,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index abb8206..4198d79 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -23,8 +23,8 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.Camera;
import android.os.Build;
import android.os.Handler;
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 445420f..7008d32 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -249,20 +249,34 @@
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 true if the route supports at least one of the specified control categories
+ * 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 supportsControlCategory(@NonNull Collection<String> controlCategories) {
+ public boolean supportsControlCategories(@NonNull Collection<String> controlCategories) {
Objects.requireNonNull(controlCategories, "control categories must not be null");
for (String controlCategory : controlCategories) {
- for (String supportedCategory : getSupportedCategories()) {
- if (TextUtils.equals(controlCategory, supportedCategory)) {
- return true;
- }
+ if (supportsControlCategory(controlCategory)) {
+ return true;
}
}
return false;
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index 4f203de..7078d4a 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -46,14 +46,13 @@
};
@Nullable
- private final String mUniqueId;
+ final String mUniqueId;
@NonNull
- private final ArrayMap<String, MediaRoute2Info> mRoutes;
+ final ArrayMap<String, MediaRoute2Info> mRoutes;
MediaRoute2ProviderInfo(@NonNull Builder builder) {
- if (builder == null) {
- throw new NullPointerException("Builder must not be null.");
- }
+ Objects.requireNonNull(builder, "builder must not be null.");
+
mUniqueId = builder.mUniqueId;
mRoutes = builder.mRoutes;
}
@@ -143,6 +142,7 @@
public Builder(@NonNull MediaRoute2ProviderInfo descriptor) {
Objects.requireNonNull(descriptor, "descriptor must not be null");
+ mUniqueId = descriptor.mUniqueId;
mRoutes = new ArrayMap<>(descriptor.mRoutes);
}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 1b6183e..8d6f2551 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -27,8 +27,13 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -40,10 +45,14 @@
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
private final Handler mHandler;
+ private final Object mSessionLock = new Object();
private ProviderStub mStub;
private IMediaRoute2ProviderClient mClient;
private MediaRoute2ProviderInfo mProviderInfo;
+ @GuardedBy("mSessionLock")
+ private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
+
public MediaRoute2ProviderService() {
mHandler = new Handler(Looper.getMainLooper());
}
@@ -93,6 +102,7 @@
/**
* Called when requestSetVolume is called on a route of the provider
+ *
* @param routeId the id of the route
* @param volume the target volume
*/
@@ -100,15 +110,164 @@
/**
* Called when requestUpdateVolume is called on a route of the provider
+ *
* @param routeId id of the route
* @param delta the delta to add to the current volume
*/
public abstract void onUpdateVolume(@NonNull String routeId, int delta);
/**
- * Updates provider info and publishes routes
+ * Gets information of the session with the given id.
+ *
+ * @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.
*/
- public final void setProviderInfo(MediaRoute2ProviderInfo info) {
+ @Nullable
+ public final RouteSessionInfo getSessionInfo(int sessionId) {
+ synchronized (mSessionLock) {
+ return mSessionInfo.get(sessionId);
+ }
+ }
+
+ /**
+ * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains.
+ */
+ @NonNull
+ public final List<RouteSessionInfo> getAllSessionInfo() {
+ synchronized (mSessionLock) {
+ return new ArrayList<>(mSessionInfo.values());
+ }
+ }
+
+ /**
+ * Sets the information of the session with the given id.
+ * If there is no session matched with the given id, 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 sessionId id of the session that should update its information
+ * @param sessionInfo new session information
+ */
+ public final void setSessionInfo(int sessionId, @NonNull RouteSessionInfo sessionInfo) {
+ Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+ synchronized (mSessionLock) {
+ if (mSessionInfo.containsKey(sessionId)) {
+ mSessionInfo.put(sessionId, sessionInfo);
+ } else {
+ Log.w(TAG, "Ignoring session info update.");
+ }
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param sessionId id of the session
+ * @param sessionInfo information of the new session.
+ * Pass {@code null} to reject the request or inform clients that
+ * session creation has failed.
+ * @param controlHints a {@link Bundle} that contains how to control the session.
+ */
+ //TODO: fail reason?
+ public final void notifySessionCreated(int sessionId, @Nullable RouteSessionInfo sessionInfo,
+ @Nullable Bundle controlHints) {
+ //TODO: validate sessionId (it must be in "waiting list")
+ synchronized (mSessionLock) {
+ mSessionInfo.put(sessionId, sessionInfo);
+ //TODO: notify media router service of session creation.
+ }
+ }
+
+ /**
+ * Releases a session with the given id.
+ * {@link #onDestroySession} is called if the session is released.
+ *
+ * @param sessionId id of the session to be released
+ * @see #onDestroySession(int, RouteSessionInfo)
+ */
+ public final void releaseSession(int sessionId) {
+ RouteSessionInfo sessionInfo;
+ synchronized (mSessionLock) {
+ sessionInfo = mSessionInfo.put(sessionId, null);
+ }
+ if (sessionInfo != null) {
+ mHandler.sendMessage(obtainMessage(
+ MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo));
+ }
+ }
+
+ /**
+ * Called when a session should be created.
+ * You should create and maintain your own session and notifies the client of
+ * session info. Call {@link #notifySessionCreated(int, RouteSessionInfo, Bundle)}
+ * 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(int, RouteSessionInfo, Bundle)}.
+ *
+ * @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 sessionId the id of a new session
+ */
+ public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
+ @NonNull String controlCategory, int sessionId);
+
+ /**
+ * 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.
+ *
+ * @param sessionId id of the session being destroyed.
+ * @param lastSessionInfo information of the session being destroyed.
+ * @see #releaseSession(int)
+ */
+ public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo);
+
+ //TODO: make a way to reject the request
+ /**
+ * Called when a client requests adding a route to a session.
+ * After the route is added, call {@link #setSessionInfo(int, RouteSessionInfo)} to update
+ * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
+ * clients of updated session info.
+ *
+ * @param sessionId id of the session
+ * @param routeId id of the route
+ * @see #setSessionInfo(int, RouteSessionInfo)
+ */
+ public abstract void onAddRoute(int sessionId, @NonNull String routeId);
+
+ //TODO: make a way to reject the request
+ /**
+ * Called when a client requests removing a route from a session.
+ * After the route is removed, call {@link #setSessionInfo(int, RouteSessionInfo)} to update
+ * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
+ * clients of updated session info.
+ *
+ * @param sessionId id of the session
+ * @param routeId id of the route
+ */
+ public abstract void onRemoveRoute(int 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 #setSessionInfo(int, RouteSessionInfo)} to update
+ * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
+ * clients of updated session info.
+ *
+ * @param sessionId id of the session
+ * @param routeId id of the route
+ */
+ public abstract void onTransferRoute(int sessionId, @NonNull String routeId);
+
+ /**
+ * Updates provider info and publishes routes and session info.
+ */
+ public final void updateProviderInfo(MediaRoute2ProviderInfo info) {
mProviderInfo = info;
publishState();
}
@@ -142,6 +301,7 @@
}
void publishState() {
+ //TODO: sends session info
if (mClient == null) {
return;
}
@@ -179,35 +339,35 @@
}
@Override
- public void requestSelectRoute(String packageName, String id, int seq) {
+ public void requestSelectRoute(String packageName, String routeId, int seq) {
+ //TODO: call onCreateSession instead
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
- MediaRoute2ProviderService.this, packageName, id,
- new SelectToken(packageName, id, seq)));
-
+ MediaRoute2ProviderService.this, packageName, routeId,
+ new SelectToken(packageName, routeId, seq)));
}
@Override
- public void unselectRoute(String packageName, String id) {
+ public void unselectRoute(String packageName, String routeId) {
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUnselectRoute,
- MediaRoute2ProviderService.this, packageName, id));
+ MediaRoute2ProviderService.this, packageName, routeId));
}
@Override
- public void notifyControlRequestSent(String id, Intent request) {
+ public void notifyControlRequestSent(String routeId, Intent request) {
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onControlRequest,
- MediaRoute2ProviderService.this, id, request));
+ MediaRoute2ProviderService.this, routeId, request));
}
@Override
- public void requestSetVolume(String id, int volume) {
+ public void requestSetVolume(String routeId, int volume) {
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume,
- MediaRoute2ProviderService.this, id, volume));
+ MediaRoute2ProviderService.this, routeId, volume));
}
@Override
- public void requestUpdateVolume(String id, int delta) {
+ public void requestUpdateVolume(String routeId, int delta) {
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume,
- MediaRoute2ProviderService.this, id, delta));
+ MediaRoute2ProviderService.this, routeId, delta));
}
}
}
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index d72231f..9837e1c 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -22,8 +22,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3e6f4c0..f9dabcf 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -23,6 +23,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -51,7 +52,6 @@
* @hide
*/
public class MediaRouter2 {
-
/** @hide */
@Retention(SOURCE)
@IntDef(value = {
@@ -102,13 +102,11 @@
new CopyOnWriteArrayList<>();
private final String mPackageName;
+ @GuardedBy("sLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- //TODO: Use a lock for this to cover the below use case
- // mRouter.setControlCategories(...);
- // routes = mRouter.getRoutes();
- // The current implementation returns empty list
- private volatile List<String> mControlCategories = Collections.emptyList();
+ @GuardedBy("sLock")
+ private List<String> mControlCategories = Collections.emptyList();
private MediaRoute2Info mSelectedRoute;
@GuardedBy("sLock")
@@ -117,7 +115,9 @@
private Client2 mClient;
final Handler mHandler;
- volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
+ @GuardedBy("sLock")
+ private boolean mShouldUpdateRoutes;
+ private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
/**
* Gets an instance of the media router associated with the context.
@@ -171,8 +171,7 @@
/**
* Registers a callback to discover routes and to receive events when they change.
* <p>
- * If you register the same callback twice or more, the previous arguments will be overwritten
- * with the new arguments.
+ * If you register the same callback twice or more, it will be ignored.
* </p>
*/
public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@@ -180,18 +179,10 @@
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
- CallbackRecord record;
- // This is required to prevent adding the same callback twice.
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- record = new CallbackRecord(callback);
- mCallbackRecords.add(record);
- } else {
- record = mCallbackRecords.get(index);
- }
- record.mExecutor = executor;
- record.mFlags = flags;
+ CallbackRecord record = new CallbackRecord(callback, executor, flags);
+ if (!mCallbackRecords.addIfAbsent(record)) {
+ Log.w(TAG, "Ignoring the same callback");
+ return;
}
synchronized (sLock) {
@@ -206,8 +197,6 @@
}
}
}
- //TODO: Is it thread-safe?
- record.notifyRoutes();
//TODO: Update discovery request here.
}
@@ -222,23 +211,20 @@
public void unregisterCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "callback must not be null");
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- Log.w(TAG, "Ignoring to remove unknown callback. " + callback);
- return;
- }
- mCallbackRecords.remove(index);
- synchronized (sLock) {
- if (mCallbackRecords.size() == 0 && mClient != null) {
- try {
- mMediaRouterService.unregisterClient2(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router.", ex);
- }
- //TODO: Clean up mRoutes. (onHandler?)
- mClient = null;
+ if (!mCallbackRecords.remove(new CallbackRecord(callback, null, 0))) {
+ Log.w(TAG, "Ignoring unknown callback");
+ return;
+ }
+
+ synchronized (sLock) {
+ if (mCallbackRecords.size() == 0 && mClient != null) {
+ try {
+ mMediaRouterService.unregisterClient2(mClient);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to unregister media router.", ex);
}
+ //TODO: Clean up mRoutes. (onHandler?)
+ mClient = null;
}
}
}
@@ -246,26 +232,52 @@
//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 only exists and are handled
+ * 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");
- // To ensure invoking callbacks correctly according to control categories
- mHandler.sendMessage(obtainMessage(MediaRouter2::setControlCategoriesOnHandler,
- MediaRouter2.this, new ArrayList<>(controlCategories)));
+ 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);
+ }
+ }
+ }
}
/**
* Gets the unmodifiable list of {@link MediaRoute2Info routes} currently
* 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
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
+ synchronized (sLock) {
+ if (mShouldUpdateRoutes) {
+ mShouldUpdateRoutes = false;
+
+ List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ for (MediaRoute2Info route : mRoutes.values()) {
+ if (route.supportsControlCategories(mControlCategories)) {
+ filteredRoutes.add(route);
+ }
+ }
+ mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
+ }
+ }
return mFilteredRoutes;
}
@@ -379,43 +391,16 @@
}
}
- @GuardedBy("mCallbackRecords")
- private int findCallbackRecordIndexLocked(Callback callback) {
- final int count = mCallbackRecords.size();
- for (int i = 0; i < count; i++) {
- CallbackRecord callbackRecord = mCallbackRecords.get(i);
- if (callbackRecord.mCallback == callback) {
- return i;
- }
- }
- return -1;
- }
-
- private void setControlCategoriesOnHandler(List<String> newControlCategories) {
- List<String> prevControlCategories = mControlCategories;
+ private void handleControlCategoriesChangedLocked(List<String> newControlCategories) {
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+ List<String> prevControlCategories = mControlCategories;
mControlCategories = newControlCategories;
- Client2 client;
- synchronized (sLock) {
- client = mClient;
- }
- if (client != null) {
- try {
- mMediaRouterService.setControlCategories(client, mControlCategories);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to set control categories.", ex);
- }
- }
for (MediaRoute2Info route : mRoutes.values()) {
- boolean preSupported = route.supportsControlCategory(prevControlCategories);
- boolean postSupported = route.supportsControlCategory(newControlCategories);
- if (postSupported) {
- filteredRoutes.add(route);
- }
+ boolean preSupported = route.supportsControlCategories(prevControlCategories);
+ boolean postSupported = route.supportsControlCategories(newControlCategories);
if (preSupported == postSupported) {
continue;
}
@@ -425,13 +410,14 @@
addedRoutes.add(route);
}
}
- mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
if (removedRoutes.size() > 0) {
- notifyRoutesRemoved(removedRoutes);
+ mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesRemoved,
+ MediaRouter2.this, removedRoutes));
}
if (addedRoutes.size() > 0) {
- notifyRoutesAdded(addedRoutes);
+ mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesAdded,
+ MediaRouter2.this, addedRoutes));
}
}
@@ -441,42 +427,47 @@
// 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<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
- if (route.supportsControlCategory(mControlCategories)) {
- addedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.put(route.getUniqueId(), route);
+ if (route.supportsControlCategories(mControlCategories)) {
+ addedRoutes.add(route);
+ }
}
+ mShouldUpdateRoutes = true;
}
if (addedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesAdded(addedRoutes);
}
}
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getUniqueId());
- if (route.supportsControlCategory(mControlCategories)) {
- removedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.remove(route.getUniqueId());
+ if (route.supportsControlCategories(mControlCategories)) {
+ removedRoutes.add(route);
+ }
}
+ mShouldUpdateRoutes = true;
}
if (removedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesRemoved(removedRoutes);
}
}
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
- if (route.supportsControlCategory(mControlCategories)) {
- changedRoutes.add(route);
+ synchronized (sLock) {
+ for (MediaRoute2Info route : routes) {
+ mRoutes.put(route.getUniqueId(), route);
+ if (route.supportsControlCategories(mControlCategories)) {
+ changedRoutes.add(route);
+ }
}
}
if (changedRoutes.size() > 0) {
- refreshFilteredRoutes();
notifyRoutesChanged(changedRoutes);
}
}
@@ -500,17 +491,6 @@
notifyRouteSelected(route, reason, controlHints);
}
- private void refreshFilteredRoutes() {
- List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
-
- for (MediaRoute2Info route : mRoutes.values()) {
- if (route.supportsControlCategory(mControlCategories)) {
- filteredRoutes.add(route);
- }
- }
- mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
- }
-
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
@@ -544,13 +524,16 @@
*/
public static class Callback {
/**
- * Called when routes are added.
+ * Called when routes are added. Whenever you registers a callback, this will
+ * be invoked with known routes.
+ *
* @param routes the list of routes that have been added. It's never empty.
*/
public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when routes are removed.
+ *
* @param routes the list of routes that have been removed. It's never empty.
*/
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
@@ -569,6 +552,7 @@
/**
* Called when a route is selected. Exactly one route can be selected at a time.
+ *
* @param route the selected route.
* @param reason the reason why the route is selected.
* @param controlHints An optional bundle of provider-specific arguments which may be
@@ -587,16 +571,26 @@
public Executor mExecutor;
public int mFlags;
- CallbackRecord(@NonNull Callback callback) {
+ CallbackRecord(@NonNull Callback callback, @Nullable Executor executor, int flags) {
mCallback = callback;
+ mExecutor = executor;
+ mFlags = flags;
}
- void notifyRoutes() {
- final List<MediaRoute2Info> routes = mFilteredRoutes;
- // notify only when bound to media router service.
- if (routes.size() > 0) {
- mExecutor.execute(() -> mCallback.onRoutesAdded(routes));
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
}
+ if (!(obj instanceof CallbackRecord)) {
+ return false;
+ }
+ return mCallback == ((CallbackRecord) obj).mCallback;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCallback.hashCode();
}
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index d56dd11..5de3370 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -25,6 +25,7 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -57,7 +58,7 @@
private Client mClient;
private final IMediaRouterService mMediaRouterService;
final Handler mHandler;
- final List<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
+ final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
private final Object mRoutesLock = new Object();
@GuardedBy("mRoutesLock")
@@ -99,14 +100,10 @@
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(callback, "callback must not be null");
- CallbackRecord callbackRecord;
- synchronized (mCallbackRecords) {
- if (findCallbackRecordIndexLocked(callback) >= 0) {
- Log.w(TAG, "Ignoring to add the same callback twice.");
- return;
- }
- callbackRecord = new CallbackRecord(executor, callback);
- mCallbackRecords.add(callbackRecord);
+ CallbackRecord callbackRecord = new CallbackRecord(executor, callback);
+ if (!mCallbackRecords.addIfAbsent(callbackRecord)) {
+ Log.w(TAG, "Ignoring to add the same callback twice.");
+ return;
}
synchronized (sLock) {
@@ -118,8 +115,6 @@
} catch (RemoteException ex) {
Log.e(TAG, "Unable to register media router manager.", ex);
}
- } else {
- callbackRecord.notifyRoutes();
}
}
}
@@ -132,36 +127,23 @@
public void unregisterCallback(@NonNull Callback callback) {
Objects.requireNonNull(callback, "callback must not be null");
- synchronized (mCallbackRecords) {
- final int index = findCallbackRecordIndexLocked(callback);
- if (index < 0) {
- Log.w(TAG, "Ignore removing unknown callback. " + callback);
- return;
- }
- mCallbackRecords.remove(index);
- synchronized (sLock) {
- if (mCallbackRecords.size() == 0 && mClient != null) {
- try {
- mMediaRouterService.unregisterManager(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router manager", ex);
- }
- //TODO: clear mRoutes?
- mClient = null;
- }
- }
+ if (!mCallbackRecords.remove(new CallbackRecord(null, callback))) {
+ Log.w(TAG, "Ignore removing unknown callback. " + callback);
+ return;
}
- }
- @GuardedBy("mCallbackRecords")
- private int findCallbackRecordIndexLocked(Callback callback) {
- final int count = mCallbackRecords.size();
- for (int i = 0; i < count; i++) {
- if (mCallbackRecords.get(i).mCallback == callback) {
- return i;
+ synchronized (sLock) {
+ if (mCallbackRecords.size() == 0 && mClient != null) {
+ try {
+ mMediaRouterService.unregisterManager(mClient);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to unregister media router manager", ex);
+ }
+ //TODO: clear mRoutes?
+ mClient = null;
+ mControlCategoryMap.clear();
}
}
- return -1;
}
//TODO: Use cache not to create array. For now, it's unclear when to purge the cache.
@@ -182,12 +164,39 @@
List<MediaRoute2Info> routes = new ArrayList<>();
synchronized (mRoutesLock) {
for (MediaRoute2Info route : mRoutes.values()) {
- if (route.supportsControlCategory(controlCategories)) {
+ if (route.supportsControlCategories(controlCategories)) {
routes.add(route);
}
}
}
- //TODO: Should we cache this?
+ return routes;
+ }
+
+ /**
+ * Gets the list of routes that are actively used by {@link MediaRouter2}.
+ */
+ @NonNull
+ public List<MediaRoute2Info> getActiveRoutes() {
+ List<MediaRoute2Info> routes = new ArrayList<>();
+ synchronized (mRoutesLock) {
+ for (MediaRoute2Info route : mRoutes.values()) {
+ if (!TextUtils.isEmpty(route.getClientPackageName())) {
+ routes.add(route);
+ }
+ }
+ }
+ return routes;
+ }
+
+ /**
+ * Gets the list of discovered routes
+ */
+ @NonNull
+ public List<MediaRoute2Info> getAllRoutes() {
+ List<MediaRoute2Info> routes = new ArrayList<>();
+ synchronized (mRoutesLock) {
+ routes.addAll(mRoutes.values());
+ }
return routes;
}
@@ -342,10 +351,14 @@
}
void updateControlCategories(String packageName, List<String> categories) {
- mControlCategoryMap.put(packageName, categories);
+ List<String> prevCategories = mControlCategoryMap.put(packageName, categories);
+ if ((prevCategories == null && categories.size() == 0)
+ || Objects.equals(categories, prevCategories)) {
+ return;
+ }
for (CallbackRecord record : mCallbackRecords) {
record.mExecutor.execute(
- () -> record.mCallback.onControlCategoriesChanged(packageName));
+ () -> record.mCallback.onControlCategoriesChanged(packageName, categories));
}
}
@@ -386,8 +399,10 @@
* Called when the control categories of an app is changed.
*
* @param packageName the package name of the application
+ * @param controlCategories the list of control categories set by an application.
*/
- public void onControlCategoriesChanged(@NonNull String packageName) {}
+ public void onControlCategoriesChanged(@NonNull String packageName,
+ @NonNull List<String> controlCategories) {}
}
final class CallbackRecord {
@@ -399,14 +414,20 @@
mCallback = callback;
}
- void notifyRoutes() {
- List<MediaRoute2Info> routes;
- synchronized (mRoutesLock) {
- routes = new ArrayList<>(mRoutes.values());
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
}
- if (routes.size() > 0) {
- mExecutor.execute(() -> mCallback.onRoutesAdded(routes));
+ if (!(obj instanceof CallbackRecord)) {
+ return false;
}
+ return mCallback == ((CallbackRecord) obj).mCallback;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCallback.hashCode();
}
}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index ca96c9a..aae2606 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 515f6a8..40e9073 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -19,6 +19,7 @@
import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ServiceConnection;
import android.net.Uri;
@@ -197,7 +198,7 @@
private static Uri scanFileQuietly(ContentProviderClient client, File file) {
Uri uri = null;
try {
- uri = MediaStore.scanFile(client, file.getCanonicalFile());
+ uri = MediaStore.scanFile(ContentResolver.wrap(client), file.getCanonicalFile());
Log.d(TAG, "Scanned " + file + " to " + uri);
} catch (Exception e) {
Log.w(TAG, "Failed to scan " + file + ": " + e);
diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java
index 792a2ba..ef17073 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.util.Log;
import android.util.MathUtils;
diff --git a/media/java/android/media/MicrophoneInfo.java b/media/java/android/media/MicrophoneInfo.java
index f805975..876628f 100644
--- a/media/java/android/media/MicrophoneInfo.java
+++ b/media/java/android/media/MicrophoneInfo.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Pair;
import java.lang.annotation.Retention;
diff --git a/media/java/android/media/PlaybackParams.java b/media/java/android/media/PlaybackParams.java
index b4325b6..f24f831 100644
--- a/media/java/android/media/PlaybackParams.java
+++ b/media/java/android/media/PlaybackParams.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
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/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index 325420b..c5fd3c3 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -16,8 +16,8 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index f70963a..9e48f1e 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -16,8 +16,8 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java
index 2be206f..e529af9 100644
--- a/media/java/android/media/RemoteDisplay.java
+++ b/media/java/android/media/RemoteDisplay.java
@@ -16,12 +16,12 @@
package android.media;
-import dalvik.system.CloseGuard;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.view.Surface;
+import dalvik.system.CloseGuard;
+
/**
* Listens for Wifi remote display connections managed by the media server.
*
diff --git a/media/java/android/media/RemoteDisplayState.java b/media/java/android/media/RemoteDisplayState.java
index 2f4ace0..fed361a 100644
--- a/media/java/android/media/RemoteDisplayState.java
+++ b/media/java/android/media/RemoteDisplayState.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index eb680c8..d35bc41 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -17,7 +17,7 @@
package android.media;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 9064e68..d058243 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -23,9 +23,9 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
import android.app.Activity;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -908,7 +908,7 @@
}
// Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}.
- return MediaStore.scanFile(mContext, outFile);
+ return MediaStore.scanFile(mContext.getContentResolver(), outFile);
}
private static final String getExternalDirectoryForType(final int type) {
diff --git a/media/java/android/media/RouteSessionController.java b/media/java/android/media/RouteSessionController.java
new file mode 100644
index 0000000..5ff7218
--- /dev/null
+++ b/media/java/android/media/RouteSessionController.java
@@ -0,0 +1,208 @@
+/*
+ * 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 com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * A class to control media route session in media route provider.
+ * For example, adding/removing/transferring routes to session can be done through this class.
+ * Instances are created by {@link MediaRouter2}.
+ *
+ * TODO: When session is introduced, change Javadoc of all methods/classes by using [@link Session].
+ *
+ * @hide
+ */
+public class RouteSessionController {
+ private final int mSessionId;
+ private final String mCategory;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
+ new CopyOnWriteArrayList<>();
+
+ private volatile boolean mIsReleased;
+
+ /**
+ * @param sessionId the ID of the session.
+ * @param category The category of media routes that the session includes.
+ */
+ RouteSessionController(int sessionId, @NonNull String category) {
+ mSessionId = sessionId;
+ mCategory = category;
+ }
+
+ /**
+ * @return the ID of this controller
+ */
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ /**
+ * @return the category of routes that the session includes.
+ */
+ @NonNull
+ public String getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * @return the list of currently connected routes
+ */
+ @NonNull
+ public List<MediaRoute2Info> getRoutes() {
+ // TODO: Implement this when SessionInfo is introduced.
+ return null;
+ }
+
+ /**
+ * Returns true if the session is released, false otherwise.
+ * If it is released, then all other getters from this instance may return invalid values.
+ * Also, any operations to this instance will be ignored once released.
+ *
+ * @see #release
+ * @see Callback#onReleased
+ */
+ public boolean isReleased() {
+ return mIsReleased;
+ }
+
+ /**
+ * Add routes to the remote session.
+ *
+ * @see #getRoutes()
+ * @see Callback#onSessionInfoChanged
+ */
+ public void addRoutes(List<MediaRoute2Info> routes) {
+ // TODO: Implement this when the actual connection logic is implemented.
+ }
+
+ /**
+ * Remove routes from this session. Media may be stopped on those devices.
+ * Route removal requests that are not currently in {@link #getRoutes()} will be ignored.
+ *
+ * @see #getRoutes()
+ * @see Callback#onSessionInfoChanged
+ */
+ public void removeRoutes(List<MediaRoute2Info> routes) {
+ // TODO: Implement this when the actual connection logic is implemented.
+ }
+
+ /**
+ * Registers a {@link Callback} for monitoring route changes.
+ * If the same callback is registered previously, previous executor will be overwritten with the
+ * new one.
+ */
+ public void registerCallback(Executor executor, Callback callback) {
+ if (mIsReleased) {
+ return;
+ }
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ synchronized (mLock) {
+ CallbackRecord recordWithSameCallback = null;
+ for (CallbackRecord record : mCallbackRecords) {
+ if (callback == record.mCallback) {
+ recordWithSameCallback = record;
+ break;
+ }
+ }
+
+ if (recordWithSameCallback != null) {
+ recordWithSameCallback.mExecutor = executor;
+ } else {
+ mCallbackRecords.add(new CallbackRecord(executor, callback));
+ }
+ }
+ }
+
+ /**
+ * Unregisters a previously registered {@link Callback}.
+ */
+ public void unregisterCallback(Callback callback) {
+ Objects.requireNonNull(callback, "callback must not be null");
+
+ synchronized (mLock) {
+ CallbackRecord recordToRemove = null;
+ for (CallbackRecord record : mCallbackRecords) {
+ if (callback == record.mCallback) {
+ recordToRemove = record;
+ break;
+ }
+ }
+
+ if (recordToRemove != null) {
+ mCallbackRecords.remove(recordToRemove);
+ }
+ }
+ }
+
+ /**
+ * Release this session.
+ * Any operation on this session after calling this method will be ignored.
+ *
+ * @param stopMedia Should the device where the media is played
+ * be stopped after this session is released.
+ */
+ public void release(boolean stopMedia) {
+ mIsReleased = true;
+ mCallbackRecords.clear();
+ // TODO: Use stopMedia variable when the actual connection logic is implemented.
+ }
+
+ /**
+ * Callback class for getting updates on routes and session release.
+ */
+ public static class Callback {
+
+ /**
+ * Called when the session info has changed.
+ * TODO: When SessionInfo is introduced, uncomment below argument.
+ */
+ void onSessionInfoChanged(/* SessionInfo info */) {}
+
+ /**
+ * Called when the session is released. Session can be released by the controller using
+ * {@link #release(boolean)}, or by the {@link MediaRoute2ProviderService} itself.
+ * One can do clean-ups here.
+ *
+ * TODO: When SessionInfo is introduced, change the javadoc of releasing session on
+ * provider side.
+ */
+ void onReleased(int reason, boolean shouldStop) {}
+ }
+
+ private class CallbackRecord {
+ public final Callback mCallback;
+ public Executor mExecutor;
+
+ CallbackRecord(@NonNull Executor executor, @NonNull Callback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+ }
+}
diff --git a/core/java/android/util/StatsEvent.aidl b/media/java/android/media/RouteSessionInfo.aidl
similarity index 72%
rename from core/java/android/util/StatsEvent.aidl
rename to media/java/android/media/RouteSessionInfo.aidl
index deac873..fb5d836 100644
--- a/core/java/android/util/StatsEvent.aidl
+++ b/media/java/android/media/RouteSessionInfo.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2019, The Android Open Source Project
+/*
+ * 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.util;
+package android.media;
-parcelable StatsEvent cpp_header "android/util/StatsEvent.h";
+parcelable RouteSessionInfo;
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
new file mode 100644
index 0000000..53c8eec
--- /dev/null
+++ b/media/java/android/media/RouteSessionInfo.java
@@ -0,0 +1,337 @@
+/*
+ * 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.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;
+ final List<String> mSelectedRoutes;
+ final List<String> mDeselectableRoutes;
+ final List<String> mGroupableRoutes;
+ final List<String> mTransferrableRoutes;
+
+ RouteSessionInfo(@NonNull Builder builder) {
+ Objects.requireNonNull(builder, "builder must not be null.");
+
+ mSessionId = builder.mSessionId;
+ mPackageName = builder.mPackageName;
+ mControlCategory = builder.mControlCategory;
+
+ mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes);
+ mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes);
+ mGroupableRoutes = Collections.unmodifiableList(builder.mGroupableRoutes);
+ mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes);
+ }
+
+ RouteSessionInfo(@NonNull Parcel src) {
+ Objects.requireNonNull(src, "src must not be null.");
+
+ mSessionId = src.readInt();
+ mPackageName = ensureString(src.readString());
+ mControlCategory = ensureString(src.readString());
+
+ mSelectedRoutes = ensureList(src.createStringArrayList());
+ mDeselectableRoutes = ensureList(src.createStringArrayList());
+ mGroupableRoutes = ensureList(src.createStringArrayList());
+ mTransferrableRoutes = ensureList(src.createStringArrayList());
+ }
+
+ 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();
+ }
+
+ /**
+ * 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 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 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 deselectable routes for the session.
+ */
+ @NonNull
+ public List<String> getDeselectableRoutes() {
+ return mDeselectableRoutes;
+ }
+
+ /**
+ * Gets the list of ids of groupable routes for the session.
+ */
+ @NonNull
+ public List<String> getGroupableRoutes() {
+ return mGroupableRoutes;
+ }
+
+ /**
+ * Gets the list of ids of transferrable routes for the session.
+ */
+ @NonNull
+ public List<String> getTransferrableRoutes() {
+ return mTransferrableRoutes;
+ }
+
+ @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.writeStringList(mSelectedRoutes);
+ dest.writeStringList(mDeselectableRoutes);
+ dest.writeStringList(mGroupableRoutes);
+ dest.writeStringList(mTransferrableRoutes);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder()
+ .append("RouteSessionInfo{ ")
+ .append("sessionId=").append(mSessionId)
+ .append(", selectedRoutes={");
+ for (int i = 0; i < mSelectedRoutes.size(); i++) {
+ if (i > 0) result.append(", ");
+ result.append(mSelectedRoutes.get(i));
+ }
+ result.append("}");
+ result.append(" }");
+ return result.toString();
+ }
+
+ /**
+ * Builder class for {@link RouteSessionInfo}.
+ */
+ public static final class Builder {
+ final String mPackageName;
+ final int mSessionId;
+ final String mControlCategory;
+ final List<String> mSelectedRoutes;
+ final List<String> mDeselectableRoutes;
+ final List<String> mGroupableRoutes;
+ final List<String> mTransferrableRoutes;
+
+ 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<>();
+ mDeselectableRoutes = new ArrayList<>();
+ mGroupableRoutes = new ArrayList<>();
+ mTransferrableRoutes = new ArrayList<>();
+ }
+
+ public Builder(RouteSessionInfo sessionInfo) {
+ mSessionId = sessionInfo.mSessionId;
+ mPackageName = sessionInfo.mPackageName;
+ mControlCategory = sessionInfo.mControlCategory;
+
+ mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
+ mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
+ mGroupableRoutes = new ArrayList<>(sessionInfo.mGroupableRoutes);
+ mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
+ }
+
+ /**
+ * 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 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 groupable routes.
+ */
+ @NonNull
+ public Builder clearGroupableRoutes() {
+ mGroupableRoutes.clear();
+ return this;
+ }
+
+ /**
+ * Adds a route to the groupable routes.
+ */
+ @NonNull
+ public Builder addGroupableRoute(@NonNull String routeId) {
+ mGroupableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+ return this;
+ }
+
+ /**
+ * Removes a route from the groupable routes.
+ */
+ @NonNull
+ public Builder removeGroupableRoute(@NonNull String routeId) {
+ mGroupableRoutes.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;
+ }
+
+ /**
+ * Builds a route session info.
+ */
+ @NonNull
+ public RouteSessionInfo build() {
+ return new RouteSessionInfo(this);
+ }
+ }
+}
diff --git a/media/java/android/media/SubtitleController.java b/media/java/android/media/SubtitleController.java
index 1a241af..48657a6c 100644
--- a/media/java/android/media/SubtitleController.java
+++ b/media/java/android/media/SubtitleController.java
@@ -16,10 +16,7 @@
package android.media;
-import java.util.Locale;
-import java.util.Vector;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.MediaPlayer.TrackInfo;
import android.media.SubtitleTrack.RenderingWidget;
@@ -28,6 +25,9 @@
import android.os.Message;
import android.view.accessibility.CaptioningManager;
+import java.util.Locale;
+import java.util.Vector;
+
/**
* The subtitle controller provides the architecture to display subtitles for a
* media source. It allows specifying which tracks to display, on which anchor
diff --git a/media/java/android/media/SubtitleTrack.java b/media/java/android/media/SubtitleTrack.java
index 0705d97..10669f4 100644
--- a/media/java/android/media/SubtitleTrack.java
+++ b/media/java/android/media/SubtitleTrack.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.media.MediaPlayer.TrackInfo;
import android.os.Handler;
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index a315c1e..1f7b5ad 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -24,7 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -33,14 +33,13 @@
import android.graphics.ImageDecoder.ImageInfo;
import android.graphics.ImageDecoder.Source;
import android.graphics.Matrix;
-import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore.ThumbnailConstants;
+import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
@@ -77,15 +76,7 @@
public static final int OPTIONS_RECYCLE_INPUT = 0x2;
private static Size convertKind(int kind) {
- if (kind == ThumbnailConstants.MICRO_KIND) {
- return Point.convert(ThumbnailConstants.MICRO_SIZE);
- } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
- return Point.convert(ThumbnailConstants.FULL_SCREEN_SIZE);
- } else if (kind == ThumbnailConstants.MINI_KIND) {
- return Point.convert(ThumbnailConstants.MINI_SIZE);
- } else {
- throw new IllegalArgumentException("Unsupported kind: " + kind);
- }
+ return MediaStore.Images.Thumbnails.getKindSize(kind);
}
private static class Resizer implements ImageDecoder.OnHeaderDecodedListener {
diff --git a/media/java/android/media/TimedText.java b/media/java/android/media/TimedText.java
index d8cdf9c..120642a 100644
--- a/media/java/android/media/TimedText.java
+++ b/media/java/android/media/TimedText.java
@@ -16,14 +16,15 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
import android.os.Parcel;
import android.util.Log;
-import java.util.HashMap;
-import java.util.Set;
-import java.util.List;
+
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
/**
* Class to hold the timed text's metadata, including:
diff --git a/media/java/android/media/ToneGenerator.java b/media/java/android/media/ToneGenerator.java
index c6d5ba3..cc114a9 100644
--- a/media/java/android/media/ToneGenerator.java
+++ b/media/java/android/media/ToneGenerator.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
diff --git a/media/java/android/media/TtmlRenderer.java b/media/java/android/media/TtmlRenderer.java
index 34154ce..e578264 100644
--- a/media/java/android/media/TtmlRenderer.java
+++ b/media/java/android/media/TtmlRenderer.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -27,6 +27,10 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
@@ -37,10 +41,6 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
-
/** @hide */
public class TtmlRenderer extends SubtitleController.Renderer {
private final Context mContext;
diff --git a/media/java/android/media/VolumeShaper.java b/media/java/android/media/VolumeShaper.java
index 663d564..99dfe1e 100644
--- a/media/java/android/media/VolumeShaper.java
+++ b/media/java/android/media/VolumeShaper.java
@@ -19,13 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.AutoCloseable;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Objects;
diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java
index 36458d7..bc14294 100644
--- a/media/java/android/media/WebVttRenderer.java
+++ b/media/java/android/media/WebVttRenderer.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.text.Layout.Alignment;
import android.text.SpannableStringBuilder;
diff --git a/media/java/android/media/audio/common/AudioChannelMask.aidl b/media/java/android/media/audio/common/AudioChannelMask.aidl
new file mode 100644
index 0000000..b9b08e6
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioChannelMask.aidl
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * A channel mask per se only defines the presence or absence of a channel, not
+ * the order.
+ *
+ * The channel order convention is that channels are interleaved in order from
+ * least significant channel mask bit to most significant channel mask bit,
+ * with unused bits skipped. For example for stereo, LEFT would be first,
+ * followed by RIGHT.
+ * Any exceptions to this convention are noted at the appropriate API.
+ *
+ * AudioChannelMask is an opaque type and its internal layout should not be
+ * assumed as it may change in the future. Instead, always use functions
+ * to examine it.
+ *
+ * These are the current representations:
+ *
+ * REPRESENTATION_POSITION
+ * is a channel mask representation for position assignment. Each low-order
+ * bit corresponds to the spatial position of a transducer (output), or
+ * interpretation of channel (input). The user of a channel mask needs to
+ * know the context of whether it is for output or input. The constants
+ * OUT_* or IN_* apply to the bits portion. It is not permitted for no bits
+ * to be set.
+ *
+ * REPRESENTATION_INDEX
+ * is a channel mask representation for index assignment. Each low-order
+ * bit corresponds to a selected channel. There is no platform
+ * interpretation of the various bits. There is no concept of output or
+ * input. It is not permitted for no bits to be set.
+ *
+ * All other representations are reserved for future use.
+ *
+ * Warning: current representation distinguishes between input and output, but
+ * this will not the be case in future revisions of the platform. Wherever there
+ * is an ambiguity between input and output that is currently resolved by
+ * checking the channel mask, the implementer should look for ways to fix it
+ * with additional information outside of the mask.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioChannelMask {
+ /**
+ * must be 0 for compatibility
+ */
+ REPRESENTATION_POSITION = 0,
+ /**
+ * 1 is reserved for future use
+ */
+ REPRESENTATION_INDEX = 2,
+ /**
+ * 3 is reserved for future use
+ *
+ *
+ * These can be a complete value of AudioChannelMask
+ */
+ NONE = 0x0,
+ INVALID = 0xC0000000,
+ /**
+ * These can be the bits portion of an AudioChannelMask
+ * with representation REPRESENTATION_POSITION.
+ *
+ *
+ * output channels
+ */
+ OUT_FRONT_LEFT = 0x1,
+ OUT_FRONT_RIGHT = 0x2,
+ OUT_FRONT_CENTER = 0x4,
+ OUT_LOW_FREQUENCY = 0x8,
+ OUT_BACK_LEFT = 0x10,
+ OUT_BACK_RIGHT = 0x20,
+ OUT_FRONT_LEFT_OF_CENTER = 0x40,
+ OUT_FRONT_RIGHT_OF_CENTER = 0x80,
+ OUT_BACK_CENTER = 0x100,
+ OUT_SIDE_LEFT = 0x200,
+ OUT_SIDE_RIGHT = 0x400,
+ OUT_TOP_CENTER = 0x800,
+ OUT_TOP_FRONT_LEFT = 0x1000,
+ OUT_TOP_FRONT_CENTER = 0x2000,
+ OUT_TOP_FRONT_RIGHT = 0x4000,
+ OUT_TOP_BACK_LEFT = 0x8000,
+ OUT_TOP_BACK_CENTER = 0x10000,
+ OUT_TOP_BACK_RIGHT = 0x20000,
+ OUT_TOP_SIDE_LEFT = 0x40000,
+ OUT_TOP_SIDE_RIGHT = 0x80000,
+ /**
+ * Haptic channel characteristics are specific to a device and
+ * only used to play device specific resources (eg: ringtones).
+ * The HAL can freely map A and B to haptic controllers, the
+ * framework shall not interpret those values and forward them
+ * from the device audio assets.
+ */
+ OUT_HAPTIC_A = 0x20000000,
+ OUT_HAPTIC_B = 0x10000000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// OUT_MONO = OUT_FRONT_LEFT,
+// OUT_STEREO = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT),
+// OUT_2POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_2POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_2POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_3POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_3POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_QUAD = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+// OUT_QUAD_BACK = OUT_QUAD,
+// /**
+// * like OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_*
+// */
+// OUT_QUAD_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_SURROUND = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_BACK_CENTER),
+// OUT_PENTA = (OUT_QUAD | OUT_FRONT_CENTER),
+// OUT_5POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+// OUT_5POINT1_BACK = OUT_5POINT1,
+// /**
+// * like OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_*
+// */
+// OUT_5POINT1_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_5POINT1POINT2 = (OUT_5POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_5POINT1POINT4 = (OUT_5POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+// OUT_6POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_BACK_CENTER),
+// /**
+// * matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND
+// */
+// OUT_7POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_7POINT1POINT2 = (OUT_7POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_7POINT1POINT4 = (OUT_7POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+// OUT_MONO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_HAPTIC_A),
+// OUT_STEREO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A),
+// OUT_HAPTIC_AB = (OUT_HAPTIC_A | OUT_HAPTIC_B),
+// OUT_MONO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+// OUT_STEREO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+ /**
+ * These are bits only, not complete values
+ *
+ *
+ * input channels
+ */
+ IN_LEFT = 0x4,
+ IN_RIGHT = 0x8,
+ IN_FRONT = 0x10,
+ IN_BACK = 0x20,
+ IN_LEFT_PROCESSED = 0x40,
+ IN_RIGHT_PROCESSED = 0x80,
+ IN_FRONT_PROCESSED = 0x100,
+ IN_BACK_PROCESSED = 0x200,
+ IN_PRESSURE = 0x400,
+ IN_X_AXIS = 0x800,
+ IN_Y_AXIS = 0x1000,
+ IN_Z_AXIS = 0x2000,
+ IN_BACK_LEFT = 0x10000,
+ IN_BACK_RIGHT = 0x20000,
+ IN_CENTER = 0x40000,
+ IN_LOW_FREQUENCY = 0x100000,
+ IN_TOP_LEFT = 0x200000,
+ IN_TOP_RIGHT = 0x400000,
+ IN_VOICE_UPLINK = 0x4000,
+ IN_VOICE_DNLINK = 0x8000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// IN_MONO = IN_FRONT,
+// IN_STEREO = (IN_LEFT | IN_RIGHT),
+// IN_FRONT_BACK = (IN_FRONT | IN_BACK),
+// IN_6 = (IN_LEFT | IN_RIGHT | IN_FRONT | IN_BACK | IN_LEFT_PROCESSED | IN_RIGHT_PROCESSED),
+// IN_2POINT0POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+// IN_2POINT1POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+// IN_3POINT0POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+// IN_3POINT1POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+// IN_5POINT1 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_BACK_LEFT | IN_BACK_RIGHT | IN_LOW_FREQUENCY),
+// IN_VOICE_UPLINK_MONO = (IN_VOICE_UPLINK | IN_MONO),
+// IN_VOICE_DNLINK_MONO = (IN_VOICE_DNLINK | IN_MONO),
+// IN_VOICE_CALL_MONO = (IN_VOICE_UPLINK_MONO | IN_VOICE_DNLINK_MONO),
+// COUNT_MAX = 30,
+// INDEX_HDR = REPRESENTATION_INDEX << COUNT_MAX,
+// INDEX_MASK_1 = INDEX_HDR | ((1 << 1) - 1),
+// INDEX_MASK_2 = INDEX_HDR | ((1 << 2) - 1),
+// INDEX_MASK_3 = INDEX_HDR | ((1 << 3) - 1),
+// INDEX_MASK_4 = INDEX_HDR | ((1 << 4) - 1),
+// INDEX_MASK_5 = INDEX_HDR | ((1 << 5) - 1),
+// INDEX_MASK_6 = INDEX_HDR | ((1 << 6) - 1),
+// INDEX_MASK_7 = INDEX_HDR | ((1 << 7) - 1),
+// INDEX_MASK_8 = INDEX_HDR | ((1 << 8) - 1),
+// INDEX_MASK_9 = INDEX_HDR | ((1 << 9) - 1),
+// INDEX_MASK_10 = INDEX_HDR | ((1 << 10) - 1),
+// INDEX_MASK_11 = INDEX_HDR | ((1 << 11) - 1),
+// INDEX_MASK_12 = INDEX_HDR | ((1 << 12) - 1),
+// INDEX_MASK_13 = INDEX_HDR | ((1 << 13) - 1),
+// INDEX_MASK_14 = INDEX_HDR | ((1 << 14) - 1),
+// INDEX_MASK_15 = INDEX_HDR | ((1 << 15) - 1),
+// INDEX_MASK_16 = INDEX_HDR | ((1 << 16) - 1),
+// INDEX_MASK_17 = INDEX_HDR | ((1 << 17) - 1),
+// INDEX_MASK_18 = INDEX_HDR | ((1 << 18) - 1),
+// INDEX_MASK_19 = INDEX_HDR | ((1 << 19) - 1),
+// INDEX_MASK_20 = INDEX_HDR | ((1 << 20) - 1),
+// INDEX_MASK_21 = INDEX_HDR | ((1 << 21) - 1),
+// INDEX_MASK_22 = INDEX_HDR | ((1 << 22) - 1),
+// INDEX_MASK_23 = INDEX_HDR | ((1 << 23) - 1),
+// INDEX_MASK_24 = INDEX_HDR | ((1 << 24) - 1),
+}
diff --git a/media/java/android/media/audio/common/AudioConfig.aidl b/media/java/android/media/audio/common/AudioConfig.aidl
new file mode 100644
index 0000000..50dd796
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioConfig.aidl
@@ -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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioOffloadInfo;
+
+/**
+ * Commonly used audio stream configuration parameters.
+ *
+ * {@hide}
+ */
+parcelable AudioConfig {
+ int sampleRateHz;
+ int channelMask;
+ AudioFormat format;
+ AudioOffloadInfo offloadInfo;
+ long frameCount;
+}
diff --git a/media/java/android/media/audio/common/AudioFormat.aidl b/media/java/android/media/audio/common/AudioFormat.aidl
new file mode 100644
index 0000000..aadc8e2
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioFormat.aidl
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio format is a 32-bit word that consists of:
+ * main format field (upper 8 bits)
+ * sub format field (lower 24 bits).
+ *
+ * The main format indicates the main codec type. The sub format field indicates
+ * options and parameters for each format. The sub format is mainly used for
+ * record to indicate for instance the requested bitrate or profile. It can
+ * also be used for certain formats to give informations not present in the
+ * encoded audio stream (e.g. octet alignement for AMR).
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioFormat {
+ INVALID = 0xFFFFFFFF,
+ DEFAULT = 0,
+ PCM = 0x00000000,
+ MP3 = 0x01000000,
+ AMR_NB = 0x02000000,
+ AMR_WB = 0x03000000,
+ AAC = 0x04000000,
+ /**
+ * Deprecated, Use AAC_HE_V1
+ */
+ HE_AAC_V1 = 0x05000000,
+ /**
+ * Deprecated, Use AAC_HE_V2
+ */
+ HE_AAC_V2 = 0x06000000,
+ VORBIS = 0x07000000,
+ OPUS = 0x08000000,
+ AC3 = 0x09000000,
+ E_AC3 = 0x0A000000,
+ DTS = 0x0B000000,
+ DTS_HD = 0x0C000000,
+ /**
+ * IEC61937 is encoded audio wrapped in 16-bit PCM.
+ */
+ IEC61937 = 0x0D000000,
+ DOLBY_TRUEHD = 0x0E000000,
+ EVRC = 0x10000000,
+ EVRCB = 0x11000000,
+ EVRCWB = 0x12000000,
+ EVRCNW = 0x13000000,
+ AAC_ADIF = 0x14000000,
+ WMA = 0x15000000,
+ WMA_PRO = 0x16000000,
+ AMR_WB_PLUS = 0x17000000,
+ MP2 = 0x18000000,
+ QCELP = 0x19000000,
+ DSD = 0x1A000000,
+ FLAC = 0x1B000000,
+ ALAC = 0x1C000000,
+ APE = 0x1D000000,
+ AAC_ADTS = 0x1E000000,
+ SBC = 0x1F000000,
+ APTX = 0x20000000,
+ APTX_HD = 0x21000000,
+ AC4 = 0x22000000,
+ LDAC = 0x23000000,
+ /**
+ * Dolby Metadata-enhanced Audio Transmission
+ */
+ MAT = 0x24000000,
+ AAC_LATM = 0x25000000,
+ CELT = 0x26000000,
+ APTX_ADAPTIVE = 0x27000000,
+ LHDC = 0x28000000,
+ LHDC_LL = 0x29000000,
+ APTX_TWSP = 0x2A000000,
+ /**
+ * Deprecated
+ */
+ MAIN_MASK = 0xFF000000,
+ SUB_MASK = 0x00FFFFFF,
+ /**
+ * Subformats
+ */
+ PCM_SUB_16_BIT = 0x1,
+ PCM_SUB_8_BIT = 0x2,
+ PCM_SUB_32_BIT = 0x3,
+ PCM_SUB_8_24_BIT = 0x4,
+ PCM_SUB_FLOAT = 0x5,
+ PCM_SUB_24_BIT_PACKED = 0x6,
+ MP3_SUB_NONE = 0x0,
+ AMR_SUB_NONE = 0x0,
+ AAC_SUB_MAIN = 0x1,
+ AAC_SUB_LC = 0x2,
+ AAC_SUB_SSR = 0x4,
+ AAC_SUB_LTP = 0x8,
+ AAC_SUB_HE_V1 = 0x10,
+ AAC_SUB_SCALABLE = 0x20,
+ AAC_SUB_ERLC = 0x40,
+ AAC_SUB_LD = 0x80,
+ AAC_SUB_HE_V2 = 0x100,
+ AAC_SUB_ELD = 0x200,
+ AAC_SUB_XHE = 0x300,
+ VORBIS_SUB_NONE = 0x0,
+ E_AC3_SUB_JOC = 0x1,
+ MAT_SUB_1_0 = 0x1,
+ MAT_SUB_2_0 = 0x2,
+ MAT_SUB_2_1 = 0x3,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// /**
+// * Aliases
+// *
+// *
+// * note != AudioFormat.ENCODING_PCM_16BIT
+// */
+// PCM_16_BIT = (PCM | PCM_SUB_16_BIT),
+// /**
+// * note != AudioFormat.ENCODING_PCM_8BIT
+// */
+// PCM_8_BIT = (PCM | PCM_SUB_8_BIT),
+// PCM_32_BIT = (PCM | PCM_SUB_32_BIT),
+// PCM_8_24_BIT = (PCM | PCM_SUB_8_24_BIT),
+// PCM_FLOAT = (PCM | PCM_SUB_FLOAT),
+// PCM_24_BIT_PACKED = (PCM | PCM_SUB_24_BIT_PACKED),
+// AAC_MAIN = (AAC | AAC_SUB_MAIN),
+// AAC_LC = (AAC | AAC_SUB_LC),
+// AAC_SSR = (AAC | AAC_SUB_SSR),
+// AAC_LTP = (AAC | AAC_SUB_LTP),
+// AAC_HE_V1 = (AAC | AAC_SUB_HE_V1),
+// AAC_SCALABLE = (AAC | AAC_SUB_SCALABLE),
+// AAC_ERLC = (AAC | AAC_SUB_ERLC),
+// AAC_LD = (AAC | AAC_SUB_LD),
+// AAC_HE_V2 = (AAC | AAC_SUB_HE_V2),
+// AAC_ELD = (AAC | AAC_SUB_ELD),
+// AAC_XHE = (AAC | AAC_SUB_XHE),
+// AAC_ADTS_MAIN = (AAC_ADTS | AAC_SUB_MAIN),
+// AAC_ADTS_LC = (AAC_ADTS | AAC_SUB_LC),
+// AAC_ADTS_SSR = (AAC_ADTS | AAC_SUB_SSR),
+// AAC_ADTS_LTP = (AAC_ADTS | AAC_SUB_LTP),
+// AAC_ADTS_HE_V1 = (AAC_ADTS | AAC_SUB_HE_V1),
+// AAC_ADTS_SCALABLE = (AAC_ADTS | AAC_SUB_SCALABLE),
+// AAC_ADTS_ERLC = (AAC_ADTS | AAC_SUB_ERLC),
+// AAC_ADTS_LD = (AAC_ADTS | AAC_SUB_LD),
+// AAC_ADTS_HE_V2 = (AAC_ADTS | AAC_SUB_HE_V2),
+// AAC_ADTS_ELD = (AAC_ADTS | AAC_SUB_ELD),
+// AAC_ADTS_XHE = (AAC_ADTS | AAC_SUB_XHE),
+// E_AC3_JOC = (E_AC3 | E_AC3_SUB_JOC),
+// MAT_1_0 = (MAT | MAT_SUB_1_0),
+// MAT_2_0 = (MAT | MAT_SUB_2_0),
+// MAT_2_1 = (MAT | MAT_SUB_2_1),
+// AAC_LATM_LC = (AAC_LATM | AAC_SUB_LC),
+// AAC_LATM_HE_V1 = (AAC_LATM | AAC_SUB_HE_V1),
+// AAC_LATM_HE_V2 = (AAC_LATM | AAC_SUB_HE_V2),
+}
diff --git a/media/java/android/media/audio/common/AudioOffloadInfo.aidl b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
new file mode 100644
index 0000000..ec10d71
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioStreamType;
+import android.media.audio.common.AudioUsage;
+
+/**
+ * Additional information about the stream passed to hardware decoders.
+ *
+ * {@hide}
+ */
+parcelable AudioOffloadInfo {
+ int sampleRateHz;
+ int channelMask;
+ AudioFormat format;
+ AudioStreamType streamType;
+ int bitRatePerSecond;
+ long durationMicroseconds;
+ boolean hasVideo;
+ boolean isStreaming;
+ int bitWidth;
+ int bufferSize;
+ AudioUsage usage;
+}
+
diff --git a/media/java/android/media/audio/common/AudioStreamType.aidl b/media/java/android/media/audio/common/AudioStreamType.aidl
new file mode 100644
index 0000000..c545667
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioStreamType.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio streams
+ *
+ * Audio stream type describing the intended use case of a stream.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioStreamType {
+ DEFAULT = -1,
+ MIN = 0,
+ VOICE_CALL = 0,
+ SYSTEM = 1,
+ RING = 2,
+ MUSIC = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ BLUETOOTH_SCO = 6,
+ ENFORCED_AUDIBLE = 7,
+ DTMF = 8,
+ TTS = 9,
+ ACCESSIBILITY = 10,
+}
diff --git a/media/java/android/media/audio/common/AudioUsage.aidl b/media/java/android/media/audio/common/AudioUsage.aidl
new file mode 100644
index 0000000..ef34816
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioUsage.aidl
@@ -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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioUsage {
+ UNKNOWN = 0,
+ MEDIA = 1,
+ VOICE_COMMUNICATION = 2,
+ VOICE_COMMUNICATION_SIGNALLING = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ NOTIFICATION_TELEPHONY_RINGTONE = 6,
+ ASSISTANCE_ACCESSIBILITY = 11,
+ ASSISTANCE_NAVIGATION_GUIDANCE = 12,
+ ASSISTANCE_SONIFICATION = 13,
+ GAME = 14,
+ VIRTUAL_SOURCE = 15,
+ ASSISTANT = 16,
+}
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 5b4bbce..4a40c62 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -19,8 +19,8 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index 89a509f..392e8fe 100644
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -16,8 +16,8 @@
package android.media.audiofx;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index a82c78f..dd9877a 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.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.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioSystem;
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index c4afd95..8c204d2 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.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.media.AudioAttributes;
import android.os.Parcel;
import android.util.Log;
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 2799d46..612f83a 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.media.AudioAttributes;
import android.media.AudioSystem;
import android.media.MediaRecorder;
@@ -82,6 +83,18 @@
/**
* @hide
+ * Create an invalid AudioProductStrategy instance for testing
+ * @param id the ID for the invalid strategy, always use a different one than in use
+ * @return an invalid instance that cannot successfully be used for volume groups or routing
+ */
+ @TestApi
+ @SystemApi
+ public static @NonNull AudioProductStrategy createInvalidAudioProductStrategy(int id) {
+ return new AudioProductStrategy("dummy strategy", id, new AudioAttributesGroup[0]);
+ }
+
+ /**
+ * @hide
* @param streamType to match against AudioProductStrategy
* @return the AudioAttributes for the first strategy found with the associated stream type
* If no match is found, returns AudioAttributes with unknown content_type and usage
@@ -222,6 +235,7 @@
* @return true if the {@link AudioProductStrategy} supports the given {@link AudioAttributes},
* false otherwise.
*/
+ @SystemApi
public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) {
Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
diff --git a/media/java/android/media/session/ICallback.aidl b/media/java/android/media/session/ICallback.aidl
deleted file mode 100644
index 322bffa..0000000
--- a/media/java/android/media/session/ICallback.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/* Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.session;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.media.session.MediaSession;
-import android.view.KeyEvent;
-
-/**
- * @hide
- */
-oneway interface ICallback {
- void onMediaKeyEventDispatchedToMediaSession(in KeyEvent event,
- in MediaSession.Token sessionToken);
- void onMediaKeyEventDispatchedToMediaButtonReceiver(in KeyEvent event,
- in ComponentName mediaButtonReceiver);
-
- void onAddressedPlayerChangedToMediaSession(in MediaSession.Token sessionToken);
- void onAddressedPlayerChangedToMediaButtonReceiver(in ComponentName mediaButtonReceiver);
-}
-
diff --git a/core/java/android/service/sms/IFinancialSmsService.aidl b/media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl
similarity index 63%
rename from core/java/android/service/sms/IFinancialSmsService.aidl
rename to media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl
index caabe58..90d9134 100644
--- a/core/java/android/service/sms/IFinancialSmsService.aidl
+++ b/media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package android.service.sms;
+package android.media.session;
-import android.os.Bundle;
-import android.os.RemoteCallback;
+import android.media.session.MediaSession;
+import android.view.KeyEvent;
/**
- * Service used by financial apps to read sms messages.
- *
* @hide
*/
-oneway interface IFinancialSmsService
-{
- void getSmsMessages(in RemoteCallback callback, in Bundle params);
-}
\ No newline at end of file
+oneway interface IOnMediaKeyEventDispatchedListener {
+ void onMediaKeyEventDispatched(in KeyEvent event, in String packageName,
+ in MediaSession.Token sessionToken);
+}
diff --git a/core/java/android/net/ITetheringEventCallback.aidl b/media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl
similarity index 65%
copy from core/java/android/net/ITetheringEventCallback.aidl
copy to media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl
index d502088..9566e75 100644
--- a/core/java/android/net/ITetheringEventCallback.aidl
+++ b/media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package android.net;
+package android.media.session;
-import android.net.Network;
+import android.media.session.MediaSession;
/**
- * Callback class for receiving tethering changed events
* @hide
*/
-oneway interface ITetheringEventCallback
-{
- void onUpstreamChanged(in Network network);
+oneway interface IOnMediaKeyEventSessionChangedListener {
+ void onMediaKeyEventSessionChanged(in String packageName,
+ in MediaSession.Token mediaKeyEventSessionToken);
}
+
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 01e6ed5..c8502a5 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -20,7 +20,8 @@
import android.media.IRemoteVolumeController;
import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
-import android.media.session.ICallback;
+import android.media.session.IOnMediaKeyEventDispatchedListener;
+import android.media.session.IOnMediaKeyEventSessionChangedListener;
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
@@ -62,8 +63,12 @@
// For PhoneWindowManager to precheck media keys
boolean isGlobalPriorityActive();
- void registerCallback(in ICallback callback);
- void unregisterCallback(in ICallback callback);
+ void addOnMediaKeyEventDispatchedListener(in IOnMediaKeyEventDispatchedListener listener);
+ void removeOnMediaKeyEventDispatchedListener(in IOnMediaKeyEventDispatchedListener listener);
+ void addOnMediaKeyEventSessionChangedListener(
+ in IOnMediaKeyEventSessionChangedListener listener);
+ void removeOnMediaKeyEventSessionChangedListener(
+ in IOnMediaKeyEventSessionChangedListener listener);
void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
void setOnMediaKeyListener(in IOnMediaKeyListener listener);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 1fc4f7d..646fc06 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
@@ -443,8 +443,8 @@
* Get the session's tag for debugging purposes.
*
* @return The session's tag.
- * @hide
*/
+ @NonNull
public String getTag() {
if (mTag == null) {
try {
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index e11715f..5db4e8c 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -19,9 +19,9 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 123c4f7..0d506f0 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -16,9 +16,9 @@
package android.media.session;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -26,7 +26,6 @@
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
-import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 92fb31b..aff7257 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -22,7 +22,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.content.pm.ParceledListSlice;
@@ -32,7 +32,6 @@
import android.media.Session2Token;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -78,29 +77,33 @@
*/
public static final int RESULT_MEDIA_KEY_HANDLED = 1;
private final ISessionManager mService;
+ private final OnMediaKeyEventDispatchedListenerStub mOnMediaKeyEventDispatchedListenerStub =
+ new OnMediaKeyEventDispatchedListenerStub();
+ private final OnMediaKeyEventSessionChangedListenerStub
+ mOnMediaKeyEventSessionChangedListenerStub =
+ new OnMediaKeyEventSessionChangedListenerStub();
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
- = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
+ private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners =
+ new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
@GuardedBy("mLock")
private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper>
mSession2TokensListeners = new ArrayMap<>();
@GuardedBy("mLock")
- private final CallbackStub mCbStub = new CallbackStub();
+ private final Map<OnMediaKeyEventDispatchedListener, Executor>
+ mOnMediaKeyEventDispatchedListeners = new HashMap<>();
@GuardedBy("mLock")
- private final Map<Callback, Executor> mCallbacks = new HashMap<>();
+ private final Map<OnMediaKeyEventSessionChangedListener, Executor>
+ mMediaKeyEventSessionChangedCallbacks = new HashMap<>();
@GuardedBy("mLock")
- private MediaSession.Token mCurMediaButtonSession;
+ private String mCurMediaKeyEventSessionPackage;
@GuardedBy("mLock")
- private ComponentName mCurMediaButtonReceiver;
+ private MediaSession.Token mCurMediaKeyEventSession;
private Context mContext;
private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
private OnMediaKeyListenerImpl mOnMediaKeyListener;
- // TODO: Remove mLegacyCallback once Bluetooth app stop calling setCallback() method.
- @GuardedBy("mLock")
- private Callback mLegacyCallback;
/**
* @hide
@@ -756,89 +759,118 @@
}
/**
- * Set a {@link Callback}.
- *
- * <p>System can only have a single callback, and the callback can only be set by
- * Bluetooth service process.
- *
- * @param callback A {@link Callback}. {@code null} to reset.
- * @param handler The handler on which the callback should be invoked, or {@code null}
- * if the callback should be invoked on the calling thread's looper.
- * @hide
- */
- // TODO: Remove this method once Bluetooth app stop calling it.
- public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
- if (handler == null) {
- handler = new Handler();
- }
- synchronized (mLock) {
- if (mLegacyCallback != null) {
- unregisterCallback(mLegacyCallback);
- }
- mLegacyCallback = callback;
- if (callback != null) {
- registerCallback(new HandlerExecutor(handler), callback);
- }
- }
- }
-
- /**
- * Register a {@link Callback}.
+ * Add a {@link OnMediaKeyEventDispatchedListener}.
*
* @param executor The executor on which the callback should be invoked
- * @param callback A {@link Callback}.
+ * @param listener A {@link OnMediaKeyEventDispatchedListener}.
* @hide
*/
@SystemApi
@RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
- public void registerCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull Callback callback) {
+ public void addOnMediaKeyEventDispatchedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnMediaKeyEventDispatchedListener listener) {
if (executor == null) {
throw new NullPointerException("executor shouldn't be null");
}
- if (callback == null) {
- throw new NullPointerException("callback shouldn't be null");
+ if (listener == null) {
+ throw new NullPointerException("listener shouldn't be null");
}
synchronized (mLock) {
try {
- mCallbacks.put(callback, executor);
- if (mCurMediaButtonSession != null) {
- executor.execute(
- () -> callback.onAddressedPlayerChanged(mCurMediaButtonSession));
- } else if (mCurMediaButtonReceiver != null) {
- executor.execute(
- () -> callback.onAddressedPlayerChanged(mCurMediaButtonReceiver));
- }
-
- if (mCallbacks.size() == 1) {
- mService.registerCallback(mCbStub);
+ mOnMediaKeyEventDispatchedListeners.put(listener, executor);
+ if (mOnMediaKeyEventDispatchedListeners.size() == 1) {
+ mService.addOnMediaKeyEventDispatchedListener(
+ mOnMediaKeyEventDispatchedListenerStub);
}
} catch (RemoteException e) {
- Log.e(TAG, "Failed to set media key callback", e);
+ Log.e(TAG, "Failed to set media key listener", e);
}
}
}
/**
- * Unregister a {@link Callback}.
+ * Remove a {@link OnMediaKeyEventDispatchedListener}.
*
- * @param callback A {@link Callback}.
+ * @param listener A {@link OnMediaKeyEventDispatchedListener}.
* @hide
*/
@SystemApi
@RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
- public void unregisterCallback(@NonNull Callback callback) {
- if (callback == null) {
- throw new NullPointerException("callback shouldn't be null");
+ public void removeOnMediaKeyEventDispatchedListener(
+ @NonNull OnMediaKeyEventDispatchedListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener shouldn't be null");
}
synchronized (mLock) {
try {
- mCallbacks.remove(callback);
- if (mCallbacks.size() == 0) {
- mService.unregisterCallback(mCbStub);
+ mOnMediaKeyEventDispatchedListeners.remove(listener);
+ if (mOnMediaKeyEventDispatchedListeners.size() == 0) {
+ mService.removeOnMediaKeyEventDispatchedListener(
+ mOnMediaKeyEventDispatchedListenerStub);
}
} catch (RemoteException e) {
- Log.e(TAG, "Failed to set media key callback", e);
+ Log.e(TAG, "Failed to set media key event dispatched listener", e);
+ }
+ }
+ }
+
+ /**
+ * Add a {@link OnMediaKeyEventDispatchedListener}.
+ *
+ * @param executor The executor on which the callback should be invoked
+ * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void addOnMediaKeyEventSessionChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnMediaKeyEventSessionChangedListener listener) {
+ if (executor == null) {
+ throw new NullPointerException("executor shouldn't be null");
+ }
+ if (listener == null) {
+ throw new NullPointerException("listener shouldn't be null");
+ }
+ synchronized (mLock) {
+ try {
+ mMediaKeyEventSessionChangedCallbacks.put(listener, executor);
+ executor.execute(
+ () -> listener.onMediaKeyEventSessionChanged(
+ mCurMediaKeyEventSessionPackage, mCurMediaKeyEventSession));
+ if (mMediaKeyEventSessionChangedCallbacks.size() == 1) {
+ mService.addOnMediaKeyEventSessionChangedListener(
+ mOnMediaKeyEventSessionChangedListenerStub);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set media key listener", e);
+ }
+ }
+ }
+
+ /**
+ * Remove a {@link OnMediaKeyEventSessionChangedListener}.
+ *
+ * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ public void removeOnMediaKeyEventSessionChangedListener(
+ @NonNull OnMediaKeyEventSessionChangedListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener shouldn't be null");
+ }
+ synchronized (mLock) {
+ try {
+ mMediaKeyEventSessionChangedCallbacks.remove(listener);
+ if (mMediaKeyEventSessionChangedCallbacks.size() == 0) {
+ mService.removeOnMediaKeyEventSessionChangedListener(
+ mOnMediaKeyEventSessionChangedListenerStub);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set media key listener", e);
}
}
}
@@ -900,54 +932,46 @@
}
/**
- * Callbacks for the media session service.
- *
- * <p>Called when a media key event is dispatched or the addressed player is changed.
- * The addressed player is either the media session or the media button receiver that will
- * receive media key events.
+ * Listener to receive when the media session service
* @hide
*/
@SystemApi
- public static abstract class Callback {
+ public interface OnMediaKeyEventDispatchedListener {
/**
- * Called when a media key event is dispatched to the media session
- * through the media session service.
+ * 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
*
* @param event Dispatched media key event.
- * @param sessionToken The media session's token.
+ * @param packageName Package
+ * @param sessionToken The media session's token. Can be {@code null}.
*/
- public abstract void onMediaKeyEventDispatched(KeyEvent event,
- MediaSession.Token sessionToken);
+ default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
+ @NonNull MediaSession.Token sessionToken) { }
+ }
+ /**
+ * Listener to receive changes in the media key event session, which would receive the media key
+ * event unless specified.
+ * @hide
+ */
+ @SystemApi
+ public interface OnMediaKeyEventSessionChangedListener {
/**
- * Called when a media key event is dispatched to the media button receiver
- * through the media session service.
- * <p>MediaSessionService may broadcast key events to the media button receiver
- * when reviving playback after the media session is released.
+ * Called when the media key session is changed to the given media session. The key event
+ * session is the media session which would receive key event by default, unless the caller
+ * has specified the target.
+ * <p>
+ * The session token can be {@link null} if the media button session is unset. In that case,
+ * framework would dispatch to the last sessions's media button receiver.
*
- * @param event Dispatched media key event.
- * @param mediaButtonReceiver The media button receiver.
+ * @param packageName The package name who would receive the media key event. Can be empty.
+ * @param sessionToken The media session's token. Can be {@code null.}
*/
- public abstract void onMediaKeyEventDispatched(KeyEvent event,
- ComponentName mediaButtonReceiver);
-
- /**
- * Called when the addressed player is changed to a media session.
- * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
- * {@link #registerCallback} if the addressed player exists.
- *
- * @param sessionToken The media session's token.
- */
- public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken);
-
- /**
- * Called when the addressed player is changed to the media button receiver.
- * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
- * {@link #registerCallback} if the addressed player exists.
- *
- * @param mediaButtonReceiver The media button receiver.
- */
- public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver);
+ default void onMediaKeyEventSessionChanged(@NonNull String packageName,
+ @Nullable MediaSession.Token sessionToken) { }
}
/**
@@ -1149,50 +1173,35 @@
}
}
- private final class CallbackStub extends ICallback.Stub {
+ private final class OnMediaKeyEventDispatchedListenerStub
+ extends IOnMediaKeyEventDispatchedListener.Stub {
@Override
- public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
+ public void onMediaKeyEventDispatched(KeyEvent event, String packageName,
MediaSession.Token sessionToken) {
synchronized (mLock) {
- for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
+ for (Map.Entry<OnMediaKeyEventDispatchedListener, Executor> e
+ : mOnMediaKeyEventDispatchedListeners.entrySet()) {
e.getValue().execute(
- () -> e.getKey().onMediaKeyEventDispatched(event, sessionToken));
+ () -> e.getKey().onMediaKeyEventDispatched(event, packageName,
+ sessionToken));
}
}
}
+ }
+ private final class OnMediaKeyEventSessionChangedListenerStub
+ extends IOnMediaKeyEventSessionChangedListener.Stub {
@Override
- public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
- ComponentName mediaButtonReceiver) {
+ public void onMediaKeyEventSessionChanged(String packageName,
+ MediaSession.Token sessionToken) {
synchronized (mLock) {
- for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
- e.getValue().execute(
- () -> e.getKey().onMediaKeyEventDispatched(event, mediaButtonReceiver));
- }
- }
- }
-
- @Override
- public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
- synchronized (mLock) {
- mCurMediaButtonSession = sessionToken;
- mCurMediaButtonReceiver = null;
- for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
- e.getValue().execute(() -> e.getKey().onAddressedPlayerChanged(sessionToken));
- }
- }
- }
-
- @Override
- public void onAddressedPlayerChangedToMediaButtonReceiver(
- ComponentName mediaButtonReceiver) {
- synchronized (mLock) {
- mCurMediaButtonSession = null;
- mCurMediaButtonReceiver = mediaButtonReceiver;
- for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
- e.getValue().execute(() -> e.getKey().onAddressedPlayerChanged(
- mediaButtonReceiver));
+ mCurMediaKeyEventSessionPackage = packageName;
+ mCurMediaKeyEventSession = sessionToken;
+ for (Map.Entry<OnMediaKeyEventSessionChangedListener, Executor> e
+ : mMediaKeyEventSessionChangedCallbacks.entrySet()) {
+ e.getValue().execute(() -> e.getKey().onMediaKeyEventSessionChanged(packageName,
+ sessionToken));
}
}
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index dc400ad..61b3e76 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -26,9 +26,11 @@
import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
import android.os.Bundle;
@@ -67,7 +69,7 @@
/**
* @hide
*/
- public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService ) {
+ public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService) {
if (DBG) {
Slog.i(TAG, "SoundTriggerManager created.");
}
@@ -89,14 +91,22 @@
}
/**
- * Returns the sound trigger model represented by the given UUID. An instance of {@link Model}
- * is returned.
+ * Get {@link SoundTriggerManager.Model} which is registered with the passed UUID
+ *
+ * @param soundModelId UUID associated with a loaded model
+ * @return {@link SoundTriggerManager.Model} associated with UUID soundModelId
*/
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ @Nullable
public Model getModel(UUID soundModelId) {
try {
- return new Model(mSoundTriggerService.getSoundModel(
- new ParcelUuid(soundModelId)));
+ GenericSoundModel model =
+ mSoundTriggerService.getSoundModel(new ParcelUuid(soundModelId));
+ if (model == null) {
+ return null;
+ }
+
+ return new Model(model);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -399,4 +409,80 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * 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 SoundTriggerManager#queryParameter} should be checked first before calling this
+ * method.
+ *
+ * @param soundModelId UUID of model to apply the parameter value to.
+ * @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
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ public int setParameter(@Nullable UUID soundModelId,
+ @ModelParams int modelParam, int value)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ try {
+ return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
+ value);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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 SoundTriggerManager#queryParameter} should be checked first before
+ * calling this method. Otherwise, an exception can be thrown.
+ *
+ * @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 {
+ try {
+ return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling {@link SoundTriggerManager#setParameter} or
+ * {@link SoundTriggerManager#getParameter}.
+ *
+ * @param soundModelId handle of model to get parameter
+ * @param modelParam {@link ModelParams}
+ * @return supported range of parameter, null if not supported
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ @Nullable
+ public ModelParamRange queryParameter(@Nullable UUID soundModelId,
+ @ModelParams int modelParam) {
+ try {
+ return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId),
+ modelParam);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
new file mode 100644
index 0000000..3dbc705
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
@@ -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.media.soundtrigger_middleware;
+
+/**
+ * A recognition confidence level.
+ * This type is used to represent either a threshold or an actual detection confidence level.
+ *
+ * {@hide}
+ */
+parcelable ConfidenceLevel {
+ /** user ID. */
+ int userId;
+ /**
+ * Confidence level in percent (0 - 100).
+ * <ul>
+ * <li>Min level for recognition configuration
+ * <li>Detected level for recognition event.
+ * </ul>
+ */
+ int levelPercent;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
new file mode 100644
index 0000000..149c1cd
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+
+/**
+ * Main interface for a client to get notifications of events coming from this module.
+ *
+ * {@hide}
+ */
+oneway interface ISoundTriggerCallback {
+ /**
+ * Invoked whenever a recognition event is triggered (typically, on recognition, but also in
+ * case of external aborting of a recognition or a forced recognition event - see the status
+ * code in the event for determining).
+ */
+ void onRecognition(int modelHandle, in RecognitionEvent event);
+ /**
+ * Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
+ * also in case of external aborting of a recognition or a forced recognition event - see the
+ * status code in the event for determining).
+ */
+ void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event);
+ /**
+ * Notifies the client the recognition has become available after previously having been
+ * unavailable, or vice versa. This method will always be invoked once immediately after
+ * attachment, and then every time there is a change in availability.
+ * When availability changes from available to unavailable, all active recognitions are aborted,
+ * and this event will be sent in addition to the abort event.
+ */
+ void onRecognitionAvailabilityChange(boolean available);
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
new file mode 100644
index 0000000..8033307
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+
+/**
+ * Main entry point into this module.
+ *
+ * Allows the client to enumerate the available soundtrigger devices and their capabilities, then
+ * attach to either one of them in order to use it.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerMiddlewareService {
+ /**
+ * Query the available modules and their capabilities.
+ */
+ SoundTriggerModuleDescriptor[] listModules();
+
+ /**
+ * Attach to one of the available modules.
+ * listModules() must be called prior to calling this method and the provided handle must be
+ * one of the handles from the returned list.
+ */
+ ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);
+
+ /**
+ * Notify the service that external input capture is taking place. This may cause some of the
+ * active recognitions to be aborted.
+ */
+ void setExternalCaptureState(boolean active);
+}
\ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
new file mode 100644
index 0000000..c4a5785
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+
+/**
+ * A sound-trigger module.
+ *
+ * This interface allows a client to operate a sound-trigger device, intended for low-power
+ * detection of various sound patterns, represented by a "sound model".
+ *
+ * Basic operation is to load a sound model (either a generic one or a "phrase" model), then
+ * initiate recognition on this model. A trigger will be delivered asynchronously via a callback
+ * provided by the caller earlier, when attaching to this interface.
+ *
+ * In additon to recognition events, this module will also produce abort events in cases where
+ * recognition has been externally preempted.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerModule {
+ /**
+ * Load a sound model. Will return a handle to the model on success or will throw a
+ * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+ * (for example, lack of resources of loading a model at the time of call.
+ * Model must eventually be unloaded using {@link #unloadModel(int)} prior to detaching.
+ *
+ * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+ * resources required for loading the model are currently consumed by other clients.
+ */
+ int loadModel(in SoundModel model);
+
+ /**
+ * Load a phrase sound model. Will return a handle to the model on success or will throw a
+ * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+ * (for example, lack of resources of loading a model at the time of call.
+ * Model must eventually be unloaded using unloadModel prior to detaching.
+ *
+ * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+ * resources required for loading the model are currently consumed by other clients.
+ */
+ int loadPhraseModel(in PhraseSoundModel model);
+
+ /**
+ * Unload a model, previously loaded with loadModel or loadPhraseModel. After unloading, model
+ * can no longer be used for recognition and the resources occupied by it are released.
+ * Model must not be active at the time of unloading. Cient may call stopRecognition to ensure
+ * that.
+ */
+ void unloadModel(int modelHandle);
+
+ /**
+ * Initiate recognition on a previously loaded model.
+ * Recognition event would eventually be delivered via the client-provided callback, typically
+ * supplied during attachment to this interface.
+ *
+ * Once a recognition event is passed to the client, the recognition automatically become
+ * inactive, unless the event is of the RecognitionStatus.FORCED kind. Client can also shut down
+ * the recognition explicitly, via stopRecognition.
+ */
+ void startRecognition(int modelHandle, in RecognitionConfig config);
+
+ /**
+ * Stop a recognition of a previously active recognition. Will NOT generate a recognition event.
+ * This call is idempotent - calling it on an inactive model has no effect. However, it must
+ * only be used with a loaded model handle.
+ */
+ void stopRecognition(int modelHandle);
+
+ /**
+ * Force generation of a recognition event. Handle must be that of a loaded model. If
+ * recognition is inactive, will do nothing. If recognition is active, will asynchronously
+ * deliever an event with RecognitionStatus.FORCED status and leave recognition in active state.
+ * To avoid any race conditions, once an event signalling the automatic stopping of recognition
+ * is sent, no more forced events will get sent (even if previously requested) until recognition
+ * is explicitly started again.
+ *
+ * Since not all module implementations support this feature, may throw a
+ * ServiceSpecificException with an OPERATION_NOT_SUPPORTED status.
+ */
+ void forceRecognitionEvent(int modelHandle);
+
+ /**
+ * Set a model specific parameter 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.
+ * It is expected to check if the handle supports the parameter via the
+ * queryModelParameterSupport API prior to calling this method.
+ *
+ * @param modelHandle The sound model handle indicating which model to modify parameters
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @param value The value to set for the given model parameter
+ */
+ void setModelParameter(int modelHandle, ModelParameter modelParam, int value);
+
+ /**
+ * Get a model specific parameter. 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 ModelParameter for parameter default values.
+ * It is expected to check if the handle supports the parameter via the
+ * queryModelParameterSupport API prior to calling this method.
+ *
+ * @param modelHandle The sound model associated with given modelParam
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @return Value set to the requested parameter.
+ */
+ int getModelParameter(int modelHandle, ModelParameter modelParam);
+
+ /**
+ * Determine if parameter control is supported for the given model handle, and its valid value
+ * range if it is.
+ *
+ * @param modelHandle The sound model handle indicating which model to query
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @return If parameter is supported, the return value is its valid range, otherwise null.
+ */
+ @nullable ModelParameterRange queryModelParameterSupport(int modelHandle,
+ ModelParameter modelParam);
+
+ /**
+ * Detach from the module, releasing any active resources.
+ * This will ensure the client callback is no longer called after this call returns.
+ * All models must have been unloaded prior to calling this method.
+ */
+ void detach();
+}
\ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
new file mode 100644
index 0000000..0993627
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameter.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 android.media.soundtrigger_middleware;
+
+/**
+ * Model specific parameters to be used with parameter set and get APIs.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum ModelParameter {
+ /**
+ * Placeholder for invalid model parameter used for returning error or
+ * passing an invalid value.
+ */
+ INVALID = -1,
+
+ /**
+ * 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.
+ */
+ THRESHOLD_FACTOR = 0,
+}
diff --git a/tests/utils/testutils/java/test/package-info.java b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
similarity index 71%
copy from tests/utils/testutils/java/test/package-info.java
copy to media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
index c34d7b2..d6948a8 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
@@ -13,9 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.media.soundtrigger_middleware;
/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+ * Value range for a model parameter.
+ *
+ * {@hide}
+ */
+parcelable ModelParameterRange {
+ /** Minimum (inclusive) */
+ int minInclusive;
+ /** Maximum (inclusive) */
+ int maxInclusive;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Phrase.aidl b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
new file mode 100644
index 0000000..98a489f8
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
@@ -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 android.media.soundtrigger_middleware;
+
+/**
+ * Key phrase descriptor.
+ *
+ * {@hide}
+ */
+parcelable Phrase {
+ /** Unique keyphrase ID assigned at enrollment time. */
+ int id;
+ /** Recognition modes supported by this key phrase (bitfield of RecognitionMode enum). */
+ int recognitionModes;
+ /** List of users IDs associated with this key phrase. */
+ int[] users;
+ /** Locale - Java Locale style (e.g. en_US). */
+ String locale;
+ /** Phrase text. */
+ String text;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
new file mode 100644
index 0000000..6a3ec61
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+
+/**
+ * An event that gets sent to indicate a phrase recognition (or aborting of the recognition
+ process).
+ * {@hide}
+ */
+parcelable PhraseRecognitionEvent {
+ /** Common recognition event. */
+ RecognitionEvent common;
+ /** List of descriptors for each recognized key phrase */
+ PhraseRecognitionExtra[] phraseExtras;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
new file mode 100644
index 0000000..cb96bf3
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
@@ -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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+
+/**
+ * Specialized recognition event for key phrase detection.
+ * {@hide}
+ */
+parcelable PhraseRecognitionExtra {
+ // TODO(ytai): Constants / enums.
+
+ /** keyphrase ID */
+ int id;
+ /** recognition modes used for this keyphrase */
+ int recognitionModes;
+ /** confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER */
+ int confidenceLevel;
+ /** number of user confidence levels */
+ ConfidenceLevel[] levels;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
new file mode 100644
index 0000000..81028c1
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.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;
+
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.Phrase;
+
+/**
+ * Specialized sound model for key phrase detection.
+ * Proprietary representation of key phrases in binary data must match
+ * information indicated by phrases field.
+ * {@hide}
+ */
+parcelable PhraseSoundModel {
+ /** Common part of sound model descriptor */
+ SoundModel common;
+ /** List of descriptors for key phrases supported by this sound model */
+ Phrase[] phrases;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
new file mode 100644
index 0000000..c7642e8
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+
+/**
+ * Configuration for tuning behavior of an active recognition process.
+ * {@hide}
+ */
+parcelable RecognitionConfig {
+ /* Capture and buffer audio for this recognition instance. */
+ boolean captureRequested;
+
+ /* Configuration for each key phrase. */
+ PhraseRecognitionExtra[] phraseRecognitionExtras;
+
+ /** Opaque capture configuration data. */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
new file mode 100644
index 0000000..de4d060
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * An event that gets sent to indicate a recognition (or aborting of the recognition process).
+ * {@hide}
+ */
+parcelable RecognitionEvent {
+ /** Recognition status. */
+ RecognitionStatus status;
+ /** Event type, same as sound model type. */
+ SoundModelType type;
+ /** Is it possible to capture audio from this utterance buffered by the implementation. */
+ boolean captureAvailable;
+ /* Audio session ID. framework use. */
+ int captureSession;
+ /**
+ * Delay in ms between end of model detection and start of audio available for capture.
+ * A negative value is possible (e.g. if key phrase is also available for Capture.
+ */
+ int captureDelayMs;
+ /** Duration in ms of audio captured before the start of the trigger. 0 if none. */
+ int capturePreambleMs;
+ /** If true, the 'data' field below contains the capture of the trigger sound. */
+ boolean triggerInData;
+ /**
+ * Audio format of either the trigger in event data or to use for capture of the rest of the
+ * utterance.
+ */
+ AudioConfig audioConfig;
+ /** Additional data. */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
new file mode 100644
index 0000000..d8bfff4
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionMode.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;
+
+/**
+ * Recognition mode.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionMode {
+ /** Simple voice trigger. */
+ VOICE_TRIGGER = 0x1,
+ /** Trigger only if one user in model identified. */
+ USER_IDENTIFICATION = 0x2,
+ /** Trigger only if one user in model authenticated. */
+ USER_AUTHENTICATION = 0x4,
+ /** Generic sound trigger. */
+ GENERIC_TRIGGER = 0x8,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
new file mode 100644
index 0000000..d563edc
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
@@ -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.media.soundtrigger_middleware;
+
+/**
+ * A status for indicating the type of a recognition event.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionStatus {
+ /** Recognition success. */
+ SUCCESS = 0,
+ /** Recognition aborted (e.g. capture preempted by another use-case. */
+ ABORTED = 1,
+ /** Recognition failure. */
+ FAILURE = 2,
+ /**
+ * Recognition event was triggered by a forceRecognitionEvent request, not by the DSP.
+ * Note that forced detections *do not* stop the active recognition, unlike the other types.
+ */
+ FORCED = 3
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
new file mode 100644
index 0000000..fba1ee50
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * Base sound model descriptor. This struct can be extended for various specific types by way of
+ * aggregation.
+ * {@hide}
+ */
+parcelable SoundModel {
+ /** Model type. */
+ SoundModelType type;
+ /** Unique sound model ID. */
+ String uuid;
+ /**
+ * Unique vendor ID. Identifies the engine the sound model
+ * was build for */
+ String vendorUuid;
+ /** Opaque data transparent to Android framework */
+ byte[] data;
+}
diff --git a/core/java/android/net/ITetheringEventCallback.aidl b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
similarity index 67%
copy from core/java/android/net/ITetheringEventCallback.aidl
copy to media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
index d502088..f2abc9a 100644
--- a/core/java/android/net/ITetheringEventCallback.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
@@ -13,16 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package android.net;
-
-import android.net.Network;
+package android.media.soundtrigger_middleware;
/**
- * Callback class for receiving tethering changed events
- * @hide
+ * Sound model type.
+ * {@hide}
*/
-oneway interface ITetheringEventCallback
-{
- void onUpstreamChanged(in Network network);
+@Backing(type="int")
+enum SoundModelType {
+ /** Unspecified sound model type */
+ UNKNOWN = -1,
+ /** Key phrase sound models */
+ KEYPHRASE = 0,
+ /** All models other than keyphrase */
+ GENERIC = 1,
}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
new file mode 100644
index 0000000..667135f
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+/**
+ * A descriptor of an available sound trigger module, containing the handle used to reference the
+ * module, as well its capabilities.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleDescriptor {
+ /** Module handle to be used for attaching to it. */
+ int handle;
+ /** Module capabilities. */
+ SoundTriggerModuleProperties properties;
+}
+
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
new file mode 100644
index 0000000..1a3b402
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * Capabilities of a sound trigger module.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleProperties {
+ /** Implementor name */
+ String implementor;
+ /** Implementation description */
+ String description;
+ /** Implementation version */
+ int version;
+ /**
+ * Unique implementation ID. The UUID must change with each version of
+ the engine implementation */
+ String uuid;
+ /** Maximum number of concurrent sound models loaded */
+ int maxSoundModels;
+ /** Maximum number of key phrases */
+ int maxKeyPhrases;
+ /** Maximum number of concurrent users detected */
+ int maxUsers;
+ /** All supported modes. e.g RecognitionMode.VOICE_TRIGGER */
+ int recognitionModes;
+ /** Supports seamless transition from detection to capture */
+ boolean captureTransition;
+ /** Maximum buffering capacity in ms if captureTransition is true */
+ int maxBufferMs;
+ /** Supports capture by other use cases while detection is active */
+ boolean concurrentCapture;
+ /** Returns the trigger capture in event */
+ boolean triggerInEvent;
+ /**
+ * Rated power consumption when detection is active with TDB
+ * silence/sound/speech ratio */
+ int powerConsumptionMw;
+}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
similarity index 62%
copy from core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
copy to media/java/android/media/soundtrigger_middleware/Status.aidl
index cd988dc..d8f9d8f 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -13,17 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package android.os.incremental;
-
-import android.os.incremental.NamedParcelFileDescriptor;
+package android.media.soundtrigger_middleware;
/**
- * Class for holding data loader configuration parameters.
- * @hide
+ * {@hide}
*/
-parcelable IncrementalDataLoaderParamsParcel {
- @utf8InCpp String packageName;
- @utf8InCpp String staticArgs;
- NamedParcelFileDescriptor[] dynamicArgs;
+@Backing(type="int")
+enum Status {
+ /** Success. */
+ SUCCESS = 0,
+ /** Failure due to resource contention. This is typically a temporary condition. */
+ RESOURCE_CONTENTION = 1,
+ /** Operation is not supported in this implementation. This is a permanent condition. */
+ OPERATION_NOT_SUPPORTED = 2,
}
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
old mode 100644
new mode 100755
index bd05184..f90c504
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -38,4 +38,5 @@
void notifyHardwareRemoved(in TvInputHardwareInfo hardwareInfo);
void notifyHdmiDeviceAdded(in HdmiDeviceInfo deviceInfo);
void notifyHdmiDeviceRemoved(in HdmiDeviceInfo deviceInfo);
+ void notifyHdmiDeviceUpdated(in HdmiDeviceInfo deviceInfo);
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
old mode 100644
new mode 100755
index ff69779..5c11ed9b
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -173,6 +173,12 @@
mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_INPUT,
deviceInfo).sendToTarget();
}
+
+ @Override
+ public void notifyHdmiDeviceUpdated(HdmiDeviceInfo deviceInfo) {
+ mServiceHandler.obtainMessage(ServiceHandler.DO_UPDATE_HDMI_INPUT,
+ deviceInfo).sendToTarget();
+ }
};
}
@@ -257,6 +263,24 @@
return null;
}
+ /**
+ * Called when {@code deviceInfo} is updated.
+ *
+ * <p>The changes are usually cuased by the corresponding HDMI-CEC logical device.
+ *
+ * <p>The default behavior ignores all changes.
+ *
+ * <p>The TV input service responsible for {@code deviceInfo} can update the {@link TvInputInfo}
+ * object based on the updated {@code deviceInfo} (e.g. update the label based on the preferred
+ * device OSD name).
+ *
+ * @param deviceInfo the updated {@link HdmiDeviceInfo} object.
+ * @hide
+ */
+ @SystemApi
+ public void onHdmiDeviceUpdated(@NonNull HdmiDeviceInfo deviceInfo) {
+ }
+
private boolean isPassthroughInput(String inputId) {
if (mTvInputManager == null) {
mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
@@ -1962,6 +1986,7 @@
private static final int DO_REMOVE_HARDWARE_INPUT = 5;
private static final int DO_ADD_HDMI_INPUT = 6;
private static final int DO_REMOVE_HDMI_INPUT = 7;
+ private static final int DO_UPDATE_HDMI_INPUT = 8;
private void broadcastAddHardwareInput(int deviceId, TvInputInfo inputInfo) {
int n = mCallbacks.beginBroadcast();
@@ -2131,6 +2156,11 @@
}
return;
}
+ case DO_UPDATE_HDMI_INPUT: {
+ HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
+ onHdmiDeviceUpdated(deviceInfo);
+ return;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
diff --git a/media/java/android/media/tv/tuner/DvrSettings.java b/media/java/android/media/tv/tuner/DvrSettings.java
new file mode 100644
index 0000000..76160dc
--- /dev/null
+++ b/media/java/android/media/tv/tuner/DvrSettings.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;
+
+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/FilterSettings.java b/media/java/android/media/tv/tuner/FilterSettings.java
index d5f1003..673b3d9 100644
--- a/media/java/android/media/tv/tuner/FilterSettings.java
+++ b/media/java/android/media/tv/tuner/FilterSettings.java
@@ -17,8 +17,7 @@
package android.media.tv.tuner;
import android.annotation.Nullable;
-import android.hardware.tv.tuner.V1_0.Constants;
-import android.media.tv.tuner.TunerConstants.FilterSettingsType;
+import android.media.tv.tuner.TunerConstants.FilterType;
import java.util.List;
@@ -38,7 +37,8 @@
/**
* Gets filter settings type
*/
- @FilterSettingsType public abstract int getType();
+ @FilterType
+ public abstract int getType();
// TODO: more builders and getters
@@ -55,7 +55,7 @@
@Override
public int getType() {
- return TunerConstants.FILTER_SETTINGS_TS;
+ return TunerConstants.FILTER_TYPE_TS;
}
/**
@@ -109,7 +109,7 @@
@Override
public int getType() {
- return TunerConstants.FILTER_SETTINGS_MMTP;
+ return TunerConstants.FILTER_TYPE_MMTP;
}
}
@@ -130,7 +130,7 @@
@Override
public int getType() {
- return TunerConstants.FILTER_SETTINGS_IP;
+ return TunerConstants.FILTER_TYPE_IP;
}
}
@@ -149,7 +149,7 @@
@Override
public int getType() {
- return TunerConstants.FILTER_SETTINGS_TLV;
+ return TunerConstants.FILTER_TYPE_TLV;
}
}
@@ -167,7 +167,7 @@
@Override
public int getType() {
- return TunerConstants.FILTER_SETTINGS_ALP;
+ return TunerConstants.FILTER_TYPE_ALP;
}
}
@@ -197,24 +197,7 @@
public static class SectionSettings extends Settings {
private SectionSettings(int mainType) {
- super(SectionSettings.findType(mainType));
- }
-
- private static int findType(int mainType) {
- switch (mainType) {
- case TunerConstants.FILTER_SETTINGS_TS:
- return Constants.DemuxTsFilterType.SECTION;
- case TunerConstants.FILTER_SETTINGS_MMTP:
- return Constants.DemuxMmtpFilterType.SECTION;
- case TunerConstants.FILTER_SETTINGS_IP:
- return Constants.DemuxIpFilterType.SECTION;
- case TunerConstants.FILTER_SETTINGS_TLV:
- return Constants.DemuxTlvFilterType.SECTION;
- case TunerConstants.FILTER_SETTINGS_ALP:
- return Constants.DemuxAlpFilterType.SECTION;
- }
- // UNDEFINED
- return 0;
+ super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_SECTION));
}
}
@@ -251,22 +234,11 @@
private boolean mIsRaw;
private PesSettings(int mainType, int streamId, boolean isRaw) {
- super(PesSettings.findType(mainType));
+ super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES));
mStreamId = streamId;
mIsRaw = isRaw;
}
- private static int findType(int mainType) {
- switch (mainType) {
- case TunerConstants.FILTER_SETTINGS_TS:
- return Constants.DemuxTsFilterType.PES;
- case TunerConstants.FILTER_SETTINGS_MMTP:
- return Constants.DemuxMmtpFilterType.PES;
- }
- // UNDEFINED
- return 0;
- }
-
/**
* Creates a builder for PesSettings.
*/
@@ -319,22 +291,11 @@
private boolean mIsPassthrough;
private AvSettings(int mainType, boolean isAudio) {
- super(AvSettings.findType(mainType, isAudio));
- }
-
- private static int findType(int mainType, boolean isAudio) {
- switch (mainType) {
- case TunerConstants.FILTER_SETTINGS_TS:
- return isAudio
- ? Constants.DemuxTsFilterType.AUDIO
- : Constants.DemuxTsFilterType.VIDEO;
- case TunerConstants.FILTER_SETTINGS_MMTP:
- return isAudio
- ? Constants.DemuxMmtpFilterType.AUDIO
- : Constants.DemuxMmtpFilterType.VIDEO;
- }
- // UNDEFINED
- return 0;
+ super(TunerUtils.getFilterSubtype(
+ mainType,
+ isAudio
+ ? TunerConstants.FILTER_SUBTYPE_AUDIO
+ : TunerConstants.FILTER_SUBTYPE_VIDEO));
}
}
@@ -345,15 +306,7 @@
private int mDownloadId;
public DownloadSettings(int mainType) {
- super(DownloadSettings.findType(mainType));
- }
-
- private static int findType(int mainType) {
- if (mainType == TunerConstants.FILTER_SETTINGS_MMTP) {
- return Constants.DemuxMmtpFilterType.DOWNLOAD;
- }
- // UNDEFINED
- return 0;
+ super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
}
}
@@ -365,18 +318,7 @@
private int mIndexMask;
public RecordSettings(int mainType) {
- super(RecordSettings.findType(mainType));
- }
-
- private static int findType(int mainType) {
- switch (mainType) {
- case TunerConstants.FILTER_SETTINGS_TS:
- return Constants.DemuxTsFilterType.RECORD;
- case TunerConstants.FILTER_SETTINGS_MMTP:
- return Constants.DemuxMmtpFilterType.RECORD;
- }
- // UNDEFINED
- return 0;
+ 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 3782cc6..531e210 100644
--- a/media/java/android/media/tv/tuner/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/FrontendSettings.java
@@ -16,15 +16,22 @@
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.
* @hide
*/
+@SystemApi
public abstract class FrontendSettings {
- protected int mFrequency;
+ private final int mFrequency;
+
+ FrontendSettings(int frequency) {
+ mFrequency = frequency;
+ }
/**
* Returns the frontend type.
@@ -32,7 +39,12 @@
@FrontendSettingsType
public abstract int getType();
- public int getFrequency() {
+ /**
+ * Gets the frequency setting.
+ *
+ * @return the frequency in Hz.
+ */
+ public final int getFrequency() {
return mFrequency;
}
@@ -42,6 +54,7 @@
/**
* Frontend settings for analog.
+ * @hide
*/
public static class FrontendAnalogSettings extends FrontendSettings {
private int mAnalogType;
@@ -68,7 +81,7 @@
}
private FrontendAnalogSettings(int frequency, int analogType, int sifStandard) {
- mFrequency = frequency;
+ super(frequency);
mAnalogType = analogType;
mSifStandard = sifStandard;
}
@@ -118,10 +131,15 @@
/**
* 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;
@@ -130,12 +148,17 @@
/**
* 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;
@@ -144,6 +167,7 @@
/**
* Frontend settings for DVBS.
+ * @hide
*/
public static class FrontendDvbsSettings extends FrontendSettings {
public int modulation;
@@ -154,6 +178,10 @@
public int inputStreamId;
public byte standard;
+ FrontendDvbsSettings(int frequency) {
+ super(frequency);
+ }
+
@Override
public int getType() {
return TunerConstants.FRONTEND_TYPE_DVBS;
@@ -162,6 +190,7 @@
/**
* Frontend settings for DVBC.
+ * @hide
*/
public static class FrontendDvbcSettings extends FrontendSettings {
public int modulation;
@@ -171,6 +200,10 @@
public byte annex;
public int spectralInversion;
+ FrontendDvbcSettings(int frequency) {
+ super(frequency);
+ }
+
@Override
public int getType() {
return TunerConstants.FRONTEND_TYPE_DVBC;
@@ -179,6 +212,7 @@
/**
* Frontend settings for DVBT.
+ * @hide
*/
public static class FrontendDvbtSettings extends FrontendSettings {
public int transmissionMode;
@@ -195,6 +229,10 @@
public byte plpId;
public byte plpGroupId;
+ FrontendDvbtSettings(int frequency) {
+ super(frequency);
+ }
+
@Override
public int getType() {
return TunerConstants.FRONTEND_TYPE_DVBT;
@@ -203,6 +241,7 @@
/**
* Frontend settings for ISDBS.
+ * @hide
*/
public static class FrontendIsdbsSettings extends FrontendSettings {
public int streamId;
@@ -212,6 +251,10 @@
public int symbolRate;
public int rolloff;
+ FrontendIsdbsSettings(int frequency) {
+ super(frequency);
+ }
+
@Override
public int getType() {
return TunerConstants.FRONTEND_TYPE_ISDBS;
@@ -220,6 +263,7 @@
/**
* Frontend settings for ISDBS-3.
+ * @hide
*/
public static class FrontendIsdbs3Settings extends FrontendSettings {
public int streamId;
@@ -229,6 +273,10 @@
public int symbolRate;
public int rolloff;
+ FrontendIsdbs3Settings(int frequency) {
+ super(frequency);
+ }
+
@Override
public int getType() {
return TunerConstants.FRONTEND_TYPE_ISDBS3;
@@ -237,6 +285,7 @@
/**
* Frontend settings for ISDBT.
+ * @hide
*/
public static class FrontendIsdbtSettings extends FrontendSettings {
public int modulation;
@@ -245,6 +294,10 @@
public int guardInterval;
public int serviceAreaId;
+ FrontendIsdbtSettings(int frequency) {
+ super(frequency);
+ }
+
@Override
public int getType() {
return TunerConstants.FRONTEND_TYPE_ISDBT;
@@ -253,6 +306,7 @@
/**
* PLP settings for ATSC-3.
+ * @hide
*/
public static class FrontendAtsc3PlpSettings {
public byte plpId;
@@ -264,6 +318,7 @@
/**
* Code rate for DVBS.
+ * @hide
*/
public static class FrontendDvbsCodeRate {
public long fec;
diff --git a/media/java/android/media/tv/tuner/ScanMessage.java b/media/java/android/media/tv/tuner/ScanMessage.java
new file mode 100644
index 0000000..35f54f8
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ScanMessage.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;
+
+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 6537f6f..f02c4aa 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -18,7 +18,13 @@
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.FilterSubtype;
+import android.media.tv.tuner.TunerConstants.FilterType;
+import android.media.tv.tuner.TunerConstants.FrontendScanType;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -26,10 +32,15 @@
import java.util.List;
/**
- * Tuner is used to interact with tuner devices.
+ * This class is used to interact with hardware tuners devices.
+ *
+ * <p> Each TvInputService Session should create one instance of this class.
+ *
+ * <p> This class controls the TIS interaction with Tuner HAL.
*
* @hide
*/
+@SystemApi
public final class Tuner implements AutoCloseable {
private static final String TAG = "MediaTvTuner";
private static final boolean DEBUG = false;
@@ -44,6 +55,8 @@
nativeInit();
}
+ private final Context mContext;
+
private List<Integer> mFrontendIds;
private Frontend mFrontend;
private EventHandler mHandler;
@@ -51,12 +64,19 @@
private List<Integer> mLnbIds;
private Lnb mLnb;
- public Tuner() {
+ /**
+ * Constructs a Tuner instance.
+ *
+ * @param context context of the caller.
+ */
+ public Tuner(@NonNull Context context) {
+ mContext = context;
nativeSetup();
}
private long mNativeContext; // used by native jMediaTuner
+ /** @hide */
@Override
public void close() {}
@@ -80,7 +100,10 @@
*/
private native Frontend nativeOpenFrontendById(int id);
private native int nativeTune(int type, FrontendSettings settings);
-
+ private native int nativeStopTune();
+ private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
+ private native int nativeSetLnb(int lnbId);
+ private native int nativeSetLna(boolean enable);
private native Filter nativeOpenFilter(int type, int subType, int bufferSize);
private native List<Integer> nativeGetLnbIds();
@@ -92,6 +115,8 @@
/**
* Frontend Callback.
+ *
+ * @hide
*/
public interface FrontendCallback {
@@ -99,10 +124,18 @@
* 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 {
/**
@@ -113,6 +146,8 @@
/**
* Frontend Callback.
+ *
+ * @hide
*/
public interface FilterCallback {
/**
@@ -123,6 +158,8 @@
/**
* DVR Callback.
+ *
+ * @hide
*/
public interface DvrCallback {
/**
@@ -177,7 +214,7 @@
}
}
- protected class Frontend {
+ private class Frontend {
private int mId;
private FrontendCallback mCallback;
@@ -210,12 +247,66 @@
}
/**
- * Tunes the frontend to using the settings given.
+ * Tunes the frontend to the settings given.
+ *
+ * @return result status of tune operation.
+ * @throws SecurityException if the caller does not have appropriate permissions.
+ * TODO: add result constants or throw exceptions.
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
public int tune(@NonNull FrontendSettings settings) {
+ TunerUtils.checkTunerPermission(mContext);
return nativeTune(settings.getType(), settings);
}
+ /**
+ * Stops a previous tuning.
+ *
+ * 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
+ */
+ public int stopTune() {
+ return nativeStopTune();
+ }
+
+ /**
+ * Scan channels.
+ * @hide
+ */
+ public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType) {
+ return nativeScan(settings.getType(), settings, scanType);
+ }
+
+ /**
+ * Sets Low-Noise Block downconverter (LNB) for satellite frontend.
+ *
+ * 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
+ */
+ public int setLnb(@NonNull Lnb lnb) {
+ return nativeSetLnb(lnb.mId);
+ }
+
+ /**
+ * Enable or Disable Low Noise Amplifier (LNA).
+ *
+ * @param enable true to activate LNA module; false to deactivate LNA
+ *
+ * @return result status of the operation.
+ * @hide
+ */
+ public int setLna(boolean enable) {
+ return nativeSetLna(enable);
+ }
+
private List<Integer> getFrontendIds() {
mFrontendIds = nativeGetFrontendIds();
return mFrontendIds;
@@ -238,7 +329,8 @@
}
}
- protected class Filter {
+ /** @hide */
+ public class Filter {
private long mNativeContext;
private FilterCallback mCallback;
int mId;
@@ -286,8 +378,10 @@
}
}
- private Filter openFilter(int type, int subType, int bufferSize, FilterCallback cb) {
- Filter filter = nativeOpenFilter(type, subType, bufferSize);
+ private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize,
+ FilterCallback cb) {
+ Filter filter = nativeOpenFilter(
+ mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
if (filter != null) {
filter.mCallback = cb;
if (mHandler == null) {
@@ -297,7 +391,8 @@
return filter;
}
- protected class Lnb {
+ /** @hide */
+ public class Lnb {
private int mId;
private LnbCallback mCallback;
@@ -338,7 +433,15 @@
}
}
- protected class Descrambler {
+ /**
+ * 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.
+ *
+ */
+ public class Descrambler {
private long mNativeContext;
private native boolean nativeAddPid(int pidType, int pid, Filter filter);
@@ -346,28 +449,39 @@
private Descrambler() {}
- private boolean addPid(@DemuxPidType int pidType, int pid, Filter filter) {
+ /** @hide */
+ public boolean addPid(@DemuxPidType int pidType, int pid, Filter filter) {
return nativeAddPid(pidType, pid, filter);
}
- private boolean removePid(@DemuxPidType int pidType, int pid, Filter filter) {
+ /** @hide */
+ public boolean removePid(@DemuxPidType int pidType, int pid, Filter filter) {
return nativeRemovePid(pidType, pid, filter);
}
}
- private Descrambler openDescrambler() {
- Descrambler descrambler = nativeOpenDescrambler();
- return descrambler;
+ /**
+ * Opens a Descrambler in tuner.
+ *
+ * @return a {@link Descrambler} object.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public Descrambler openDescrambler() {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeOpenDescrambler();
}
// TODO: consider splitting Dvr to Playback and Recording
- protected class Dvr {
+ /** @hide */
+ public class Dvr {
private long mNativeContext;
private DvrCallback mCallback;
private native boolean nativeAttachFilter(Filter filter);
private native boolean nativeDetachFilter(Filter filter);
+ private native int nativeConfigureDvr(DvrSettings settings);
private native boolean nativeStartDvr();
private native boolean nativeStopDvr();
private native boolean nativeFlushDvr();
@@ -380,6 +494,9 @@
public boolean detachFilter(Filter filter) {
return nativeDetachFilter(filter);
}
+ public int configure(DvrSettings settings) {
+ return nativeConfigureDvr(settings);
+ }
public boolean start() {
return nativeStartDvr();
}
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index f2d5e93..2c6d850 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -75,6 +75,7 @@
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})
@@ -91,6 +92,75 @@
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)
+ @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, })
+ public @interface FilterSubtype {}
+
+ public static final int FILTER_SUBTYPE_UNDEFINED = 0;
+ public static final int FILTER_SUBTYPE_SECTION = 1;
+ public static final int FILTER_SUBTYPE_PES = 2;
+ public static final int FILTER_SUBTYPE_AUDIO = 3;
+ public static final int FILTER_SUBTYPE_VIDEO = 4;
+ public static final int FILTER_SUBTYPE_DOWNLOAD = 5;
+ public static final int FILTER_SUBTYPE_RECORD = 6;
+ public static final int FILTER_SUBTYPE_TS = 7;
+ public static final int FILTER_SUBTYPE_PCR = 8;
+ public static final int FILTER_SUBTYPE_TEMI = 9;
+ public static final int FILTER_SUBTYPE_MMPT = 10;
+ public static final int FILTER_SUBTYPE_NTP = 11;
+ public static final int FILTER_SUBTYPE_IP_PAYLOAD = 12;
+ public static final int FILTER_SUBTYPE_IP = 13;
+ public static final int FILTER_SUBTYPE_PAYLOAD_THROUGH = 14;
+ public static final int FILTER_SUBTYPE_TLV = 15;
+ public static final int FILTER_SUBTYPE_PTP = 16;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
+ public @interface FrontendScanType {}
+
+ public static final int FRONTEND_SCAN_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED;
+ public static final int FRONTEND_SCAN_AUTO = Constants.FrontendScanType.SCAN_AUTO;
+ 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({FILTER_SETTINGS_TS, FILTER_SETTINGS_MMTP, FILTER_SETTINGS_IP, FILTER_SETTINGS_TLV,
FILTER_SETTINGS_ALP})
public @interface FilterSettingsType {}
@@ -101,6 +171,13 @@
public static final int FILTER_SETTINGS_TLV = Constants.DemuxFilterMainType.TLV;
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;
+
private TunerConstants() {
}
}
diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java
new file mode 100644
index 0000000..b3bd780
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerUtils.java
@@ -0,0 +1,135 @@
+/*
+ * 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.content.Context;
+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;
+
+/**
+ * Utility class for tuner framework.
+ *
+ * @hide
+ */
+public final class TunerUtils {
+ private static final String PERMISSION = android.Manifest.permission.ACCESS_TV_TUNER;
+
+ 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) {
+ switch (subtype) {
+ case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+ return Constants.DemuxTsFilterType.UNDEFINED;
+ case TunerConstants.FILTER_SUBTYPE_SECTION:
+ return Constants.DemuxTsFilterType.SECTION;
+ case TunerConstants.FILTER_SUBTYPE_PES:
+ return Constants.DemuxTsFilterType.PES;
+ case TunerConstants.FILTER_SUBTYPE_TS:
+ return Constants.DemuxTsFilterType.TS;
+ case TunerConstants.FILTER_SUBTYPE_AUDIO:
+ return Constants.DemuxTsFilterType.AUDIO;
+ case TunerConstants.FILTER_SUBTYPE_VIDEO:
+ return Constants.DemuxTsFilterType.VIDEO;
+ case TunerConstants.FILTER_SUBTYPE_PCR:
+ return Constants.DemuxTsFilterType.PCR;
+ case TunerConstants.FILTER_SUBTYPE_RECORD:
+ return Constants.DemuxTsFilterType.RECORD;
+ case TunerConstants.FILTER_SUBTYPE_TEMI:
+ return Constants.DemuxTsFilterType.TEMI;
+ default:
+ break;
+ }
+ } else if (mainType == TunerConstants.FILTER_TYPE_MMTP) {
+ switch (subtype) {
+ case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+ return Constants.DemuxMmtpFilterType.UNDEFINED;
+ case TunerConstants.FILTER_SUBTYPE_SECTION:
+ return Constants.DemuxMmtpFilterType.SECTION;
+ case TunerConstants.FILTER_SUBTYPE_PES:
+ return Constants.DemuxMmtpFilterType.PES;
+ case TunerConstants.FILTER_SUBTYPE_MMPT:
+ return Constants.DemuxMmtpFilterType.MMTP;
+ case TunerConstants.FILTER_SUBTYPE_AUDIO:
+ return Constants.DemuxMmtpFilterType.AUDIO;
+ case TunerConstants.FILTER_SUBTYPE_VIDEO:
+ return Constants.DemuxMmtpFilterType.VIDEO;
+ case TunerConstants.FILTER_SUBTYPE_RECORD:
+ return Constants.DemuxMmtpFilterType.RECORD;
+ case TunerConstants.FILTER_SUBTYPE_DOWNLOAD:
+ return Constants.DemuxMmtpFilterType.DOWNLOAD;
+ default:
+ break;
+ }
+
+ } else if (mainType == TunerConstants.FILTER_TYPE_IP) {
+ switch (subtype) {
+ case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+ return Constants.DemuxIpFilterType.UNDEFINED;
+ case TunerConstants.FILTER_SUBTYPE_SECTION:
+ return Constants.DemuxIpFilterType.SECTION;
+ case TunerConstants.FILTER_SUBTYPE_NTP:
+ return Constants.DemuxIpFilterType.NTP;
+ case TunerConstants.FILTER_SUBTYPE_IP_PAYLOAD:
+ return Constants.DemuxIpFilterType.IP_PAYLOAD;
+ case TunerConstants.FILTER_SUBTYPE_IP:
+ return Constants.DemuxIpFilterType.IP;
+ case TunerConstants.FILTER_SUBTYPE_PAYLOAD_THROUGH:
+ return Constants.DemuxIpFilterType.PAYLOAD_THROUGH;
+ default:
+ break;
+ }
+ } else if (mainType == TunerConstants.FILTER_TYPE_TLV) {
+ switch (subtype) {
+ case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+ return Constants.DemuxTlvFilterType.UNDEFINED;
+ case TunerConstants.FILTER_SUBTYPE_SECTION:
+ return Constants.DemuxTlvFilterType.SECTION;
+ case TunerConstants.FILTER_SUBTYPE_TLV:
+ return Constants.DemuxTlvFilterType.TLV;
+ case TunerConstants.FILTER_SUBTYPE_PAYLOAD_THROUGH:
+ return Constants.DemuxTlvFilterType.PAYLOAD_THROUGH;
+ default:
+ break;
+ }
+ } else if (mainType == TunerConstants.FILTER_TYPE_ALP) {
+ switch (subtype) {
+ case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
+ return Constants.DemuxAlpFilterType.UNDEFINED;
+ case TunerConstants.FILTER_SUBTYPE_SECTION:
+ return Constants.DemuxAlpFilterType.SECTION;
+ case TunerConstants.FILTER_SUBTYPE_PTP:
+ return Constants.DemuxAlpFilterType.PTP;
+ case TunerConstants.FILTER_SUBTYPE_PAYLOAD_THROUGH:
+ return Constants.DemuxAlpFilterType.PAYLOAD_THROUGH;
+ default:
+ break;
+ }
+ }
+ throw new IllegalArgumentException(
+ "Invalid filter types. Main type=" + mainType + ", subtype=" + subtype);
+ }
+
+ private TunerUtils() {}
+}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 16ba63b..f3c071a0 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -16,8 +16,10 @@
package android.mtp;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -422,13 +424,13 @@
}
// Add the new file to MediaProvider
if (succeeded) {
- MediaStore.scanFile(mContext, obj.getPath().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
}
}
@VisibleForNative
private void rescanFile(String path, int handle, int format) {
- MediaStore.scanFile(mContext, new File(path));
+ MediaStore.scanFile(mContext.getContentResolver(), new File(path));
}
@VisibleForNative
@@ -577,7 +579,7 @@
try {
// note - we are relying on a special case in MediaProvider.update() to update
// the paths for all children in the case where this is a directory.
- final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+ final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in mMediaProvider.update", e);
@@ -587,13 +589,13 @@
if (obj.isDir()) {
// for directories, check if renamed from something hidden to something non-hidden
if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
- MediaStore.scanFile(mContext, newPath.toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
}
} else {
// for files, check if renamed from .nomedia to something else
if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
&& !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
- MediaStore.scanFile(mContext, newPath.getParent().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
}
}
return MtpConstants.RESPONSE_OK;
@@ -658,11 +660,11 @@
// Old parent exists in MediaProvider - perform a move
// note - we are relying on a special case in MediaProvider.update() to update
// the paths for all children in the case where this is a directory.
- final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+ final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
} else {
// Old parent doesn't exist - add the object
- MediaStore.scanFile(mContext, path.toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in mMediaProvider.update", e);
@@ -689,7 +691,7 @@
if (!success) {
return;
}
- MediaStore.scanFile(mContext, obj.getPath().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
}
@VisibleForNative
@@ -873,7 +875,7 @@
}
private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
- final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+ final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
int ret = -1;
Cursor c = null;
@@ -893,7 +895,7 @@
}
private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
- final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+ final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
try {
// Delete the object(s) from MediaProvider, but ignore errors.
if (isDir) {
@@ -909,7 +911,7 @@
String[] whereArgs = new String[]{path.toString()};
if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
- MediaStore.scanFile(mContext, path.getParent().toFile());
+ MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
}
} else {
Log.i(TAG, "Mediaprovider didn't delete " + path);
@@ -921,71 +923,12 @@
@VisibleForNative
private int[] getObjectReferences(int handle) {
- MtpStorageManager.MtpObject obj = mManager.getObject(handle);
- if (obj == null)
- return null;
- // Translate this handle to the MediaProvider Handle
- handle = findInMedia(obj, obj.getPath());
- if (handle == -1)
- return null;
- Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
- Cursor c = null;
- try {
- c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
- if (c == null) {
- return null;
- }
- ArrayList<Integer> result = new ArrayList<>();
- while (c.moveToNext()) {
- // Translate result handles back into handles for this session.
- String refPath = c.getString(0);
- MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
- if (refObj != null) {
- result.add(refObj.getId());
- }
- }
- return result.stream().mapToInt(Integer::intValue).toArray();
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in getObjectList", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
return null;
}
@VisibleForNative
private int setObjectReferences(int handle, int[] references) {
- MtpStorageManager.MtpObject obj = mManager.getObject(handle);
- if (obj == null)
- return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
- // Translate this handle to the MediaProvider Handle
- handle = findInMedia(obj, obj.getPath());
- if (handle == -1)
- return MtpConstants.RESPONSE_GENERAL_ERROR;
- Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
- ArrayList<ContentValues> valuesList = new ArrayList<>();
- for (int id : references) {
- // Translate each reference id to the MediaProvider Id
- MtpStorageManager.MtpObject refObj = mManager.getObject(id);
- if (refObj == null)
- continue;
- int refHandle = findInMedia(refObj, refObj.getPath());
- if (refHandle == -1)
- continue;
- ContentValues values = new ContentValues();
- values.put(Files.FileColumns._ID, refHandle);
- valuesList.add(values);
- }
- try {
- if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
- return MtpConstants.RESPONSE_OK;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in setObjectReferences", e);
- }
- return MtpConstants.RESPONSE_GENERAL_ERROR;
+ return MtpConstants.RESPONSE_OPERATION_NOT_SUPPORTED;
}
@VisibleForNative
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 65d0fef..c7dbca6 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -41,11 +41,7 @@
mDescription = volume.getDescription(null);
mRemovable = volume.isRemovable();
mMaxFileSize = volume.getMaxFileSize();
- if (volume.isPrimary()) {
- mVolumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
- } else {
- mVolumeName = volume.getNormalizedUuid();
- }
+ mVolumeName = volume.getMediaStoreVolumeName();
}
/**
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 05aaa82..20980a9 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -742,11 +742,11 @@
return OK;
}
-status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const {
- mediametrics_handle_t reply2 = MediaAnalyticsItem::convert(reply);
+status_t JMediaCodec::getMetrics(JNIEnv *, mediametrics::Item * &reply) const {
+ mediametrics_handle_t reply2 = mediametrics::Item::convert(reply);
status_t status = mCodec->getMetrics(reply2);
// getMetrics() updates reply2, pass the converted update along to our caller.
- reply = MediaAnalyticsItem::convert(reply2);
+ reply = mediametrics::Item::convert(reply2);
return status;
}
@@ -1850,7 +1850,7 @@
}
// get what we have for the metrics from the codec
- MediaAnalyticsItem *item = 0;
+ mediametrics::Item *item = 0;
status_t err = codec->getMetrics(env, item);
if (err != OK) {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index dfe30a3..ce1c805 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -21,7 +21,7 @@
#include "jni.h"
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/hardware/CryptoAPI.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/foundation/AHandler.h>
@@ -124,7 +124,7 @@
status_t getCodecInfo(JNIEnv *env, jobject *codecInfo) const;
- status_t getMetrics(JNIEnv *env, MediaAnalyticsItem * &reply) const;
+ status_t getMetrics(JNIEnv *env, mediametrics::Item * &reply) const;
status_t setParameters(const sp<AMessage> ¶ms);
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index f5ae9d0..528dc62 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -913,7 +913,7 @@
}
// build and return the Bundle
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(reply);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index baa779c..f5ba92e 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -19,7 +19,7 @@
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/foundation/AudioPresentationInfo.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/DataSource.h>
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index d315154..c064de2 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,11 +18,14 @@
#include <binder/Parcel.h>
#include <jni.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/IMediaMetricsService.h>
+#include <media/MediaMetricsItem.h>
#include <nativehelper/JNIHelp.h>
+#include <variant>
#include "android_media_MediaMetricsJNI.h"
#include "android_os_Parcel.h"
+#include "android_runtime/AndroidRuntime.h"
// This source file is compiled and linked into:
// core/jni/ (libandroid_runtime.so)
@@ -52,7 +55,7 @@
const jmethodID constructID;
jobject const bundle;
- // We use templated put to access MediaAnalyticsItem based on data type not type enum.
+ // We use templated put to access mediametrics::Item based on data type not type enum.
// See std::variant and std::visit.
template<typename T>
void put(jstring keyName, const T& value) = delete;
@@ -73,6 +76,23 @@
}
template<>
+ void put(jstring keyName, const std::string& value) {
+ env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value.c_str()));
+ }
+
+ template<>
+ void put(jstring keyName, const std::pair<int64_t, int64_t>& value) {
+ ; // rate is currently ignored
+ }
+
+ template<>
+ void put(jstring keyName, const std::monostate& value) {
+ ; // none is currently ignored
+ }
+
+ // string char * helpers
+
+ template<>
void put(jstring keyName, const char * const& value) {
env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
}
@@ -82,11 +102,6 @@
env->CallVoidMethod(bundle, putStringID, keyName, env->NewStringUTF(value));
}
- template<>
- void put(jstring keyName, const std::pair<int64_t, int64_t>& value) {
- ; // rate is currently ignored
- }
-
// We allow both jstring and non-jstring variants.
template<typename T>
void put(const char *keyName, const T& value) {
@@ -97,7 +112,7 @@
// place the attributes into a java PersistableBundle object
jobject MediaMetricsJNI::writeMetricsToBundle(
- JNIEnv* env, MediaAnalyticsItem *item, jobject bundle)
+ JNIEnv* env, mediametrics::Item *item, jobject bundle)
{
BundleHelper bh(env, bundle);
@@ -106,15 +121,15 @@
return nullptr;
}
- bh.put("__key", item->getKey().c_str());
+ bh.put(mediametrics::BUNDLE_KEY, item->getKey().c_str());
if (item->getPid() != -1) {
- bh.put("__pid", (int32_t)item->getPid());
+ bh.put(mediametrics::BUNDLE_PID, (int32_t)item->getPid());
}
if (item->getTimestamp() > 0) {
- bh.put("__timestamp", (int64_t)item->getTimestamp());
+ bh.put(mediametrics::BUNDLE_TIMESTAMP, (int64_t)item->getTimestamp());
}
if (item->getUid() != -1) {
- bh.put("__uid", (int32_t)item->getUid());
+ bh.put(mediametrics::BUNDLE_UID, (int32_t)item->getUid());
}
for (const auto &prop : *item) {
const char *name = prop.getName();
@@ -124,6 +139,26 @@
return bh.bundle;
}
+// Implementation of MediaMetrics.native_submit_bytebuffer(),
+// Delivers the byte buffer to the mediametrics service.
+static jint android_media_MediaMetrics_submit_bytebuffer(
+ JNIEnv* env, jobject thiz, jobject byteBuffer, jint length)
+{
+ const jbyte* buffer =
+ reinterpret_cast<const jbyte*>(env->GetDirectBufferAddress(byteBuffer));
+ if (buffer == nullptr) {
+ ALOGE("Error retrieving source of audio data to play, can't play");
+ return (jint)BAD_VALUE;
+ }
+
+ sp<IMediaMetricsService> service = mediametrics::BaseItem::getService();
+ if (service == nullptr) {
+ ALOGW("Cannot retrieve mediametrics service");
+ return (jint)NO_INIT;
+ }
+ return (jint)service->submitBuffer((char *)buffer, length);
+}
+
// Helper function to convert a native PersistableBundle to a Java
// PersistableBundle.
jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env,
@@ -191,5 +226,18 @@
return newBundle;
}
-}; // namespace android
+// ----------------------------------------------------------------------------
+static constexpr JNINativeMethod gMethods[] = {
+ {"native_submit_bytebuffer", "(Ljava/nio/ByteBuffer;I)I",
+ (void *)android_media_MediaMetrics_submit_bytebuffer},
+};
+
+// Registers the native methods, called from core/jni/AndroidRuntime.cpp
+int register_android_media_MediaMetrics(JNIEnv *env)
+{
+ return AndroidRuntime::registerNativeMethods(
+ env, "android/media/MediaMetrics", gMethods, std::size(gMethods));
+}
+
+}; // namespace android
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
index 63ec27a..bcad558 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -19,7 +19,7 @@
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <binder/PersistableBundle.h>
// Copeid from core/jni/ (libandroid_runtime.so)
@@ -27,7 +27,7 @@
class MediaMetricsJNI {
public:
- static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
+ static jobject writeMetricsToBundle(JNIEnv* env, mediametrics::Item *item, jobject mybundle);
static jobject nativeToJavaPersistableBundle(JNIEnv*, os::PersistableBundle*);
};
diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp
index f0aa4c3..0c1e9a2 100644
--- a/media/jni/android_media_MediaMuxer.cpp
+++ b/media/jni/android_media_MediaMuxer.cpp
@@ -26,11 +26,15 @@
#include <unistd.h>
#include <fcntl.h>
+#include <android/api-level.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaMuxer.h>
+extern "C" int android_get_application_target_sdk_version();
+
namespace android {
struct fields_t {
@@ -229,10 +233,31 @@
status_t err = muxer->stop();
- if (err != OK) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "Failed to stop the muxer");
- return;
+ if (android_get_application_target_sdk_version() >= __ANDROID_API_R__) {
+ switch (err) {
+ case OK:
+ break;
+ case ERROR_IO: {
+ jniThrowException(env, "java/lang/UncheckedIOException",
+ "Muxer stopped unexpectedly");
+ return;
+ }
+ case ERROR_MALFORMED: {
+ jniThrowException(env, "java/io/IOError",
+ "Failure of reading or writing operation");
+ return;
+ }
+ default: {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to stop the muxer");
+ return;
+ }
+ }
+ } else {
+ if (err != OK) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Failed to stop the muxer");
+ return;
+ }
}
}
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index b4fa07b..963b650 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -23,7 +23,7 @@
#include <media/AudioResamplerPublic.h>
#include <media/IMediaHTTPService.h>
#include <media/MediaPlayerInterface.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition
#include <stdio.h>
#include <assert.h>
@@ -682,7 +682,7 @@
return (jobject) NULL;
}
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(p);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index f8ba36d..6eeccf0 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -29,7 +29,7 @@
#include <gui/Surface.h>
#include <camera/Camera.h>
#include <media/mediarecorder.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/MicrophoneInfo.h>
#include <media/stagefright/PersistentSurface.h>
#include <utils/threads.h>
@@ -694,7 +694,7 @@
}
// build and return the Bundle
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
item->readFromParcel(reply);
jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index a0be12e..0cac2af 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -27,6 +27,7 @@
using ::android::hardware::Void;
using ::android::hardware::hidl_vec;
+using ::android::hardware::tv::tuner::V1_0::DataFormat;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
@@ -35,10 +36,13 @@
using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_0::DvrSettings;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSettings;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSifStandard;
using ::android::hardware::tv::tuner::V1_0::FrontendAnalogType;
using ::android::hardware::tv::tuner::V1_0::ITuner;
+using ::android::hardware::tv::tuner::V1_0::PlaybackSettings;
+using ::android::hardware::tv::tuner::V1_0::RecordSettings;
using ::android::hardware::tv::tuner::V1_0::Result;
struct fields_t {
@@ -93,6 +97,14 @@
mDvr = env->NewWeakGlobalRef(dvr);
}
+/////////////// Dvr ///////////////////////
+
+Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj) {}
+
+sp<IDvr> Dvr::getIDvr() {
+ return mDvrSp;
+}
+
/////////////// FilterCallback ///////////////////////
//TODO: implement filter callback
Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& /*filterEvent*/) {
@@ -300,6 +312,15 @@
return (int)result;
}
+int JTuner::scan(const FrontendSettings& settings, FrontendScanType scanType) {
+ if (mFe == NULL) {
+ ALOGE("frontend is not initialized");
+ return (int)Result::INVALID_STATE;
+ }
+ Result result = mFe->scan(settings, scanType);
+ return (int)result;
+}
+
bool JTuner::openDemux() {
if (mTuner == nullptr) {
return false;
@@ -391,14 +412,14 @@
return NULL;
}
}
- sp<IDvr> dvrSp;
+ sp<IDvr> iDvrSp;
sp<DvrCallback> callback = new DvrCallback();
mDemux->openDvr(type, bufferSize, callback,
[&](Result, const sp<IDvr>& dvr) {
- dvrSp = dvr;
+ iDvrSp = dvr;
});
- if (dvrSp == NULL) {
+ if (iDvrSp == NULL) {
return NULL;
}
@@ -408,7 +429,7 @@
env->FindClass("android/media/tv/tuner/Tuner$Dvr"),
gFields.dvrInitID,
mObject);
-
+ sp<Dvr> dvrSp = new Dvr(iDvrSp, dvrObj);
dvrSp->incStrong(dvrObj);
env->SetLongField(dvrObj, gFields.dvrContext, (jlong)dvrSp.get());
@@ -485,8 +506,51 @@
return (Filter *)env->GetLongField(filter, gFields.filterContext);
}
-static sp<IDvr> getDvr(JNIEnv *env, jobject dvr) {
- return (IDvr *)env->GetLongField(dvr, gFields.dvrContext);
+static DvrSettings getDvrSettings(JNIEnv *env, jobject settings) {
+ DvrSettings dvrSettings;
+ jclass clazz = env->FindClass("android/media/tv/tuner/DvrSettings");
+ uint32_t statusMask =
+ static_cast<uint32_t>(env->GetIntField(
+ settings, env->GetFieldID(clazz, "mStatusMask", "I")));
+ uint32_t lowThreshold =
+ static_cast<uint32_t>(env->GetIntField(
+ settings, env->GetFieldID(clazz, "mLowThreshold", "I")));
+ uint32_t highThreshold =
+ static_cast<uint32_t>(env->GetIntField(
+ settings, env->GetFieldID(clazz, "mHighThreshold", "I")));
+ uint8_t packetSize =
+ static_cast<uint8_t>(env->GetIntField(
+ settings, env->GetFieldID(clazz, "mPacketSize", "I")));
+ DataFormat dataFormat =
+ static_cast<DataFormat>(env->GetIntField(
+ settings, env->GetFieldID(clazz, "mDataFormat", "I")));
+ DvrType type =
+ static_cast<DvrType>(env->GetIntField(
+ settings, env->GetFieldID(clazz, "mType", "I")));
+ if (type == DvrType::RECORD) {
+ RecordSettings recordSettings {
+ .statusMask = static_cast<unsigned char>(statusMask),
+ .lowThreshold = lowThreshold,
+ .highThreshold = highThreshold,
+ .dataFormat = dataFormat,
+ .packetSize = packetSize,
+ };
+ dvrSettings.record(recordSettings);
+ } else if (type == DvrType::PLAYBACK) {
+ PlaybackSettings PlaybackSettings {
+ .statusMask = statusMask,
+ .lowThreshold = lowThreshold,
+ .highThreshold = highThreshold,
+ .dataFormat = dataFormat,
+ .packetSize = packetSize,
+ };
+ dvrSettings.playback(PlaybackSettings);
+ }
+ return dvrSettings;
+}
+
+static sp<Dvr> getDvr(JNIEnv *env, jobject dvr) {
+ return (Dvr *)env->GetLongField(dvr, gFields.dvrContext);
}
static void android_media_tv_Tuner_native_init(JNIEnv *env) {
@@ -545,6 +609,25 @@
return tuner->tune(getFrontendSettings(env, type, settings));
}
+static int android_media_tv_Tuner_stop_tune(JNIEnv, jobject) {
+ return 0;
+}
+
+static int android_media_tv_Tuner_scan(
+ JNIEnv *env, jobject thiz, jint settingsType, jobject settings, jint scanType) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->scan(getFrontendSettings(
+ env, settingsType, settings), static_cast<FrontendScanType>(scanType));
+}
+
+static int android_media_tv_Tuner_set_lnb(JNIEnv, jobject, jint) {
+ return 0;
+}
+
+static int android_media_tv_Tuner_set_lna(JNIEnv, jobject, jint, jboolean) {
+ return 0;
+}
+
static jobject android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->getLnbIds();
@@ -730,7 +813,7 @@
}
static bool android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) {
- sp<IDvr> dvrSp = getDvr(env, dvr);
+ sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
sp<IFilter> filterSp = getFilter(env, filter)->getIFilter();
if (dvrSp == NULL || filterSp == NULL) {
return false;
@@ -740,7 +823,7 @@
}
static bool android_media_tv_Tuner_detach_filter(JNIEnv *env, jobject dvr, jobject filter) {
- sp<IDvr> dvrSp = getDvr(env, dvr);
+ sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
sp<IFilter> filterSp = getFilter(env, filter)->getIFilter();
if (dvrSp == NULL || filterSp == NULL) {
return false;
@@ -749,8 +832,18 @@
return result == Result::SUCCESS;
}
+static int android_media_tv_Tuner_configure_dvr(JNIEnv *env, jobject dvr, jobject settings) {
+ sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
+ if (dvrSp == NULL) {
+ ALOGD("Failed to configure dvr: dvr not found");
+ return (int)Result::INVALID_STATE;
+ }
+ Result result = dvrSp->configure(getDvrSettings(env, settings));
+ return (int)result;
+}
+
static bool android_media_tv_Tuner_start_dvr(JNIEnv *env, jobject dvr) {
- sp<IDvr> dvrSp = getDvr(env, dvr);
+ sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
if (dvrSp == NULL) {
ALOGD("Failed to start dvr: dvr not found");
return false;
@@ -759,7 +852,7 @@
}
static bool android_media_tv_Tuner_stop_dvr(JNIEnv *env, jobject dvr) {
- sp<IDvr> dvrSp = getDvr(env, dvr);
+ sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
if (dvrSp == NULL) {
ALOGD("Failed to stop dvr: dvr not found");
return false;
@@ -768,7 +861,7 @@
}
static bool android_media_tv_Tuner_flush_dvr(JNIEnv *env, jobject dvr) {
- sp<IDvr> dvrSp = getDvr(env, dvr);
+ sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
if (dvrSp == NULL) {
ALOGD("Failed to flush dvr: dvr not found");
return false;
@@ -785,6 +878,11 @@
(void *)android_media_tv_Tuner_open_frontend_by_id },
{ "nativeTune", "(ILandroid/media/tv/tuner/FrontendSettings;)I",
(void *)android_media_tv_Tuner_tune },
+ { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
+ { "nativeScan", "(ILandroid/media/tv/tuner/FrontendSettings;I)I",
+ (void *)android_media_tv_Tuner_scan },
+ { "nativeSetLnb", "(I)I", (void *)android_media_tv_Tuner_set_lnb },
+ { "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
{ "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;",
(void *)android_media_tv_Tuner_open_filter },
{ "nativeGetLnbIds", "()Ljava/util/List;",
@@ -818,6 +916,8 @@
(void *)android_media_tv_Tuner_attach_filter },
{ "nativeDetachFilter", "(Landroid/media/tv/tuner/Tuner$Filter;)Z",
(void *)android_media_tv_Tuner_detach_filter },
+ { "nativeConfigureDvr", "(Landroid/media/tv/tuner/DvrSettings;)I",
+ (void *)android_media_tv_Tuner_configure_dvr },
{ "nativeStartDvr", "()Z", (void *)android_media_tv_Tuner_start_dvr },
{ "nativeStopDvr", "()Z", (void *)android_media_tv_Tuner_stop_dvr },
{ "nativeFlushDvr", "()Z", (void *)android_media_tv_Tuner_flush_dvr },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 467acb8..d37a2d9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -39,6 +39,7 @@
using ::android::hardware::tv::tuner::V1_0::FrontendId;
using ::android::hardware::tv::tuner::V1_0::FrontendScanMessage;
using ::android::hardware::tv::tuner::V1_0::FrontendScanMessageType;
+using ::android::hardware::tv::tuner::V1_0::FrontendScanType;
using ::android::hardware::tv::tuner::V1_0::FrontendSettings;
using ::android::hardware::tv::tuner::V1_0::IDemux;
using ::android::hardware::tv::tuner::V1_0::IDescrambler;
@@ -77,6 +78,13 @@
jweak mDvr;
};
+struct Dvr : public RefBase {
+ Dvr(sp<IDvr> sp, jweak obj);
+ sp<IDvr> getIDvr();
+ sp<IDvr> mDvrSp;
+ jweak mDvrObj;
+};
+
struct FilterCallback : public IFilterCallback {
virtual Return<void> onFilterEvent(const DemuxFilterEvent& filterEvent);
virtual Return<void> onFilterStatus(const DemuxFilterStatus status);
@@ -115,6 +123,7 @@
jobject getFrontendIds();
jobject openFrontendById(int id);
int tune(const FrontendSettings& settings);
+ int scan(const FrontendSettings& settings, FrontendScanType scanType);
jobject getLnbIds();
jobject openLnbById(int id);
jobject openFilter(DemuxFilterType type, int bufferSize);
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index 41ab670..5ba5c01 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -17,6 +17,7 @@
"libnativehelper",
"libaudioclient",
"libaudioutils",
+ "libaudiofoundation",
],
version_script: "exports.lds",
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index c8f0ff1..79e4d8a 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -38,7 +38,7 @@
// kPlayOnCallingThread = true prior to R.
// Changing to false means calls to play() are almost instantaneous instead of taking around
// ~10ms to launch the AudioTrack. It is perhaps 100x faster.
-static constexpr bool kPlayOnCallingThread = false;
+static constexpr bool kPlayOnCallingThread = true;
// Amount of time for a StreamManager thread to wait before closing.
static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
@@ -170,6 +170,7 @@
if (stream->getSoundID() == soundID) {
ALOGV("%s: found soundID %d in restart queue", __func__, soundID);
newStream = stream;
+ fromAvailableQueue = false;
break;
} else if (newStream == nullptr) {
ALOGV("%s: found stream in restart queue", __func__);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
index 6facec4..41914b8 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
@@ -969,7 +969,7 @@
*/
private static class IntInMatcher extends InMatcher<Integer> {
public IntInMatcher(int[] values) {
- Preconditions.checkNotNull("values", values);
+ Objects.requireNonNull(values, "values");
mValues = new ArrayList<>(values.length);
for (int i : values) {
mValues.add(i);
@@ -1005,7 +1005,7 @@
*/
private static class BooleanInMatcher extends InMatcher<Boolean> {
public BooleanInMatcher(boolean[] values) {
- Preconditions.checkNotNull("values", values);
+ Objects.requireNonNull(values, "values");
mValues = new ArrayList<>(values.length);
for (boolean i : values) {
mValues.add(i);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
index e25a140..c77a042 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
@@ -36,7 +36,7 @@
protected Collection<T> mValues;
public InMatcher(Collection<T> values) {
- Preconditions.checkNotNull("values", values);
+ Objects.requireNonNull(values, "values");
mValues = values;
}
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 6650f96..df6345f 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -20,6 +20,7 @@
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
+import android.media.RouteSessionInfo;
import android.os.Bundle;
import android.os.IBinder;
@@ -159,10 +160,55 @@
publishRoutes();
}
+ @Override
+ public void onCreateSession(String packageName, String routeId, String controlCategory,
+ int sessionId) {
+ RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
+ sessionId, packageName, controlCategory)
+ .addSelectedRoute(routeId)
+ .build();
+ notifySessionCreated(sessionId, sessionInfo, null);
+ }
+
+ @Override
+ public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {}
+
+ @Override
+ public void onAddRoute(int sessionId, String routeId) {
+ RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ //TODO: we may want to remove route if it belongs to another session
+ RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ .addSelectedRoute(routeId)
+ .build();
+ setSessionInfo(sessionId, newSessionInfo);
+ publishRoutes();
+ }
+
+ @Override
+ public void onRemoveRoute(int sessionId, String routeId) {
+ RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ .removeSelectedRoute(routeId)
+ .build();
+ setSessionInfo(sessionId, newSessionInfo);
+ publishRoutes();
+ }
+
+ @Override
+ public void onTransferRoute(int sessionId, String routeId) {
+ RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ .clearSelectedRoutes()
+ .addSelectedRoute(routeId)
+ .build();
+ setSessionInfo(sessionId, newSessionInfo);
+ publishRoutes();
+ }
+
void publishRoutes() {
MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
.addRoutes(mRoutes.values())
.build();
- setProviderInfo(info);
+ updateProviderInfo(info);
}
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 2c60d6b..3266285 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -16,7 +16,15 @@
package com.android.mediaroutertest;
+import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
+import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.media.MediaRoute2Info;
@@ -24,20 +32,37 @@
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MediaRouter2Test {
+ private static final String TAG = "MediaRouter2Test";
Context mContext;
+ private MediaRouter2 mRouter2;
+ private Executor mExecutor;
+
+ private static final int TIMEOUT_MS = 5000;
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
+ mRouter2 = MediaRouter2.getInstance(mContext);
+ mExecutor = Executors.newSingleThreadExecutor();
}
@After
@@ -50,4 +75,95 @@
MediaRoute2Info initiallySelectedRoute = router.getSelectedRoute();
assertNotNull(initiallySelectedRoute);
}
+
+ /**
+ * Tests if we get proper routes for application that has special control category.
+ */
+ @Test
+ public void testGetRoutes() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
+
+ assertEquals(1, routes.size());
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+ }
+
+ @Test
+ public void testControlVolumeWithRouter() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
+
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+ assertNotNull(volRoute);
+
+ int originalVolume = volRoute.getVolume();
+ int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+
+ awaitOnRouteChanged(
+ () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
+ ROUTE_ID_VARIABLE_VOLUME,
+ (route -> route.getVolume() == originalVolume + deltaVolume));
+
+ awaitOnRouteChanged(
+ () -> mRouter2.requestSetVolume(volRoute, originalVolume),
+ ROUTE_ID_VARIABLE_VOLUME,
+ (route -> route.getVolume() == originalVolume));
+ }
+
+
+ // 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)
+ throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+
+ // A dummy callback is required to send control category info.
+ MediaRouter2.Callback routerCallback = new MediaRouter2.Callback() {
+ @Override
+ public void onRoutesAdded(List<MediaRoute2Info> routes) {
+ for (int i = 0; i < routes.size(); i++) {
+ //TODO: use isSystem() or similar method when it's ready
+ if (!TextUtils.equals(routes.get(i).getProviderId(), SYSTEM_PROVIDER_ID)) {
+ latch.countDown();
+ }
+ }
+ }
+ };
+
+ mRouter2.setControlCategories(controlCategories);
+ mRouter2.registerCallback(mExecutor, routerCallback);
+ try {
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return createRouteMap(mRouter2.getRoutes());
+ } finally {
+ mRouter2.unregisterCallback(routerCallback);
+ }
+ }
+
+ void awaitOnRouteChanged(Runnable task, String routeId,
+ Predicate<MediaRoute2Info> predicate) throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaRouter2.Callback callback = new MediaRouter2.Callback() {
+ @Override
+ public void onRoutesChanged(List<MediaRoute2Info> changed) {
+ MediaRoute2Info route = createRouteMap(changed).get(routeId);
+ if (route != null && predicate.test(route)) {
+ latch.countDown();
+ }
+ }
+ };
+ mRouter2.registerCallback(mExecutor, callback);
+ try {
+ task.run();
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mRouter2.unregisterCallback(callback);
+ }
+ }
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index c70ad8d..2772aa4 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -64,6 +64,9 @@
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 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_NAME_FIXED_VOLUME = "Fixed Volume Route";
@@ -78,10 +81,7 @@
public static final String CATEGORY_SPECIAL =
"com.android.mediarouteprovider.CATEGORY_SPECIAL";
- // system routes
- private static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
private static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
- private static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
private static final int TIMEOUT_MS = 5000;
@@ -93,10 +93,9 @@
private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>();
- private Map<String, MediaRoute2Info> mRoutes;
- private static final List<String> CATEGORIES_ALL = new ArrayList();
- private static final List<String> CATEGORIES_SPECIAL = 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<>();
static {
@@ -109,7 +108,6 @@
CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
}
-
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
@@ -118,10 +116,6 @@
//TODO: If we need to support thread pool executors, change this to thread pool executor.
mExecutor = Executors.newSingleThreadExecutor();
mPackageName = mContext.getPackageName();
-
- // ensure media router 2 client
- addRouterCallback(new MediaRouter2.Callback());
- mRoutes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
}
@After
@@ -168,6 +162,9 @@
@Test
public void testOnRoutesRemoved() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
+ addRouterCallback(new MediaRouter2.Callback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRoutesRemoved(List<MediaRoute2Info> routes) {
@@ -182,7 +179,7 @@
//TODO: Figure out a more proper way to test.
// (Control requests shouldn't be used in this way.)
- mRouter2.sendControlRequest(mRoutes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
+ mRouter2.sendControlRequest(routes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@@ -198,23 +195,15 @@
}
/**
- * Tests if we get proper routes for application that has special control category.
- */
- @Test
- public void testGetRoutes() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
-
- assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
- }
-
- /**
* Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager.
*/
@Test
public void testRouterOnRouteSelected() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
CountDownLatch latch = new CountDownLatch(1);
+ addManagerCallback(new MediaRouter2Manager.Callback());
addRouterCallback(new MediaRouter2.Callback() {
@Override
public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
@@ -224,12 +213,16 @@
}
});
- MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+ MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertNotNull(routeToSelect);
- mManager.selectRoute(mPackageName, routeToSelect);
+ try {
+ mManager.selectRoute(mPackageName, routeToSelect);
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mManager.unselectRoute(mPackageName);
+ }
}
/**
@@ -239,7 +232,9 @@
@Test
public void testManagerOnRouteSelected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ addRouterCallback(new MediaRouter2.Callback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
public void onRouteSelected(String packageName, MediaRoute2Info route) {
@@ -250,12 +245,46 @@
}
});
- MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+ MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
assertNotNull(routeToSelect);
- mManager.selectRoute(mPackageName, routeToSelect);
+ try {
+ mManager.selectRoute(mPackageName, routeToSelect);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mManager.unselectRoute(mPackageName);
+ }
+ }
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ @Test
+ public void testGetActiveRoutes() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ CountDownLatch latch2 = new CountDownLatch(1);
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ addRouterCallback(new MediaRouter2.Callback());
+ addManagerCallback(new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRouteSelected(String packageName, MediaRoute2Info route) {
+ if (TextUtils.equals(mPackageName, packageName)
+ && route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
+ latch.countDown();
+ }
+ }
+ });
+
+ assertEquals(0, mManager.getActiveRoutes().size());
+
+ mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ assertEquals(1, mManager.getActiveRoutes().size());
+
+ awaitOnRouteChangedManager(
+ () -> mManager.unselectRoute(mPackageName),
+ ROUTE_ID1,
+ route -> TextUtils.equals(route.getClientPackageName(), null));
+ assertEquals(0, mManager.getActiveRoutes().size());
}
/**
@@ -263,13 +292,16 @@
*/
@Test
public void testSingleProviderSelect() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ addRouterCallback(new MediaRouter2.Callback());
+
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID1)),
+ () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
ROUTE_ID1,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
awaitOnRouteChangedManager(
- () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID2)),
+ () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)),
ROUTE_ID2,
route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
@@ -280,27 +312,10 @@
}
@Test
- public void testControlVolumeWithRouter() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
-
- MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
- int originalVolume = volRoute.getVolume();
- int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
-
- awaitOnRouteChanged(
- () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
- ROUTE_ID_VARIABLE_VOLUME,
- (route -> route.getVolume() == originalVolume + deltaVolume));
-
- awaitOnRouteChanged(
- () -> mRouter2.requestSetVolume(volRoute, originalVolume),
- ROUTE_ID_VARIABLE_VOLUME,
- (route -> route.getVolume() == originalVolume));
- }
-
- @Test
public void testControlVolumeWithManager() throws Exception {
- MediaRoute2Info volRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+
int originalVolume = volRoute.getVolume();
int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
@@ -317,39 +332,16 @@
@Test
public void testVolumeHandling() throws Exception {
- MediaRoute2Info fixedVolumeRoute = mRoutes.get(ROUTE_ID_FIXED_VOLUME);
- MediaRoute2Info variableVolumeRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
+ MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
+ MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling());
assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling());
assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
}
- @Test
- public void testDefaultRoute() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_LIVE_AUDIO);
-
- assertNotNull(routes.get(DEFAULT_ROUTE_ID));
- }
-
- Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories) throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
- MediaRouter2.Callback callback = new MediaRouter2.Callback() {
- @Override
- public void onRoutesAdded(List<MediaRoute2Info> added) {
- if (added.size() > 0) latch.countDown();
- }
- };
- mRouter2.setControlCategories(controlCategories);
- mRouter2.registerCallback(mExecutor, callback);
- try {
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- return createRouteMap(mRouter2.getRoutes());
- } finally {
- mRouter2.unregisterCallback(callback);
- }
- }
-
Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
throws Exception {
CountDownLatch latch = new CountDownLatch(2);
@@ -359,13 +351,17 @@
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
- if (routes.size() > 0) {
- latch.countDown();
+ for (int i = 0; i < routes.size(); i++) {
+ //TODO: use isSystem() or similar method when it's ready
+ if (!TextUtils.equals(routes.get(i).getProviderId(), SYSTEM_PROVIDER_ID)) {
+ latch.countDown();
+ break;
+ }
}
}
@Override
- public void onControlCategoriesChanged(String packageName) {
+ public void onControlCategoriesChanged(String packageName, List<String> categories) {
if (TextUtils.equals(mPackageName, packageName)) {
latch.countDown();
}
@@ -375,7 +371,7 @@
mRouter2.setControlCategories(controlCategories);
mRouter2.registerCallback(mExecutor, routerCallback);
try {
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mManager.getAvailableRoutes(mPackageName));
} finally {
mRouter2.unregisterCallback(routerCallback);
@@ -383,27 +379,6 @@
}
}
- void awaitOnRouteChanged(Runnable task, String routeId,
- Predicate<MediaRoute2Info> predicate) throws Exception {
- CountDownLatch latch = new CountDownLatch(1);
- MediaRouter2.Callback callback = new MediaRouter2.Callback() {
- @Override
- public void onRoutesChanged(List<MediaRoute2Info> changed) {
- MediaRoute2Info route = createRouteMap(changed).get(routeId);
- if (route != null && predicate.test(route)) {
- latch.countDown();
- }
- }
- };
- mRouter2.registerCallback(mExecutor, callback);
- try {
- task.run();
- assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } finally {
- mRouter2.unregisterCallback(callback);
- }
- }
-
void awaitOnRouteChangedManager(Runnable task, String routeId,
Predicate<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
new file mode 100644
index 0000000..2e81a64
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 177f2b8..203adfc 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -40,6 +40,7 @@
AConfiguration_getOrientation;
AConfiguration_getScreenHeightDp; # introduced-arm=13 introduced-arm64=21 introduced-mips=13 introduced-mips64=21 introduced-x86=13 introduced-x86_64=21
AConfiguration_getScreenLong;
+ AConfiguration_getScreenRound; # introduced=30
AConfiguration_getScreenSize;
AConfiguration_getScreenWidthDp; # introduced-arm=13 introduced-arm64=21 introduced-mips=13 introduced-mips64=21 introduced-x86=13 introduced-x86_64=21
AConfiguration_getSdkVersion;
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index b2451c9..2a8a39a 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -53,7 +53,6 @@
],
libs: [
- "telephony-common",
"android.car",
],
@@ -108,7 +107,6 @@
],
libs: [
"android.test.runner",
- "telephony-common",
"android.test.base",
"android.car",
],
@@ -129,7 +127,6 @@
],
libs: [
- "telephony-common",
"android.car",
],
@@ -140,7 +137,7 @@
],
platform_apis: true,
- product_specific: true,
+ system_ext_specific: true,
certificate: "platform",
privileged: true,
diff --git a/packages/Tethering/CleanSpec.mk b/packages/CarSystemUI/CleanSpec.mk
similarity index 93%
copy from packages/Tethering/CleanSpec.mk
copy to packages/CarSystemUI/CleanSpec.mk
index 70db351..ceac67c 100644
--- a/packages/Tethering/CleanSpec.mk
+++ b/packages/CarSystemUI/CleanSpec.mk
@@ -43,10 +43,8 @@
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
-
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Tethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
-
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/CarSystemUI)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/CarSystemUI)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index bffc6b9..b862e95 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -21,6 +21,7 @@
import android.content.Context;
+import com.android.systemui.car.CarDeviceProvisionedControllerImpl;
import com.android.systemui.car.CarNotificationEntryManager;
import com.android.systemui.car.CarNotificationInterruptionStateProvider;
import com.android.systemui.dagger.SystemUIRootComponent;
@@ -35,6 +36,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
+import com.android.systemui.statusbar.car.CarShadeControllerImpl;
import com.android.systemui.statusbar.car.CarStatusBar;
import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -45,6 +47,7 @@
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.volume.CarVolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogComponent;
@@ -119,7 +122,7 @@
KeyguardEnvironmentImpl keyguardEnvironment);
@Binds
- abstract ShadeController provideShadeController(CarStatusBar statusBar);
+ abstract ShadeController provideShadeController(CarShadeControllerImpl shadeController);
@Provides
@Singleton
@@ -142,4 +145,8 @@
@Binds
abstract StatusBarKeyguardViewManager bindStatusBarKeyguardViewManager(
CarStatusBarKeyguardViewManager keyguardViewManager);
+
+ @Binds
+ abstract DeviceProvisionedController bindDeviceProvisionedController(
+ CarDeviceProvisionedControllerImpl deviceProvisionedController);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java
new file mode 100644
index 0000000..c870cec
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java
@@ -0,0 +1,37 @@
+/*
+ * 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.car;
+
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+/**
+ * This interface defines controller that monitors the status of SUW progress for each user in
+ * addition to the functionality defined by {@link DeviceProvisionedController}.
+ */
+public interface CarDeviceProvisionedController extends DeviceProvisionedController {
+ /**
+ * Returns {@code true} then SUW is in progress for the given user.
+ */
+ boolean isUserSetupInProgress(int user);
+
+ /**
+ * Returns {@code true} then SUW is in progress for the current user.
+ */
+ default boolean isCurrentUserSetupInProgress() {
+ return isUserSetupInProgress(getCurrentUser());
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
new file mode 100644
index 0000000..38d5211b
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedControllerImpl.java
@@ -0,0 +1,116 @@
+/*
+ * 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.car;
+
+import android.app.ActivityManager;
+import android.car.settings.CarSettings;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A controller that monitors the status of SUW progress for each user in addition to the
+ * functionality provided by {@link DeviceProvisionedControllerImpl}.
+ */
+@Singleton
+public class CarDeviceProvisionedControllerImpl extends DeviceProvisionedControllerImpl implements
+ CarDeviceProvisionedController {
+ private static final Uri USER_SETUP_IN_PROGRESS_URI = Settings.Secure.getUriFor(
+ CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS);
+ private final ContentObserver mCarSettingsObserver = new ContentObserver(
+ Dependency.get(Dependency.MAIN_HANDLER)) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ if (USER_SETUP_IN_PROGRESS_URI.equals(uri)) {
+ notifyUserSetupInProgressChanged();
+ }
+ }
+ };
+ private final ContentResolver mContentResolver;
+
+ @Inject
+ public CarDeviceProvisionedControllerImpl(Context context, @Main Handler mainHandler,
+ BroadcastDispatcher broadcastDispatcher) {
+ super(context, mainHandler, broadcastDispatcher);
+ mContentResolver = context.getContentResolver();
+ }
+
+ @Override
+ public boolean isUserSetupInProgress(int user) {
+ return Settings.Secure.getIntForUser(mContentResolver,
+ CarSettings.Secure.KEY_SETUP_WIZARD_IN_PROGRESS, /* def= */ 0, user) != 0;
+ }
+
+ @Override
+ public boolean isCurrentUserSetupInProgress() {
+ return isUserSetupInProgress(ActivityManager.getCurrentUser());
+ }
+
+ @Override
+ public void addCallback(DeviceProvisionedListener listener) {
+ super.addCallback(listener);
+ if (listener instanceof CarDeviceProvisionedListener) {
+ ((CarDeviceProvisionedListener) listener).onUserSetupInProgressChanged();
+ }
+ }
+
+ @Override
+ protected void startListening(int user) {
+ mContentResolver.registerContentObserver(
+ USER_SETUP_IN_PROGRESS_URI, /* notifyForDescendants= */ true, mCarSettingsObserver,
+ user);
+ // The SUW Flag observer is registered before super.startListening() so that the observer is
+ // in place before DeviceProvisionedController starts to track user switches which avoids
+ // an edge case where our observer gets registered twice.
+ super.startListening(user);
+ }
+
+ @Override
+ protected void stopListening() {
+ super.stopListening();
+ mContentResolver.unregisterContentObserver(mCarSettingsObserver);
+ }
+
+ @Override
+ public void onUserSwitched(int newUserId) {
+ super.onUserSwitched(newUserId);
+ mContentResolver.unregisterContentObserver(mCarSettingsObserver);
+ mContentResolver.registerContentObserver(
+ USER_SETUP_IN_PROGRESS_URI, /* notifyForDescendants= */ true, mCarSettingsObserver,
+ newUserId);
+ }
+
+ private void notifyUserSetupInProgressChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ DeviceProvisionedListener listener = mListeners.get(i);
+ if (listener instanceof CarDeviceProvisionedListener) {
+ ((CarDeviceProvisionedListener) listener).onUserSetupInProgressChanged();
+ }
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedListener.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedListener.java
new file mode 100644
index 0000000..0086322
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedListener.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.systemui.car;
+
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
+/**
+ * A listener that listens for changes in SUW progress for a user in addition to the
+ * functionality defined by {@link DeviceProvisionedListener}.
+ */
+public interface CarDeviceProvisionedListener extends DeviceProvisionedListener {
+ @Override
+ default void onUserSwitched() {
+ onUserSetupChanged();
+ onUserSetupInProgressChanged();
+ }
+ /**
+ * A callback for when a change occurs in SUW progress for a user.
+ */
+ default void onUserSetupInProgressChanged() {
+ }
+}
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 08ab492..59a084e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -33,7 +33,9 @@
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarDeviceProvisionedListener;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NavigationBarController;
@@ -53,7 +55,7 @@
private final CarNavigationBarController mCarNavigationBarController;
private final WindowManager mWindowManager;
- private final DeviceProvisionedController mDeviceProvisionedController;
+ private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final CommandQueue mCommandQueue;
private final Lazy<FacetButtonTaskStackListener> mFacetButtonTaskStackListenerLazy;
private final Handler mMainHandler;
@@ -82,6 +84,7 @@
// To be attached to the navigation bars such that they can close the notification panel if
// it's open.
private boolean mDeviceIsSetUpForUser = true;
+ private boolean mIsUserSetupInProgress = false;
@Inject
public CarNavigationBar(Context context,
@@ -90,7 +93,7 @@
DeviceProvisionedController deviceProvisionedController,
CommandQueue commandQueue,
Lazy<FacetButtonTaskStackListener> facetButtonTaskStackListenerLazy,
- @MainHandler Handler mainHandler,
+ @Main Handler mainHandler,
Lazy<KeyguardStateController> keyguardStateControllerLazy,
Lazy<NavigationBarController> navigationBarControllerLazy,
SuperStatusBarViewFactory superStatusBarViewFactory,
@@ -98,7 +101,8 @@
super(context);
mCarNavigationBarController = carNavigationBarController;
mWindowManager = windowManager;
- mDeviceProvisionedController = deviceProvisionedController;
+ mCarDeviceProvisionedController = (CarDeviceProvisionedController)
+ deviceProvisionedController;
mCommandQueue = commandQueue;
mFacetButtonTaskStackListenerLazy = facetButtonTaskStackListenerLazy;
mMainHandler = mainHandler;
@@ -129,9 +133,15 @@
ex.rethrowFromSystemServer();
}
- mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
- mDeviceProvisionedController.addCallback(
- new DeviceProvisionedController.DeviceProvisionedListener() {
+ mDeviceIsSetUpForUser = mCarDeviceProvisionedController.isCurrentUserSetup();
+ mIsUserSetupInProgress = mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
+ mCarDeviceProvisionedController.addCallback(
+ new CarDeviceProvisionedListener() {
+ @Override
+ public void onUserSetupInProgressChanged() {
+ mMainHandler.post(() -> restartNavBarsIfNecessary());
+ }
+
@Override
public void onUserSetupChanged() {
mMainHandler.post(() -> restartNavBarsIfNecessary());
@@ -152,9 +162,13 @@
}
private void restartNavBarsIfNecessary() {
- boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
- if (mDeviceIsSetUpForUser != currentUserSetup) {
+ boolean currentUserSetup = mCarDeviceProvisionedController.isCurrentUserSetup();
+ boolean currentUserSetupInProgress = mCarDeviceProvisionedController
+ .isCurrentUserSetupInProgress();
+ if (mIsUserSetupInProgress != currentUserSetupInProgress
+ || mDeviceIsSetUpForUser != currentUserSetup) {
mDeviceIsSetUpForUser = currentUserSetup;
+ mIsUserSetupInProgress = currentUserSetupInProgress;
restartNavBars();
}
}
@@ -193,10 +207,14 @@
// If the UI was rebuilt (day/night change) while the keyguard was up we need to
// correctly respect that state.
if (mKeyguardStateControllerLazy.get().isShowing()) {
- mCarNavigationBarController.showAllKeyguardButtons(mDeviceIsSetUpForUser);
+ mCarNavigationBarController.showAllKeyguardButtons(isDeviceSetupForUser());
}
}
+ private boolean isDeviceSetupForUser() {
+ return mDeviceIsSetUpForUser && !mIsUserSetupInProgress;
+ }
+
private void createNavigationBar(RegisterStatusBarResult result) {
buildNavBarWindows();
buildNavBarContent();
@@ -225,22 +243,22 @@
}
private void buildNavBarContent() {
- mTopNavigationBarView = mCarNavigationBarController.getTopBar(mDeviceIsSetUpForUser);
+ mTopNavigationBarView = mCarNavigationBarController.getTopBar(isDeviceSetupForUser());
if (mTopNavigationBarView != null) {
mTopNavigationBarWindow.addView(mTopNavigationBarView);
}
- mBottomNavigationBarView = mCarNavigationBarController.getBottomBar(mDeviceIsSetUpForUser);
+ mBottomNavigationBarView = mCarNavigationBarController.getBottomBar(isDeviceSetupForUser());
if (mBottomNavigationBarView != null) {
mBottomNavigationBarWindow.addView(mBottomNavigationBarView);
}
- mLeftNavigationBarView = mCarNavigationBarController.getLeftBar(mDeviceIsSetUpForUser);
+ mLeftNavigationBarView = mCarNavigationBarController.getLeftBar(isDeviceSetupForUser());
if (mLeftNavigationBarView != null) {
mLeftNavigationBarWindow.addView(mLeftNavigationBarView);
}
- mRightNavigationBarView = mCarNavigationBarController.getRightBar(mDeviceIsSetUpForUser);
+ mRightNavigationBarView = mCarNavigationBarController.getRightBar(isDeviceSetupForUser());
if (mRightNavigationBarView != null) {
mRightNavigationBarWindow.addView(mRightNavigationBarView);
}
@@ -278,6 +296,7 @@
leftlp.windowAnimations = 0;
leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
leftlp.gravity = Gravity.LEFT;
+ leftlp.setFitWindowInsetsTypes(0 /* types */);
mWindowManager.addView(mLeftNavigationBarWindow, leftlp);
}
if (mRightNavigationBarWindow != null) {
@@ -295,6 +314,7 @@
rightlp.windowAnimations = 0;
rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
rightlp.gravity = Gravity.RIGHT;
+ rightlp.setFitWindowInsetsTypes(0 /* types */);
mWindowManager.addView(mRightNavigationBarWindow, rightlp);
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java
new file mode 100644
index 0000000..d1d352a
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.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.systemui.statusbar.car;
+
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.car.notification.CarNotificationView;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.ShadeControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/** Car specific implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
+@Singleton
+public class CarShadeControllerImpl extends ShadeControllerImpl {
+
+ @Inject
+ public CarShadeControllerImpl(CommandQueue commandQueue,
+ StatusBarStateController statusBarStateController,
+ StatusBarWindowController statusBarWindowController,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ WindowManager windowManager,
+ Lazy<StatusBar> statusBarLazy,
+ Lazy<AssistManager> assistManagerLazy,
+ Lazy<BubbleController> bubbleControllerLazy) {
+ super(commandQueue, statusBarStateController, statusBarWindowController,
+ statusBarKeyguardViewManager, windowManager,
+ statusBarLazy, assistManagerLazy, bubbleControllerLazy);
+ }
+
+ @Override
+ public void animateCollapsePanels(int flags, boolean force, boolean delayed,
+ float speedUpFactor) {
+ super.animateCollapsePanels(flags, force, delayed, speedUpFactor);
+ if (!getCarStatusBar().isPanelExpanded()
+ || getCarNotificationView().getVisibility() == View.INVISIBLE) {
+ return;
+ }
+
+ mStatusBarWindowController.setStatusBarFocusable(false);
+ getCarStatusBar().getStatusBarWindowViewController().cancelExpandHelper();
+ getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
+
+ getCarStatusBar().animateNotificationPanel(getCarStatusBar().getClosingVelocity(), true);
+
+ if (!getCarStatusBar().isTracking()) {
+ mStatusBarWindowController.setPanelVisible(false);
+ getCarNotificationView().setVisibility(View.INVISIBLE);
+ }
+
+ getCarStatusBar().setPanelExpanded(false);
+ }
+
+ private CarStatusBar getCarStatusBar() {
+ return (CarStatusBar) mStatusBarLazy.get();
+ }
+
+ private CarNotificationView getCarNotificationView() {
+ return getCarStatusBar().getCarNotificationView();
+ }
+}
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 c8532e0..867100f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -59,13 +59,15 @@
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarDeviceProvisionedListener;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -118,6 +120,7 @@
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
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.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarComponent;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -138,6 +141,7 @@
import java.io.PrintWriter;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Named;
import javax.inject.Provider;
@@ -174,10 +178,14 @@
private final Object mQueueLock = new Object();
private final CarNavigationBarController mCarNavigationBarController;
+ private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy;
+ private final ShadeController mShadeController;
private final CarServiceProvider mCarServiceProvider;
+ private final CarDeviceProvisionedController mCarDeviceProvisionedController;
- private DeviceProvisionedController mDeviceProvisionedController;
+ private boolean mDeviceIsSetUpForUser = true;
+ private boolean mIsUserSetupInProgress = false;
private PowerManagerHelper mPowerManagerHelper;
private FlingAnimationUtils mFlingAnimationUtils;
private NotificationDataManager mNotificationDataManager;
@@ -266,7 +274,7 @@
NotificationAlertingManager notificationAlertingManager,
DisplayMetrics displayMetrics,
MetricsLogger metricsLogger,
- UiOffloadThread uiOffloadThread,
+ @UiBackground Executor uiBgExecutor,
NotificationMediaManager notificationMediaManager,
NotificationLockscreenUserManager lockScreenUserManager,
NotificationRemoteInputManager remoteInputManager,
@@ -308,6 +316,7 @@
LightsOutNotifController lightsOutNotifController,
StatusBarNotificationActivityStarter.Builder
statusBarNotificationActivityStarterBuilder,
+ ShadeController shadeController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ViewMediatorCallback viewMediatorCallback,
DismissCallbackRegistry dismissCallbackRegistry,
@@ -315,7 +324,8 @@
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy,
- CarNavigationBarController carNavigationBarController) {
+ CarNavigationBarController carNavigationBarController,
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
super(
context,
featureFlags,
@@ -344,7 +354,7 @@
notificationAlertingManager,
displayMetrics,
metricsLogger,
- uiOffloadThread,
+ uiBgExecutor,
notificationMediaManager,
lockScreenUserManager,
remoteInputManager,
@@ -385,21 +395,28 @@
dividerOptional,
lightsOutNotifController,
statusBarNotificationActivityStarterBuilder,
+ shadeController,
superStatusBarViewFactory,
statusBarKeyguardViewManager,
viewMediatorCallback,
dismissCallbackRegistry);
mScrimController = scrimController;
mLockscreenLockIconController = lockscreenLockIconController;
- mDeviceProvisionedController = deviceProvisionedController;
+ mCarDeviceProvisionedController =
+ (CarDeviceProvisionedController) deviceProvisionedController;
+ mShadeController = shadeController;
mCarServiceProvider = carServiceProvider;
mPowerManagerHelperLazy = powerManagerHelperLazy;
mFullscreenUserSwitcherLazy = fullscreenUserSwitcherLazy;
mCarNavigationBarController = carNavigationBarController;
+ mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
}
@Override
public void start() {
+ mDeviceIsSetUpForUser = mCarDeviceProvisionedController.isCurrentUserSetup();
+ mIsUserSetupInProgress = mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
+
// Need to initialize screen lifecycle before calling super.start - before switcher is
// created.
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -431,8 +448,10 @@
R.integer.notification_settle_open_percentage);
mSettleClosePercentage = mContext.getResources().getInteger(
R.integer.notification_settle_close_percentage);
- mFlingAnimationUtils = new FlingAnimationUtils(mContext,
- FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR);
+ mFlingAnimationUtils = mFlingAnimationUtilsBuilder
+ .setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
+ .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
+ .build();
createBatteryController();
mCarBatteryController.startListening();
@@ -440,6 +459,33 @@
mPowerManagerHelper = mPowerManagerHelperLazy.get();
mPowerManagerHelper.setCarPowerStateListener(mCarPowerStateListener);
mPowerManagerHelper.connectToCarService();
+
+ mCarDeviceProvisionedController.addCallback(
+ new CarDeviceProvisionedListener() {
+ @Override
+ public void onUserSetupInProgressChanged() {
+ mDeviceIsSetUpForUser = mCarDeviceProvisionedController
+ .isCurrentUserSetup();
+ mIsUserSetupInProgress = mCarDeviceProvisionedController
+ .isCurrentUserSetupInProgress();
+ }
+
+ @Override
+ public void onUserSetupChanged() {
+ mDeviceIsSetUpForUser = mCarDeviceProvisionedController
+ .isCurrentUserSetup();
+ mIsUserSetupInProgress = mCarDeviceProvisionedController
+ .isCurrentUserSetupInProgress();
+ }
+
+ @Override
+ public void onUserSwitched() {
+ mDeviceIsSetUpForUser = mCarDeviceProvisionedController
+ .isCurrentUserSetup();
+ mIsUserSetupInProgress = mCarDeviceProvisionedController
+ .isCurrentUserSetupInProgress();
+ }
+ });
}
/**
@@ -455,16 +501,18 @@
@Override
public boolean hideKeyguard() {
boolean result = super.hideKeyguard();
- mCarNavigationBarController.hideAllKeyguardButtons(
- mDeviceProvisionedController.isCurrentUserSetup());
+ mCarNavigationBarController.hideAllKeyguardButtons(isDeviceSetupForUser());
return result;
}
@Override
public void showKeyguard() {
super.showKeyguard();
- mCarNavigationBarController.showAllKeyguardButtons(
- mDeviceProvisionedController.isCurrentUserSetup());
+ mCarNavigationBarController.showAllKeyguardButtons(isDeviceSetupForUser());
+ }
+
+ private boolean isDeviceSetupForUser() {
+ return mDeviceIsSetUpForUser && !mIsUserSetupInProgress;
}
@Override
@@ -506,7 +554,7 @@
@Override
protected void close() {
if (mPanelExpanded) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
}
}
});
@@ -516,7 +564,7 @@
@Override
protected void close() {
if (mPanelExpanded) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
}
}
});
@@ -526,7 +574,7 @@
new HandleBarCloseNotificationGestureListener());
mTopNavBarNotificationTouchListener = (v, event) -> {
- if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+ if (!isDeviceSetupForUser()) {
return true;
}
boolean consumed = openGestureDetector.onTouchEvent(event);
@@ -551,14 +599,19 @@
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
if (launchResult == ActivityManager.START_TASK_TO_FRONT
|| launchResult == ActivityManager.START_SUCCESS) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
}
});
CarNotificationListener carNotificationListener = new CarNotificationListener();
mCarUxRestrictionManagerWrapper = new CarUxRestrictionManagerWrapper();
mNotificationDataManager = new NotificationDataManager();
- mNotificationDataManager.setOnUnseenCountUpdateListener(this::onUnseenCountUpdate);
+
+ mNotificationDataManager.setOnUnseenCountUpdateListener(() -> {
+ if (mNotificationDataManager != null) {
+ onUseenCountUpdate(mNotificationDataManager.getUnseenNotificationCount());
+ }
+ });
mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean(
R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
@@ -680,15 +733,13 @@
}
/**
- * This method is called whenever there is an update to the number of unseen notifications.
- * This method can be extended by OEMs to customize the desired logic.
+ * This method is automatically called whenever there is an update to the number of unseen
+ * notifications. This method can be extended by OEMs to customize the desired logic.
*/
- protected void onUnseenCountUpdate() {
- if (mNotificationDataManager != null) {
- boolean hasUnseen = mNotificationDataManager.getUnseenNotificationCount() > 0;
- mCarNavigationBarController.toggleAllNotificationsUnseenIndicator(
- mDeviceProvisionedController.isCurrentUserSetup(), hasUnseen);
- }
+ protected void onUseenCountUpdate(int unseenNotificationCount) {
+ boolean hasUnseen = unseenNotificationCount > 0;
+ mCarNavigationBarController.toggleAllNotificationsUnseenIndicator(isDeviceSetupForUser(),
+ hasUnseen);
}
/**
@@ -712,25 +763,16 @@
setPanelExpanded(true);
}
- @Override
- public void animateCollapsePanels(int flags, boolean force, boolean delayed,
- float speedUpFactor) {
- super.animateCollapsePanels(flags, force, delayed, speedUpFactor);
- if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
- return;
- }
- mStatusBarWindowController.setStatusBarFocusable(false);
- mStatusBarWindowViewController.cancelExpandHelper();
- mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
+ public CarNotificationView getCarNotificationView() {
+ return mNotificationView;
+ }
- animateNotificationPanel(mClosingVelocity, true);
+ public float getClosingVelocity() {
+ return mClosingVelocity;
+ }
- if (!mIsTracking) {
- mStatusBarWindowController.setPanelVisible(false);
- mNotificationView.setVisibility(View.INVISIBLE);
- }
-
- setPanelExpanded(false);
+ public boolean isTracking() {
+ return mIsTracking;
}
private void maybeCompleteAnimation(MotionEvent event) {
@@ -749,7 +791,7 @@
* close the notification shade completely with a velocity. If the animation is to close the
* notification shade this method also makes the view invisible after animation ends.
*/
- private void animateNotificationPanel(float velocity, boolean isClosing) {
+ void animateNotificationPanel(float velocity, boolean isClosing) {
float to = 0;
if (!isClosing) {
to = mNotificationView.getHeight();
@@ -1263,8 +1305,10 @@
@Override
protected void setHeadsUpVisible() {
- // if the Notifications panel is showing don't show the Heads up
- if (!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded) {
+ // if the Notifications panel is showing or SUW for user is in progress then don't show
+ // heads up notifications
+ if ((!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded)
+ || !isDeviceSetupForUser()) {
return;
}
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 eff60fa..e3bb293 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -25,12 +25,12 @@
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -43,6 +43,7 @@
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -78,6 +79,7 @@
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
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;
@@ -94,6 +96,7 @@
import com.android.systemui.volume.VolumeComponent;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Named;
import javax.inject.Provider;
@@ -141,7 +144,7 @@
NotificationAlertingManager notificationAlertingManager,
DisplayMetrics displayMetrics,
MetricsLogger metricsLogger,
- UiOffloadThread uiOffloadThread,
+ @UiBackground Executor uiBgExecutor,
NotificationMediaManager notificationMediaManager,
NotificationLockscreenUserManager lockScreenUserManager,
NotificationRemoteInputManager remoteInputManager,
@@ -183,13 +186,15 @@
LightsOutNotifController lightsOutNotifController,
StatusBarNotificationActivityStarter.Builder
statusBarNotificationActivityStarterBuilder,
+ ShadeController shadeController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ViewMediatorCallback viewMediatorCallback,
DismissCallbackRegistry dismissCallbackRegistry,
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy,
- CarNavigationBarController carNavigationBarController) {
+ CarNavigationBarController carNavigationBarController,
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
return new CarStatusBar(
context,
featureFlags,
@@ -218,7 +223,7 @@
notificationAlertingManager,
displayMetrics,
metricsLogger,
- uiOffloadThread,
+ uiBgExecutor,
notificationMediaManager,
lockScreenUserManager,
remoteInputManager,
@@ -259,12 +264,14 @@
superStatusBarViewFactory,
lightsOutNotifController,
statusBarNotificationActivityStarterBuilder,
+ shadeController,
statusBarKeyguardViewManager,
viewMediatorCallback,
dismissCallbackRegistry,
carServiceProvider,
powerManagerHelperLazy,
fullscreenUserSwitcherLazy,
- carNavigationBarController);
+ carNavigationBarController,
+ flingAnimationUtilsBuilder);
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
index ec46433..b2f8aad 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
@@ -37,7 +37,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -68,7 +68,7 @@
private OnHideListener mOnHideListener;
@Inject
- CarTrustAgentUnlockDialogHelper(Context context, @MainResources Resources resources,
+ CarTrustAgentUnlockDialogHelper(Context context, @Main Resources resources,
UserManager userManager, WindowManager windowManager) {
mContext = context;
mResources = resources;
@@ -240,7 +240,7 @@
}
private WindowManager.LayoutParams createLayoutParams() {
- return new WindowManager.LayoutParams(
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
@@ -249,6 +249,8 @@
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
PixelFormat.TRANSLUCENT
);
+ attrs.setFitWindowInsetsTypes(0 /* types */);
+ return attrs;
}
private void logd(String message) {
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 291cdd5..f8fc3bb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -38,7 +38,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.car.CarServiceProvider;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener;
import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
@@ -78,7 +78,7 @@
@Inject
public FullscreenUserSwitcher(
Context context,
- @MainResources Resources resources,
+ @Main Resources resources,
UserManager userManager,
CarServiceProvider carServiceProvider,
CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper) {
@@ -131,10 +131,7 @@
private void showDialogForInitialUser() {
int initialUser = mCarUserManagerHelper.getInitialUser();
UserInfo initialUserInfo = mUserManager.getUserInfo(initialUser);
- mSelectedUser = new UserRecord(initialUserInfo,
- /* isStartGuestSession= */ false,
- /* isAddUser= */ false,
- /* isForeground= */ true);
+ mSelectedUser = new UserRecord(initialUserInfo, UserRecord.FOREGROUND_USER);
// If the initial user has screen lock and trusted device, display the unlock dialog on the
// keyguard.
@@ -180,12 +177,14 @@
*/
private void onUserSelected(UserGridRecyclerView.UserRecord record) {
mSelectedUser = record;
- if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) {
- mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
- return;
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id);
+ if (record.mInfo != null) {
+ if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) {
+ mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
+ return;
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id);
+ }
}
dismissUserSwitcher();
}
@@ -195,7 +194,7 @@
Log.e(TAG, "Request to dismiss user switcher, but no user selected");
return;
}
- if (mSelectedUser.mIsForeground) {
+ if (mSelectedUser.mType == UserRecord.FOREGROUND_USER) {
hide();
mStatusBar.dismissKeyguard();
return;
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 0a5f80f..cdabeeb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -21,6 +21,7 @@
import static android.os.UserManager.DISALLOW_ADD_USER;
import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AlertDialog;
@@ -54,6 +55,8 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -142,8 +145,8 @@
}
boolean isForeground = fgUserId == userInfo.id;
- UserRecord record = new UserRecord(userInfo, false /* isStartGuestSession */,
- false /* isAddUser */, isForeground);
+ UserRecord record = new UserRecord(userInfo,
+ isForeground ? UserRecord.FOREGROUND_USER : UserRecord.BACKGROUND_USER);
userRecords.add(record);
}
@@ -160,27 +163,21 @@
private UserRecord createForegroundUserRecord() {
return new UserRecord(mUserManager.getUserInfo(ActivityManager.getCurrentUser()),
- false /* isStartGuestSession */, false /* isAddUser */, true /* isForeground */);
+ UserRecord.FOREGROUND_USER);
}
/**
* Create guest user record
*/
private UserRecord createStartGuestUserRecord() {
- UserInfo userInfo = new UserInfo();
- userInfo.name = mContext.getString(R.string.start_guest_session);
- return new UserRecord(userInfo, true /* isStartGuestSession */, false /* isAddUser */,
- false /* isForeground */);
+ return new UserRecord(null /* userInfo */, UserRecord.START_GUEST);
}
/**
* Create add user record
*/
private UserRecord createAddUserRecord() {
- UserInfo userInfo = new UserInfo();
- userInfo.name = mContext.getString(R.string.car_add_user);
- return new UserRecord(userInfo, false /* isStartGuestSession */,
- true /* isAddUser */, false /* isForeground */);
+ return new UserRecord(null /* userInfo */, UserRecord.ADD_USER);
}
public void setUserSelectionListener(UserSelectionListener userSelectionListener) {
@@ -258,35 +255,36 @@
UserRecord userRecord = mUsers.get(position);
RoundedBitmapDrawable circleIcon = getCircularUserRecordIcon(userRecord);
holder.mUserAvatarImageView.setImageDrawable(circleIcon);
- holder.mUserNameTextView.setText(userRecord.mInfo.name);
+ holder.mUserNameTextView.setText(getUserRecordName(userRecord));
holder.mView.setOnClickListener(v -> {
if (userRecord == null) {
return;
}
- if (userRecord.mIsStartGuestSession) {
- notifyUserSelected(userRecord);
- UserInfo guest = createNewOrFindExistingGuest(mContext);
- if (guest != null) {
- mCarUserManagerHelper.switchToUser(guest);
- }
- return;
- }
+ switch (userRecord.mType) {
+ case UserRecord.START_GUEST:
+ notifyUserSelected(userRecord);
+ UserInfo guest = createNewOrFindExistingGuest(mContext);
+ if (guest != null) {
+ mCarUserManagerHelper.switchToUser(guest);
+ }
+ break;
+ case UserRecord.ADD_USER:
+ // If the user wants to add a user, show dialog to confirm adding a user
+ // Disable button so it cannot be clicked multiple times
+ mAddUserView = holder.mView;
+ mAddUserView.setEnabled(false);
+ mAddUserRecord = userRecord;
- // If the user wants to add a user, show dialog to confirm adding a user
- if (userRecord.mIsAddUser) {
- // Disable button so it cannot be clicked multiple times
- mAddUserView = holder.mView;
- mAddUserView.setEnabled(false);
- mAddUserRecord = userRecord;
-
- handleAddUserClicked();
- return;
+ handleAddUserClicked();
+ break;
+ default:
+ // If the user doesn't want to be a guest or add a user, switch to the user
+ // selected
+ notifyUserSelected(userRecord);
+ mCarUserManagerHelper.switchToUser(userRecord.mInfo);
}
- // If the user doesn't want to be a guest or add a user, switch to the user selected
- notifyUserSelected(userRecord);
- mCarUserManagerHelper.switchToUser(userRecord.mInfo);
});
}
@@ -372,19 +370,44 @@
private RoundedBitmapDrawable getCircularUserRecordIcon(UserRecord userRecord) {
Resources resources = mContext.getResources();
RoundedBitmapDrawable circleIcon;
- if (userRecord.mIsStartGuestSession) {
- circleIcon = mUserIconProvider.getRoundedGuestDefaultIcon(resources);
- } else if (userRecord.mIsAddUser) {
- circleIcon = RoundedBitmapDrawableFactory.create(mRes, UserIcons.convertToBitmap(
- mContext.getDrawable(R.drawable.car_add_circle_round)));
- circleIcon.setCircular(true);
- } else {
- circleIcon = mUserIconProvider.getRoundedUserIcon(userRecord.mInfo, mContext);
+ switch (userRecord.mType) {
+ case UserRecord.START_GUEST:
+ circleIcon = mUserIconProvider.getRoundedGuestDefaultIcon(resources);
+ break;
+ case UserRecord.ADD_USER:
+ circleIcon = getCircularAddUserIcon();
+ break;
+ default:
+ circleIcon = mUserIconProvider.getRoundedUserIcon(userRecord.mInfo, mContext);
+ break;
}
-
return circleIcon;
}
+ private RoundedBitmapDrawable getCircularAddUserIcon() {
+ RoundedBitmapDrawable circleIcon =
+ RoundedBitmapDrawableFactory.create(mRes, UserIcons.convertToBitmap(
+ mContext.getDrawable(R.drawable.car_add_circle_round)));
+ circleIcon.setCircular(true);
+ return circleIcon;
+ }
+
+ private String getUserRecordName(UserRecord userRecord) {
+ String recordName;
+ switch (userRecord.mType) {
+ case UserRecord.START_GUEST:
+ recordName = mContext.getString(R.string.start_guest_session);
+ break;
+ case UserRecord.ADD_USER:
+ recordName = mContext.getString(R.string.car_add_user);
+ break;
+ default:
+ recordName = userRecord.mInfo.name;
+ break;
+ }
+ return recordName;
+ }
+
/**
* Finds the existing Guest user, or creates one if it doesn't exist.
* @param context App context
@@ -468,18 +491,21 @@
* guest profile, add user profile, or the foreground user.
*/
public static final class UserRecord {
-
public final UserInfo mInfo;
- public final boolean mIsStartGuestSession;
- public final boolean mIsAddUser;
- public final boolean mIsForeground;
+ public final @UserRecordType int mType;
- public UserRecord(UserInfo userInfo, boolean isStartGuestSession, boolean isAddUser,
- boolean isForeground) {
+ public static final int START_GUEST = 0;
+ public static final int ADD_USER = 1;
+ public static final int FOREGROUND_USER = 2;
+ public static final int BACKGROUND_USER = 3;
+
+ @IntDef({START_GUEST, ADD_USER, FOREGROUND_USER, BACKGROUND_USER})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserRecordType{}
+
+ public UserRecord(@Nullable UserInfo userInfo, @UserRecordType int recordType) {
mInfo = userInfo;
- mIsStartGuestSession = isStartGuestSession;
- mIsAddUser = isAddUser;
- mIsForeground = isForeground;
+ mType = recordType;
}
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 3258d57..2697a10 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -29,6 +29,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.internal.telephony.PhoneConstants;
/**
@@ -138,7 +139,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onRegisterDefaultNetworkAvail subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, true);
+ telephonyMgr.createForSubscriptionId(subId).reportDefaultNetworkStatus(true);
}
private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) {
@@ -146,7 +147,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDeregisterDefaultNetworkAvail subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, false);
+ telephonyMgr.createForSubscriptionId(subId).reportDefaultNetworkStatus(false);
}
private static void onDisableRadio(Intent intent, Context context) {
@@ -154,7 +155,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDisableRadio subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionSetRadioEnabled(subId, !ENABLE);
+ telephonyMgr.createForSubscriptionId(subId).setRadioEnabled(!ENABLE);
}
private static void onEnableRadio(Intent intent, Context context) {
@@ -162,7 +163,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onEnableRadio subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionSetRadioEnabled(subId, ENABLE);
+ telephonyMgr.createForSubscriptionId(subId).setRadioEnabled(ENABLE);
}
private static void onShowCaptivePortalNotification(Intent intent, Context context) {
@@ -205,7 +206,7 @@
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onResetAllCarrierActions subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
- telephonyMgr.carrierActionResetAll(subId);
+ telephonyMgr.createForSubscriptionId(subId).resetAllCarrierActions();
}
private static Notification getNotification(Context context, int titleId, int textId,
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index a379bfc..1453ec3 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -15,5 +15,6 @@
android_app {
name: "CompanionDeviceManager",
srcs: ["src/**/*.java"],
+
platform_apis: true,
}
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 42885e8..a9c6685 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"/>
<application
android:allowClearUserData="true"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index d11b5c5..7aa997e 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -37,12 +37,12 @@
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
+import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.BluetoothDeviceFilter;
import android.companion.BluetoothLeDeviceFilter;
import android.companion.DeviceFilter;
import android.companion.ICompanionDeviceDiscoveryService;
-import android.companion.ICompanionDeviceDiscoveryServiceCallback;
import android.companion.IFindDeviceCallback;
import android.companion.WifiDeviceFilter;
import android.content.BroadcastReceiver;
@@ -63,6 +63,7 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
@@ -97,7 +98,7 @@
DevicesAdapter mDevicesAdapter;
IFindDeviceCallback mFindCallback;
- ICompanionDeviceDiscoveryServiceCallback mServiceCallback;
+ AndroidFuture<Association> mServiceCallback;
boolean mIsScanning = false;
@Nullable DeviceChooserActivity mActivity = null;
@@ -107,7 +108,7 @@
public void startDiscovery(AssociationRequest request,
String callingPackage,
IFindDeviceCallback findCallback,
- ICompanionDeviceDiscoveryServiceCallback serviceCallback) {
+ AndroidFuture serviceCallback) {
if (DEBUG) {
Log.i(LOG_TAG,
"startDiscovery() called with: filter = [" + request
@@ -303,23 +304,12 @@
}
void onDeviceSelected(String callingPackage, String deviceAddress) {
- try {
- mServiceCallback.onDeviceSelected(
- //TODO is this the right userId?
- callingPackage, getUserId(), deviceAddress);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Failed to record association: "
- + callingPackage + " <-> " + deviceAddress);
- }
+ mServiceCallback.complete(new Association(getUserId(), deviceAddress, callingPackage));
}
void onCancel() {
if (DEBUG) Log.i(LOG_TAG, "onCancel()");
- try {
- mServiceCallback.onDeviceSelectionCancel();
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
+ mServiceCallback.cancel(true);
}
class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> {
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 7b2922ba..1e19786 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -102,6 +102,9 @@
DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID;
private static final String ROOT_ID_HOME = "home";
+ private static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
+ private static final String GET_MEDIA_URI_CALL = "get_media_uri";
+
private StorageManager mStorageManager;
private UserManager mUserManager;
@@ -665,7 +668,7 @@
}
break;
}
- case MediaStore.GET_DOCUMENT_URI_CALL: {
+ case GET_DOCUMENT_URI_CALL: {
// All callers must go through MediaProvider
getContext().enforceCallingPermission(
android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG);
@@ -684,7 +687,7 @@
throw new IllegalStateException("File in " + path + " is not found.", e);
}
}
- case MediaStore.GET_MEDIA_URI_CALL: {
+ case GET_MEDIA_URI_CALL: {
// All callers must go through MediaProvider
getContext().enforceCallingPermission(
android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG);
diff --git a/packages/Incremental/NativeAdbDataLoader/Android.bp b/packages/Incremental/NativeAdbDataLoader/Android.bp
new file mode 100644
index 0000000..5d7b5b6
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/Android.bp
@@ -0,0 +1,22 @@
+// 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.
+
+android_app {
+ name: "NativeAdbDataLoaderService",
+ srcs: ["src/**/*.java"],
+ jni_libs: [ "libnativeadbdataloaderservice_jni"],
+ privileged: true,
+ certificate: "platform",
+ platform_apis: true,
+}
diff --git a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml
new file mode 100644
index 0000000..a06dc54
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** 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.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ coreApp="true"
+ package="com.android.incremental.nativeadb"
+ android:sharedUserId="android.uid.system">
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application android:label="@string/app_name"
+ android:directBootAware="true">
+
+ <service android:enabled="true"
+ android:name="com.android.incremental.nativeadb.NativeAdbDataLoaderService"
+ android:label="@string/app_name"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.LOAD_DATA" />
+ </intent-filter>
+ </service>
+ </application>
+
+</manifest>
diff --git a/packages/Incremental/NativeAdbDataLoader/jni/Android.bp b/packages/Incremental/NativeAdbDataLoader/jni/Android.bp
new file mode 100644
index 0000000..0fcfd28
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/jni/Android.bp
@@ -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.
+
+cc_library_shared {
+ name: "libnativeadbdataloaderservice_jni",
+ cpp_std: "c++2a",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ "-Wno-unused-parameter",
+ ],
+
+ srcs: ["com_android_incremental_nativeadb_DataLoaderService.cpp"],
+
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "libincfs",
+ "libdataloader",
+ "liblog",
+ "libnativehelper",
+ "libutils",
+ ],
+}
diff --git a/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp b/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp
new file mode 100644
index 0000000..4e49302
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp
@@ -0,0 +1,517 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_ADB
+#define LOG_TAG "NativeAdbDataLoaderService"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
+#include <android-base/unique_fd.h>
+#include <cutils/trace.h>
+#include <fcntl.h>
+#include <sys/eventfd.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <utils/Log.h>
+
+#include <charconv>
+#include <string>
+#include <thread>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "dataloader.h"
+
+#ifndef _WIN32
+#include <endian.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#else
+#define be32toh(x) _byteswap_ulong(x)
+#define be16toh(x) _byteswap_ushort(x)
+#endif
+
+namespace {
+
+using android::base::unique_fd;
+
+using namespace std::literals;
+
+using BlockSize = int16_t;
+using FileId = int16_t;
+using BlockIdx = int32_t;
+using NumBlocks = int32_t;
+using CompressionType = int16_t;
+using RequestType = int16_t;
+
+static constexpr int COMMAND_SIZE = 2 + 2 + 4; // bytes
+static constexpr int HEADER_SIZE = 2 + 2 + 4 + 2; // bytes
+static constexpr std::string_view OKAY = "OKAY"sv;
+
+static constexpr auto PollTimeoutMs = 5000;
+
+static constexpr auto ReadLogBufferSize = 128 * 1024 * 1024;
+static constexpr auto ReadLogMaxEntrySize = 128;
+
+struct BlockHeader {
+ FileId fileId = -1;
+ CompressionType compressionType = -1;
+ BlockIdx blockIdx = -1;
+ BlockSize blockSize = -1;
+} __attribute__((packed));
+
+static_assert(sizeof(BlockHeader) == HEADER_SIZE);
+
+static constexpr RequestType EXIT = 0;
+static constexpr RequestType BLOCK_MISSING = 1;
+static constexpr RequestType PREFETCH = 2;
+
+struct RequestCommand {
+ RequestType requestType;
+ FileId fileId;
+ BlockIdx blockIdx;
+} __attribute__((packed));
+
+static_assert(COMMAND_SIZE == sizeof(RequestCommand));
+
+static bool sendRequest(int fd,
+ RequestType requestType,
+ FileId fileId = -1,
+ BlockIdx blockIdx = -1) {
+ const RequestCommand command{
+ .requestType = static_cast<int16_t>(be16toh(requestType)),
+ .fileId = static_cast<int16_t>(be16toh(fileId)),
+ .blockIdx = static_cast<int32_t>(be32toh(blockIdx))};
+ return android::base::WriteFully(fd, &command, sizeof(command));
+}
+
+static int waitForDataOrSignal(int fd, int event_fd) {
+ struct pollfd pfds[2] = {{fd, POLLIN, 0}, {event_fd, POLLIN, 0}};
+ // Wait indefinitely until either data is ready or stop signal is received
+ int res = poll(pfds, 2, PollTimeoutMs);
+ if (res <= 0) {
+ return res;
+ }
+ // First check if there is a stop signal
+ if (pfds[1].revents == POLLIN) {
+ return event_fd;
+ }
+ // Otherwise check if incoming data is ready
+ if (pfds[0].revents == POLLIN) {
+ return fd;
+ }
+ return -1;
+}
+
+static bool readChunk(int fd, std::vector<uint8_t>& data) {
+ int32_t size;
+ if (!android::base::ReadFully(fd, &size, sizeof(size))) {
+ return false;
+ }
+ size = int32_t(be32toh(size));
+ if (size <= 0) {
+ return false;
+ }
+ data.resize(size);
+ return android::base::ReadFully(fd, data.data(), data.size());
+}
+
+static BlockHeader readHeader(std::span<uint8_t>& data) {
+ BlockHeader header;
+ if (data.size() < sizeof(header)) {
+ return header;
+ }
+
+ header.fileId = static_cast<FileId>(
+ be16toh(*reinterpret_cast<uint16_t*>(&data[0])));
+ header.compressionType = static_cast<CompressionType>(
+ be16toh(*reinterpret_cast<uint16_t*>(&data[2])));
+ header.blockIdx = static_cast<BlockIdx>(
+ be32toh(*reinterpret_cast<uint32_t*>(&data[4])));
+ header.blockSize = static_cast<BlockSize>(
+ be16toh(*reinterpret_cast<uint16_t*>(&data[8])));
+ data = data.subspan(sizeof(header));
+
+ return header;
+}
+
+static std::string extractPackageName(const std::string& staticArgs) {
+ static constexpr auto kPrefix = "package="sv;
+ static constexpr auto kSuffix = "&"sv;
+
+ const auto startPos = staticArgs.find(kPrefix);
+ if (startPos == staticArgs.npos || startPos + kPrefix.size() >= staticArgs.size()) {
+ return {};
+ }
+ const auto endPos = staticArgs.find(kSuffix, startPos + kPrefix.size());
+ return staticArgs.substr(startPos + kPrefix.size(),
+ endPos == staticArgs.npos ? staticArgs.npos
+ : (endPos - (startPos + kPrefix.size())));
+}
+
+class AdbDataLoader : public android::dataloader::DataLoader {
+private:
+ // Lifecycle.
+ bool onCreate(const android::dataloader::DataLoaderParams& params,
+ android::dataloader::FilesystemConnectorPtr ifs,
+ android::dataloader::StatusListenerPtr statusListener,
+ android::dataloader::ServiceConnectorPtr,
+ android::dataloader::ServiceParamsPtr) final {
+ CHECK(ifs) << "ifs can't be null";
+ CHECK(statusListener) << "statusListener can't be null";
+ ALOGE("[AdbDataLoader] onCreate: %d/%s/%s/%s/%d", params.type(), params.packageName().c_str(), params.className().c_str(), params.arguments().c_str(), (int)params.dynamicArgs().size());
+
+ if (params.dynamicArgs().empty()) {
+ ALOGE("[AdbDataLoader] Invalid DataLoaderParams. Need in/out FDs.");
+ return false;
+ }
+ for (auto const& namedFd : params.dynamicArgs()) {
+ if (namedFd.name == "inFd") {
+ mInFd.reset(dup(namedFd.fd));
+ }
+ if (namedFd.name == "outFd") {
+ mOutFd.reset(dup(namedFd.fd));
+ }
+ }
+ if (mInFd < 0 || mOutFd < 0) {
+ ALOGE("[AdbDataLoader] Failed to dup FDs.");
+ return false;
+ }
+
+ mEventFd.reset(eventfd(0, EFD_CLOEXEC));
+ if (mEventFd < 0) {
+ ALOGE("[AdbDataLoader] Failed to create eventfd.");
+ return false;
+ }
+
+ std::string logFile;
+ if (const auto packageName = extractPackageName(params.arguments()); !packageName.empty()) {
+ logFile = android::base::GetProperty("adb.readlog." + packageName, "");
+ }
+ if (logFile.empty()) {
+ logFile = android::base::GetProperty("adb.readlog", "");
+ }
+ if (!logFile.empty()) {
+ int flags = O_WRONLY | O_CREAT | O_CLOEXEC;
+ mReadLogFd.reset(
+ TEMP_FAILURE_RETRY(open(logFile.c_str(), flags, 0666)));
+ }
+
+ mIfs = ifs;
+ mStatusListener = statusListener;
+ ALOGE("[AdbDataLoader] Successfully created data loader.");
+ return true;
+ }
+
+ bool onStart() final {
+ char okay_buf[OKAY.size()];
+ if (!android::base::ReadFully(mInFd, okay_buf, OKAY.size())) {
+ ALOGE("[AdbDataLoader] Failed to receive OKAY. Abort.");
+ return false;
+ }
+ if (std::string_view(okay_buf, OKAY.size()) != OKAY) {
+ ALOGE("[AdbDataLoader] Received '%.*s', expecting '%.*s'",
+ (int)OKAY.size(), okay_buf, (int)OKAY.size(), OKAY.data());
+ return false;
+ }
+
+ mReceiverThread = std::thread([this]() { receiver(); });
+ ALOGI("[AdbDataLoader] started loading...");
+ return true;
+ }
+
+ void onStop() final {
+ mStopReceiving = true;
+ eventfd_write(mEventFd, 1);
+ if (mReceiverThread.joinable()) {
+ mReceiverThread.join();
+ }
+ }
+
+ void onDestroy() final {
+ ALOGE("[AdbDataLoader] Sending EXIT to server.");
+ sendRequest(mOutFd, EXIT);
+ // Make sure the receiver thread was stopped
+ CHECK(!mReceiverThread.joinable());
+
+ mInFd.reset();
+ mOutFd.reset();
+
+ mNodeToMetaMap.clear();
+ mIdToNodeMap.clear();
+
+ flushReadLog();
+ mReadLogFd.reset();
+ }
+
+ // IFS callbacks.
+ void onPendingReads(const android::dataloader::PendingReads& pendingReads) final {
+ std::lock_guard lock{mMapsMutex};
+ CHECK(mIfs);
+ for (auto&& pendingRead : pendingReads) {
+ const android::dataloader::Inode ino = pendingRead.file_ino;
+ const auto blockIdx =
+ static_cast<BlockIdx>(pendingRead.block_index);
+ /*
+ ALOGI("[AdbDataLoader] Missing: %d", (int) blockIdx);
+ */
+ auto fileIdOr = getFileId(ino);
+ if (!fileIdOr) {
+ ALOGE("[AdbDataLoader] Failed to handle event for inode=%d. "
+ "Ignore.",
+ static_cast<int>(ino));
+ continue;
+ }
+ const FileId fileId = *fileIdOr;
+ if (mRequestedFiles.insert(fileId).second) {
+ if (!sendRequest(mOutFd, PREFETCH, fileId, blockIdx)) {
+ ALOGE("[AdbDataLoader] Failed to request prefetch for "
+ "inode=%d. Ignore.",
+ static_cast<int>(ino));
+ mRequestedFiles.erase(fileId);
+ mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+ }
+ }
+ sendRequest(mOutFd, BLOCK_MISSING, fileId, blockIdx);
+ }
+ }
+
+ struct TracedRead {
+ uint64_t timestampUs;
+ uint64_t fileIno;
+ uint32_t firstBlockIdx;
+ uint32_t count;
+ };
+ void onPageReads(const android::dataloader::PageReads& pageReads) final {
+ auto trace = atrace_is_tag_enabled(ATRACE_TAG);
+ auto log = mReadLogFd != -1;
+ if (CC_LIKELY(!(trace || log))) {
+ return;
+ }
+
+ TracedRead last = {0, 0, 0, 0};
+ std::lock_guard lock{mMapsMutex};
+ for (auto&& read : pageReads) {
+ if (read.file_ino != last.fileIno ||
+ read.block_index != last.firstBlockIdx + last.count) {
+ traceOrLogRead(last, trace, log);
+ last = {read.timestamp_us, read.file_ino, read.block_index, 1};
+ } else {
+ ++last.count;
+ }
+ }
+ traceOrLogRead(last, trace, log);
+ }
+ void onFileCreated(android::dataloader::Inode inode, const android::dataloader::RawMetadata& metadata) {
+ }
+
+private:
+ void receiver() {
+ std::vector<uint8_t> data;
+ std::vector<incfs_new_data_block> instructions;
+ while (!mStopReceiving) {
+ const int res = waitForDataOrSignal(mInFd, mEventFd);
+ if (res == 0) {
+ flushReadLog();
+ continue;
+ }
+ if (res < 0) {
+ ALOGE("[AdbDataLoader] failed to poll. Abort.");
+ mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+ break;
+ }
+ if (res == mEventFd) {
+ ALOGE("[AdbDataLoader] received stop signal. Exit.");
+ break;
+ }
+ if (!readChunk(mInFd, data)) {
+ ALOGE("[AdbDataLoader] failed to read a message. Abort.");
+ mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION);
+ break;
+ }
+ auto remainingData = std::span(data);
+ while (!remainingData.empty()) {
+ auto header = readHeader(remainingData);
+ if (header.fileId == -1 && header.compressionType == 0 &&
+ header.blockIdx == 0 && header.blockSize == 0) {
+ ALOGI("[AdbDataLoader] stop signal received. Sending "
+ "exit command (remaining bytes: %d).",
+ int(remainingData.size()));
+
+ sendRequest(mOutFd, EXIT);
+ mStopReceiving = true;
+ break;
+ }
+ if (header.fileId < 0 || header.blockSize <= 0 ||
+ header.compressionType < 0 || header.blockIdx < 0) {
+ ALOGE("[AdbDataLoader] invalid header received. Abort.");
+ mStopReceiving = true;
+ break;
+ }
+ const android::dataloader::Inode ino = mIdToNodeMap[header.fileId];
+ if (!ino) {
+ ALOGE("Unknown data destination for file ID %d. "
+ "Ignore.",
+ header.fileId);
+ continue;
+ }
+ auto inst = incfs_new_data_block{
+ .file_ino = static_cast<__aligned_u64>(ino),
+ .block_index = static_cast<uint32_t>(header.blockIdx),
+ .data_len = static_cast<uint16_t>(header.blockSize),
+ .data = reinterpret_cast<uint64_t>(
+ remainingData.data()),
+ .compression =
+ static_cast<uint8_t>(header.compressionType)};
+ instructions.push_back(inst);
+ remainingData = remainingData.subspan(header.blockSize);
+ }
+ writeInstructions(instructions);
+ }
+ writeInstructions(instructions);
+ flushReadLog();
+ }
+
+ void writeInstructions(std::vector<incfs_new_data_block>& instructions) {
+ auto res = this->mIfs->writeBlocks(instructions.data(),
+ instructions.size());
+ if (res != instructions.size()) {
+ ALOGE("[AdbDataLoader] failed to write data to Incfs (res=%d when "
+ "expecting %d)",
+ res, int(instructions.size()));
+ }
+ instructions.clear();
+ }
+
+ struct MetaPair {
+ android::dataloader::RawMetadata meta;
+ FileId fileId;
+ };
+
+ MetaPair* updateMapsForFile(android::dataloader::Inode ino) {
+ android::dataloader::RawMetadata meta = mIfs->getRawMetadata(ino);
+ FileId fileId;
+ auto res =
+ std::from_chars(meta.data(), meta.data() + meta.size(), fileId);
+ if (res.ec != std::errc{} || fileId < 0) {
+ ALOGE("[AdbDataLoader] Invalid metadata for inode=%d (%s)",
+ static_cast<int>(ino), meta.data());
+ return nullptr;
+ }
+ mIdToNodeMap[fileId] = ino;
+ auto& metaPair = mNodeToMetaMap[ino];
+ metaPair.meta = std::move(meta);
+ metaPair.fileId = fileId;
+ return &metaPair;
+ }
+
+ android::dataloader::RawMetadata* getMeta(android::dataloader::Inode ino) {
+ auto it = mNodeToMetaMap.find(ino);
+ if (it != mNodeToMetaMap.end()) {
+ return &it->second.meta;
+ }
+
+ auto metaPair = updateMapsForFile(ino);
+ if (!metaPair) {
+ return nullptr;
+ }
+
+ return &metaPair->meta;
+ }
+
+ FileId* getFileId(android::dataloader::Inode ino) {
+ auto it = mNodeToMetaMap.find(ino);
+ if (it != mNodeToMetaMap.end()) {
+ return &it->second.fileId;
+ }
+
+ auto* metaPair = updateMapsForFile(ino);
+ if (!metaPair) {
+ return nullptr;
+ }
+
+ return &metaPair->fileId;
+ }
+
+ void traceOrLogRead(const TracedRead& read, bool trace, bool log) {
+ if (!read.count) {
+ return;
+ }
+ if (trace) {
+ auto* meta = getMeta(read.fileIno);
+ auto str = android::base::StringPrintf(
+ "page_read: index=%lld count=%lld meta=%.*s",
+ static_cast<long long>(read.firstBlockIdx),
+ static_cast<long long>(read.count),
+ meta ? int(meta->size()) : 0, meta ? meta->data() : "");
+ ATRACE_BEGIN(str.c_str());
+ ATRACE_END();
+ }
+ if (log) {
+ mReadLog.reserve(ReadLogBufferSize);
+
+ auto fileId = getFileId(read.fileIno);
+ android::base::StringAppendF(
+ &mReadLog, "%lld:%lld:%lld:%lld\n",
+ static_cast<long long>(read.timestampUs),
+ static_cast<long long>(fileId ? *fileId : -1),
+ static_cast<long long>(read.firstBlockIdx),
+ static_cast<long long>(read.count));
+
+ if (mReadLog.size() >= mReadLog.capacity() - ReadLogMaxEntrySize) {
+ flushReadLog();
+ }
+ }
+ }
+
+ void flushReadLog() {
+ if (mReadLog.empty() || mReadLogFd == -1) {
+ return;
+ }
+
+ android::base::WriteStringToFd(mReadLog, mReadLogFd);
+ mReadLog.clear();
+ }
+
+private:
+ android::dataloader::FilesystemConnectorPtr mIfs = nullptr;
+ android::dataloader::StatusListenerPtr mStatusListener = nullptr;
+ android::base::unique_fd mInFd;
+ android::base::unique_fd mOutFd;
+ android::base::unique_fd mEventFd;
+ android::base::unique_fd mReadLogFd;
+ std::string mReadLog;
+ std::thread mReceiverThread;
+ std::mutex mMapsMutex;
+ std::unordered_map<android::dataloader::Inode, MetaPair> mNodeToMetaMap GUARDED_BY(mMapsMutex);
+ std::unordered_map<FileId, android::dataloader::Inode> mIdToNodeMap GUARDED_BY(mMapsMutex);
+ /** Tracks which files have been requested */
+ std::unordered_set<FileId> mRequestedFiles;
+ std::atomic<bool> mStopReceiving = false;
+};
+
+} // namespace
+
+int JNI_OnLoad(JavaVM* jvm, void* /* reserved */) {
+ android::dataloader::DataLoader::initialize(
+ [](auto) { return std::make_unique<AdbDataLoader>(); });
+ return JNI_VERSION_1_6;
+}
diff --git a/apex/sdkext/framework/tests/AndroidManifest.xml b/packages/Incremental/NativeAdbDataLoader/res/values/strings.xml
similarity index 64%
copy from apex/sdkext/framework/tests/AndroidManifest.xml
copy to packages/Incremental/NativeAdbDataLoader/res/values/strings.xml
index 831f132..9921ae6 100644
--- a/apex/sdkext/framework/tests/AndroidManifest.xml
+++ b/packages/Incremental/NativeAdbDataLoader/res/values/strings.xml
@@ -13,15 +13,7 @@
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.sdkext.tests">
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.sdkext.tests" />
-
-</manifest>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of the Data Loader Service. [CHAR LIMIT=40] -->
+ <string name="app_name">Native Adb Data Loader Service</string>
+</resources>
diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
new file mode 100644
index 0000000..bd5b7959
--- /dev/null
+++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.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.incremental.nativeadb;
+
+import android.service.dataloader.DataLoaderService;
+
+/** This code is used for testing only. */
+public class NativeAdbDataLoaderService extends DataLoaderService {
+ public static final String TAG = "NativeAdbDataLoaderService";
+ static {
+ System.loadLibrary("nativeadbdataloaderservice_jni");
+ }
+
+ @Override
+ public DataLoader onCreateDataLoader() {
+ return null;
+ }
+}
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 39c55fd..9b4a6d6 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -140,8 +140,6 @@
<item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx">aptX™</xliff:g> audio</item>
<item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx_hd">aptX™ HD</xliff:g> audio</item>
<item>LDAC</item>
- <item>Enable Optional Codecs</item>
- <item>Disable Optional Codecs</item>
</string-array>
<!-- Values for Bluetooth Audio Codec selection preference. -->
@@ -152,8 +150,6 @@
<item>2</item>
<item>3</item>
<item>4</item>
- <item>5</item>
- <item>6</item>
</string-array>
<!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
@@ -164,8 +160,6 @@
<item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx">aptX™</xliff:g> audio</item>
<item><xliff:g id="qualcomm">Qualcomm®</xliff:g> <xliff:g id="aptx_hd">aptX™ HD</xliff:g> audio</item>
<item>LDAC</item>
- <item>Enable Optional Codecs</item>
- <item>Disable Optional Codecs</item>
</string-array>
<!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 80240af..f40105d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -40,6 +40,8 @@
<string name="wifi_security_short_sae" translatable="false">WPA3</string>
<!-- Do not translate. Concise terminology for wifi with WPA2/WPA3 transition security -->
<string name="wifi_security_short_psk_sae" translatable="false">WPA2/WPA3</string>
+ <!-- Do not translate. Concise terminology for Wi-Fi with None/OWE transition mode security -->
+ <string name="wifi_security_short_none_owe" translatable="false">None/OWE</string>
<!-- Do not translate. Concise terminology for wifi with OWE security -->
<string name="wifi_security_short_owe" translatable="false">OWE</string>
<!-- Do not translate. Concise terminology for wifi with 802.1x EAP Suite-B-192 security -->
@@ -70,6 +72,8 @@
<string name="wifi_security_sae" translatable="false">WPA3-Personal</string>
<!-- Do not translate. Terminology for wifi with WPA2/WPA3 Transition mode security -->
<string name="wifi_security_psk_sae" translatable="false">WPA2/WPA3-Personal</string>
+ <!-- Do not translate. Terminology for Wi-Fi with None/OWE transition mode security -->
+ <string name="wifi_security_none_owe" translatable="false">None/Enhanced Open</string>
<!-- Do not translate. Terminology for wifi with OWE security -->
<string name="wifi_security_owe" translatable="false">Enhanced Open</string>
<!-- Do not translate. Concise terminology for wifi with 802.1x EAP Suite-B-192 security -->
@@ -594,6 +598,8 @@
<string name="bluetooth_show_devices_without_names">Show Bluetooth devices without names</string>
<!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
<string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
+ <!-- Setting Checkbox title for enabling Bluetooth Gabeldorsche. [CHAR LIMIT=40] -->
+ <string name="bluetooth_enable_gabeldorsche">Enable Gabeldorsche</string>
<!-- UI debug setting: Select Bluetooth AVRCP Version -->
<string name="bluetooth_select_avrcp_version_string">Bluetooth AVRCP Version</string>
@@ -692,6 +698,8 @@
<string name="bluetooth_show_devices_without_names_summary">Bluetooth devices without names (MAC addresses only) will be displayed</string>
<!-- Summary of checkbox for disabling Bluetooth absolute volume -->
<string name="bluetooth_disable_absolute_volume_summary">Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.</string>
+ <!-- Summary of checkbox for enabling Bluetooth Gabeldorche features [CHAR LIMIT=none] -->
+ <string name="bluetooth_enable_gabeldorsche_summary">Enables the Bluetooth Gabeldorche feature stack.</string>
<!-- Title of checkbox setting that enables the terminal app. [CHAR LIMIT=32] -->
<string name="enable_terminal_title">Local terminal</string>
@@ -957,7 +965,7 @@
<!-- Title for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] -->
<string name="accessibility_display_daltonizer_preference_title">Color correction</string>
<!-- Subtitle for the accessibility preference to configure display color space correction. [CHAR LIMIT=NONE] -->
- <string name="accessibility_display_daltonizer_preference_subtitle">This feature is experimental and may affect performance.</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle">Color correction helps people with color blindness to see more accurate colors</string>
<!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
<string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 14f233d..2c001b0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,7 +1,5 @@
package com.android.settingslib;
-import static android.telephony.ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
-
import android.annotation.ColorInt;
import android.content.Context;
import android.content.Intent;
@@ -25,6 +23,8 @@
import android.os.UserManager;
import android.print.PrintManager;
import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import com.android.internal.annotations.VisibleForTesting;
@@ -412,15 +412,30 @@
// service" or "emergency calls only" text that indicates that voice
// is not available. Note that we ignore the IWLAN service state
// because that state indicates the use of VoWIFI and not cell service
- int state = serviceState.getState();
- int dataState = serviceState.getDataRegState();
+ final int state = serviceState.getState();
+ final int dataState = serviceState.getDataRegState();
+
if (state == ServiceState.STATE_OUT_OF_SERVICE
|| state == ServiceState.STATE_EMERGENCY_ONLY) {
- if (dataState == ServiceState.STATE_IN_SERVICE
- && serviceState.getDataNetworkType() != RIL_RADIO_TECHNOLOGY_IWLAN) {
+ if (dataState == ServiceState.STATE_IN_SERVICE && isNotInIwlan(serviceState)) {
return ServiceState.STATE_IN_SERVICE;
}
}
return state;
}
+
+ private static boolean isNotInIwlan(ServiceState serviceState) {
+ final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+ if (networkRegWlan == null) {
+ return true;
+ }
+
+ final boolean isInIwlan = (networkRegWlan.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+ || (networkRegWlan.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+ return !isInIwlan;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 96aee51..a2bd210 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -155,8 +155,8 @@
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
// Downgrade priority as user is disconnecting the headset.
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
return mService.disconnect(device);
}
@@ -179,23 +179,29 @@
}
public boolean isPreferred(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ if (mService == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
- if (mService == null) return BluetoothProfile.PRIORITY_OFF;
- return mService.getPriority(device);
+ if (mService == null) {
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
- if (mService == null) return;
+ if (mService == null) {
+ return;
+ }
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.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 55765dd..bc03c34 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -124,8 +124,8 @@
return false;
}
// Downgrade priority as user is disconnecting the headset.
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
return mService.disconnect(device);
}
@@ -141,14 +141,14 @@
if (mService == null) {
return false;
}
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.PRIORITY_OFF;
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
- return mService.getPriority(device);
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
@@ -156,21 +156,21 @@
return;
}
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
}
- boolean isA2dpPlaying() {
+ boolean isAudioPlaying() {
if (mService == null) {
return false;
}
List<BluetoothDevice> srcs = mService.getConnectedDevices();
if (!srcs.isEmpty()) {
- if (mService.isA2dpPlaying(srcs.get(0))) {
+ if (mService.isAudioPlaying(srcs.get(0))) {
return true;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index e660e43..d3f9cd4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -116,7 +116,7 @@
}
return new Pair<>(
getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_settings_bluetooth),
+ com.android.internal.R.drawable.ic_settings_bluetooth).mutate(),
context.getString(R.string.bluetooth_talkback_bluetooth));
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 0666596..747ceb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -721,12 +721,8 @@
refresh();
- if (bondState == BluetoothDevice.BOND_BONDED) {
- if (mDevice.isBluetoothDock()) {
- onBondingDockConnect();
- } else if (mDevice.isBondingInitiatedLocally()) {
- connect(false);
- }
+ if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) {
+ connect(false);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 9f7b718..560cb3b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -120,8 +120,8 @@
return false;
}
// Downgrade priority as user is disconnecting the headset.
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
return mService.disconnect(device);
}
@@ -165,14 +165,14 @@
if (mService == null) {
return false;
}
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.PRIORITY_OFF;
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
- return mService.getPriority(device);
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
@@ -180,11 +180,11 @@
return;
}
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.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 ebaeb74..58655a2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -153,8 +153,8 @@
public boolean disconnect(BluetoothDevice device) {
if (mService == null) return false;
// Downgrade priority as user is disconnecting the hearing aid.
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
return mService.disconnect(device);
}
@@ -177,23 +177,29 @@
}
public boolean isPreferred(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ if (mService == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
- if (mService == null) return BluetoothProfile.PRIORITY_OFF;
- return mService.getPriority(device);
+ if (mService == null) {
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
- if (mService == null) return;
+ if (mService == null) {
+ return;
+ }
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.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 860b77d..a372e23 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -135,8 +135,8 @@
return false;
}
// Downgrade priority as user is disconnecting the headset.
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
return mService.disconnect(device);
}
@@ -154,15 +154,15 @@
if (mService == null) {
return false;
}
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
@Override
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.PRIORITY_OFF;
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
- return mService.getPriority(device);
+ return mService.getConnectionPolicy(device);
}
@Override
@@ -171,11 +171,11 @@
return;
}
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.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 6d874ab..975a1e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -116,23 +116,27 @@
}
public boolean isPreferred(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.getPriority(device) != BluetoothProfile.PRIORITY_OFF;
+ if (mService == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
- if (mService == null) return BluetoothProfile.PRIORITY_OFF;
- return mService.getPriority(device);
+ if (mService == null) {
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
if (mService == null) return;
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 8f40ab4..80b03a4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -175,7 +175,7 @@
return;
}
A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
- if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
+ if ((a2dpSink != null) && (a2dpSink.isAudioPlaying())) {
return;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index d4dda32..95139a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -123,8 +123,8 @@
return false;
}
// Downgrade priority as user is disconnecting.
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
return mService.disconnect(device);
}
@@ -140,14 +140,14 @@
if (mService == null) {
return false;
}
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.PRIORITY_OFF;
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
- return mService.getPriority(device);
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
@@ -155,11 +155,11 @@
return;
}
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.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 b2a9a6a..31a0eea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -119,8 +119,8 @@
if (mService == null) {
return false;
}
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
return mService.disconnect(device);
}
@@ -136,14 +136,14 @@
if (mService == null) {
return false;
}
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.PRIORITY_OFF;
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
- return mService.getPriority(device);
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
@@ -151,11 +151,11 @@
return;
}
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 7162121..387bae1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -1,8 +1,7 @@
# Default reviewers for this and subdirectories.
-asapperstein@google.com
-asargent@google.com
-eisenbach@google.com
-jackqdyulei@google.com
siyuanh@google.com
+hughchen@google.com
+timhypeng@google.com
+robertluo@google.com
-# Emergency approvers in case the above are not available
\ No newline at end of file
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
index e1e5dbe..8e3f3ed 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
@@ -57,7 +57,7 @@
}
public int getPreferred(BluetoothDevice device) {
- return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle OPP
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index a2da4fb..4ea0df6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -151,14 +151,14 @@
if (mService == null) {
return false;
}
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.PRIORITY_OFF;
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
- return mService.getPriority(device);
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
@@ -166,11 +166,11 @@
return;
}
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.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 d91226e..3f920a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothPbap;
@@ -52,14 +53,16 @@
// These callbacks run on the main thread.
private final class PbapServiceListener
- implements BluetoothPbap.ServiceListener {
+ implements BluetoothProfile.ServiceListener {
- public void onServiceConnected(BluetoothPbap proxy) {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
mService = (BluetoothPbap) proxy;
mIsProfileReady=true;
}
- public void onServiceDisconnected() {
+ @Override
+ public void onServiceDisconnected(int profile) {
mIsProfileReady=false;
}
}
@@ -74,7 +77,8 @@
}
PbapServerProfile(Context context) {
- BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener());
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new PbapServiceListener(),
+ BluetoothProfile.PBAP);
}
public boolean accessProfileEnabled() {
@@ -97,13 +101,8 @@
}
public int getConnectionStatus(BluetoothDevice device) {
- if (mService == null) {
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- if (mService.isConnected(device))
- return BluetoothProfile.STATE_CONNECTED;
- else
- return BluetoothProfile.STATE_DISCONNECTED;
+ if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+ return mService.getConnectionState(device);
}
public boolean isPreferred(BluetoothDevice device) {
@@ -142,7 +141,8 @@
Log.d(TAG, "finalize()");
if (mService != null) {
try {
- mService.close();
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PBAP,
+ mService);
mService = null;
}catch (Throwable t) {
Log.w(TAG, "Error cleaning up PBAP proxy", t);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 9b733f2..0ca4d61 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -119,8 +119,8 @@
if (mService == null) {
return false;
}
- if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
return mService.disconnect(device);
}
@@ -136,14 +136,14 @@
if (mService == null) {
return false;
}
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.PRIORITY_OFF;
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
}
- return mService.getPriority(device);
+ return mService.getConnectionPolicy(device);
}
public void setPreferred(BluetoothDevice device, boolean preferred) {
@@ -151,11 +151,11 @@
return;
}
if (preferred) {
- if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
- mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index 5cf44e1..a82231a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -23,6 +23,9 @@
import android.text.TextUtils;
import android.util.Pair;
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.util.ArrayList;
@@ -132,34 +135,44 @@
return ((Instrumentable) object).getMetricsCategory();
}
- public void logDashboardStartIntent(Context context, Intent intent,
- int sourceMetricsCategory) {
+ /**
+ * Logs an event when the preference is clicked.
+ *
+ * @return true if the preference is loggable, otherwise false
+ */
+ public boolean logClickedPreference(@NonNull Preference preference, int sourceMetricsCategory) {
+ if (preference == null) {
+ return false;
+ }
+ return logSettingsTileClick(preference.getKey(), sourceMetricsCategory)
+ || logStartedIntent(preference.getIntent(), sourceMetricsCategory)
+ || logSettingsTileClick(preference.getFragment(), sourceMetricsCategory);
+ }
+
+ /**
+ * Logs an event when the intent is started.
+ *
+ * @return true if the intent is loggable, otherwise false
+ */
+ public boolean logStartedIntent(Intent intent, int sourceMetricsCategory) {
if (intent == null) {
- return;
+ return false;
}
final ComponentName cn = intent.getComponent();
- if (cn == null) {
- final String action = intent.getAction();
- if (TextUtils.isEmpty(action)) {
- // Not loggable
- return;
- }
- action(sourceMetricsCategory,
- MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
- SettingsEnums.PAGE_UNKNOWN,
- action,
- 0);
- return;
- } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) {
- // Going to a Setting internal page, skip click logging in favor of page's own
- // visibility logging.
- return;
+ return logSettingsTileClick(cn != null ? cn.flattenToString() : intent.getAction(),
+ sourceMetricsCategory);
+ }
+
+ private boolean logSettingsTileClick(String logKey, int sourceMetricsCategory) {
+ if (TextUtils.isEmpty(logKey)) {
+ // Not loggable
+ return false;
}
action(sourceMetricsCategory,
MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
SettingsEnums.PAGE_UNKNOWN,
- cn.flattenToString(),
+ logKey,
0);
+ return true;
}
-
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 104cc8f..d3315ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -235,7 +235,7 @@
public final CharSequence contentDescription;
public final long requestFinishTime;
- private Request(String packageName, UserHandle userHandle, Drawable icon,
+ public Request(String packageName, UserHandle userHandle, Drawable icon,
CharSequence label, boolean isHighBattery, CharSequence contentDescription,
long requestFinishTime) {
this.packageName = packageName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index 23e29493..ebca962 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -46,7 +46,7 @@
}
final String[] mergedSubscriberIds = telephonyManager.createForSubscriptionId(subId)
- .getMergedSubscriberIdsFromGroup();
+ .getMergedImsisFromGroup();
if (ArrayUtils.isEmpty(mergedSubscriberIds)) {
Log.i(TAG, "mergedSubscriberIds is null.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index b0bdf1d..b13c483 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -36,7 +36,6 @@
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -181,13 +180,12 @@
static final String KEY_CONFIG = "key_config";
static final String KEY_FQDN = "key_fqdn";
static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
- static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap";
- static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
- static final String KEY_CARRIER_NAME = "key_carrier_name";
static final String KEY_EAPTYPE = "eap_psktype";
static final String KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS =
"key_subscription_expiration_time_in_millis";
static final String KEY_PASSPOINT_CONFIGURATION_VERSION = "key_passpoint_configuration_version";
+ static final String KEY_IS_PSK_SAE_TRANSITION_MODE = "key_is_psk_sae_transition_mode";
+ static final String KEY_IS_OWE_TRANSITION_MODE = "key_is_owe_transition_mode";
static final AtomicInteger sLastId = new AtomicInteger(0);
/*
@@ -202,15 +200,12 @@
public static final int SECURITY_OWE = 4;
public static final int SECURITY_SAE = 5;
public static final int SECURITY_EAP_SUITE_B = 6;
- public static final int SECURITY_PSK_SAE_TRANSITION = 7;
- public static final int SECURITY_OWE_TRANSITION = 8;
- public static final int SECURITY_MAX_VAL = 9; // Has to be the last
+ public static final int SECURITY_MAX_VAL = 7; // Has to be the last
private static final int PSK_UNKNOWN = 0;
private static final int PSK_WPA = 1;
private static final int PSK_WPA2 = 2;
private static final int PSK_WPA_WPA2 = 3;
- private static final int PSK_SAE = 4;
private static final int EAP_UNKNOWN = 0;
private static final int EAP_WPA = 1; // WPA-EAP
@@ -266,19 +261,14 @@
@PasspointConfigurationVersion private int mPasspointConfigurationVersion =
PasspointConfigurationVersion.INVALID;
- private boolean mIsCarrierAp = false;
-
private OsuProvider mOsuProvider;
private String mOsuStatus;
private String mOsuFailure;
private boolean mOsuProvisioningComplete = false;
- /**
- * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
- */
- private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE;
- private String mCarrierName = null;
+ private boolean mIsPskSaeTransitionMode = false;
+ private boolean mIsOweTransitionMode = false;
public AccessPoint(Context context, Bundle savedState) {
mContext = context;
@@ -328,15 +318,6 @@
if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
}
- if (savedState.containsKey(KEY_IS_CARRIER_AP)) {
- mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP);
- }
- if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) {
- mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE);
- }
- if (savedState.containsKey(KEY_CARRIER_NAME)) {
- mCarrierName = savedState.getString(KEY_CARRIER_NAME);
- }
if (savedState.containsKey(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS)) {
mSubscriptionExpirationTimeInMillis =
savedState.getLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS);
@@ -344,6 +325,13 @@
if (savedState.containsKey(KEY_PASSPOINT_CONFIGURATION_VERSION)) {
mPasspointConfigurationVersion = savedState.getInt(KEY_PASSPOINT_CONFIGURATION_VERSION);
}
+ if (savedState.containsKey(KEY_IS_PSK_SAE_TRANSITION_MODE)) {
+ mIsPskSaeTransitionMode = savedState.getBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE);
+ }
+ if (savedState.containsKey(KEY_IS_OWE_TRANSITION_MODE)) {
+ mIsOweTransitionMode = savedState.getBoolean(KEY_IS_OWE_TRANSITION_MODE);
+ }
+
update(mConfig, mInfo, mNetworkInfo);
// Calculate required fields
@@ -675,8 +663,15 @@
return oldMetering == mIsScoredNetworkMetered;
}
- public static String getKey(ScanResult result) {
- return getKey(result.SSID, result.BSSID, getSecurity(result));
+ /**
+ * Generates an AccessPoint key for a given scan result
+ *
+ * @param context
+ * @param result Scan result
+ * @return AccessPoint key
+ */
+ public static String getKey(Context context, ScanResult result) {
+ return getKey(result.SSID, result.BSSID, getSecurity(context, result));
}
/**
@@ -734,7 +729,42 @@
* Determines if the other AccessPoint represents the same network as this AccessPoint
*/
public boolean matches(AccessPoint other) {
- return getKey().equals(other.getKey());
+ if (isPasspoint() || isPasspointConfig() || isOsuProvider()) {
+ return getKey().equals(other.getKey());
+ }
+
+ if (!isSameSsidOrBssid(other)) {
+ return false;
+ }
+
+ final int otherApSecurity = other.getSecurity();
+ if (mIsPskSaeTransitionMode) {
+ if (otherApSecurity == SECURITY_SAE && getWifiManager().isWpa3SaeSupported()) {
+ return true;
+ } else if (otherApSecurity == SECURITY_PSK) {
+ return true;
+ }
+ } else {
+ if ((security == SECURITY_SAE || security == SECURITY_PSK)
+ && other.isPskSaeTransitionMode()) {
+ return true;
+ }
+ }
+
+ if (mIsOweTransitionMode) {
+ if (otherApSecurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
+ return true;
+ } else if (otherApSecurity == SECURITY_NONE) {
+ return true;
+ }
+ } else {
+ if ((security == SECURITY_OWE || security == SECURITY_NONE)
+ && other.isOweTransitionMode()) {
+ return true;
+ }
+ }
+
+ return security == other.getSecurity();
}
public boolean matches(WifiConfiguration config) {
@@ -748,18 +778,77 @@
}
final int configSecurity = getSecurity(config);
- final WifiManager wifiManager = getWifiManager();
- switch (security) {
- case SECURITY_PSK_SAE_TRANSITION:
- return configSecurity == SECURITY_PSK
- || (wifiManager.isWpa3SaeSupported() && configSecurity == SECURITY_SAE);
- case SECURITY_OWE_TRANSITION:
- return configSecurity == SECURITY_NONE
- || (wifiManager.isEnhancedOpenSupported()
- && configSecurity == SECURITY_OWE);
- default:
- return security == configSecurity;
+ if (mIsPskSaeTransitionMode) {
+ if (configSecurity == SECURITY_SAE && getWifiManager().isWpa3SaeSupported()) {
+ return true;
+ } else if (configSecurity == SECURITY_PSK) {
+ return true;
+ }
}
+
+ if (mIsOweTransitionMode) {
+ if (configSecurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
+ return true;
+ } else if (configSecurity == SECURITY_NONE) {
+ return true;
+ }
+ }
+
+ return security == getSecurity(config);
+ }
+
+ private boolean matches(WifiConfiguration config, WifiInfo wifiInfo) {
+ if (config == null || wifiInfo == null) {
+ return false;
+ }
+ if (!config.isPasspoint() && !isSameSsidOrBssid(wifiInfo)) {
+ return false;
+ }
+ return matches(config);
+ }
+
+ @VisibleForTesting
+ boolean matches(ScanResult scanResult) {
+ if (scanResult == null) {
+ return false;
+ }
+ if (isPasspoint() || isOsuProvider()) {
+ throw new IllegalStateException("Should not matches a Passpoint by ScanResult");
+ }
+
+ if (!isSameSsidOrBssid(scanResult)) {
+ return false;
+ }
+
+ if (mIsPskSaeTransitionMode) {
+ if (scanResult.capabilities.contains("SAE")
+ && getWifiManager().isWpa3SaeSupported()) {
+ return true;
+ } else if (scanResult.capabilities.contains("PSK")) {
+ return true;
+ }
+ } else {
+ if ((security == SECURITY_SAE || security == SECURITY_PSK)
+ && AccessPoint.isPskSaeTransitionMode(scanResult)) {
+ return true;
+ }
+ }
+
+ if (mIsOweTransitionMode) {
+ final int scanResultSccurity = getSecurity(mContext, scanResult);
+ if (scanResultSccurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) {
+ return true;
+ } else if (scanResultSccurity == SECURITY_NONE) {
+ return true;
+ }
+ } else {
+ if ((security == SECURITY_OWE || security == SECURITY_NONE)
+ && AccessPoint.isOweTransitionMode(scanResult)) {
+ return true;
+ }
+ }
+
+ return security == getSecurity(mContext, scanResult);
}
public WifiConfiguration getConfig() {
@@ -846,17 +935,16 @@
if (bestResult != null) {
ssid = bestResult.SSID;
bssid = bestResult.BSSID;
- security = getSecurity(bestResult);
- if (security == SECURITY_PSK || security == SECURITY_SAE
- || security == SECURITY_PSK_SAE_TRANSITION) {
+ security = getSecurity(mContext, bestResult);
+ if (security == SECURITY_PSK || security == SECURITY_SAE) {
pskType = getPskType(bestResult);
}
if (security == SECURITY_EAP) {
mEapType = getEapType(bestResult);
}
- mIsCarrierAp = bestResult.isCarrierAp;
- mCarrierApEapType = bestResult.carrierApEapType;
- mCarrierName = bestResult.carrierName;
+
+ mIsPskSaeTransitionMode = AccessPoint.isPskSaeTransitionMode(bestResult);
+ mIsOweTransitionMode = AccessPoint.isOweTransitionMode(bestResult);
}
// Update the config SSID of a Passpoint network to that of the best RSSI
if (isPasspoint()) {
@@ -886,6 +974,16 @@
return concise ? context.getString(R.string.wifi_security_short_eap) :
context.getString(R.string.wifi_security_eap);
}
+
+ if (mIsPskSaeTransitionMode) {
+ return concise ? context.getString(R.string.wifi_security_short_psk_sae) :
+ context.getString(R.string.wifi_security_psk_sae);
+ }
+ if (mIsOweTransitionMode) {
+ return concise ? context.getString(R.string.wifi_security_short_none_owe) :
+ context.getString(R.string.wifi_security_none_owe);
+ }
+
switch(security) {
case SECURITY_EAP:
switch (mEapType) {
@@ -925,20 +1023,8 @@
return concise ? context.getString(R.string.wifi_security_short_wep) :
context.getString(R.string.wifi_security_wep);
case SECURITY_SAE:
- case SECURITY_PSK_SAE_TRANSITION:
- if (pskType == PSK_SAE) {
- return concise ? context.getString(R.string.wifi_security_short_psk_sae) :
- context.getString(R.string.wifi_security_psk_sae);
- } else {
- return concise ? context.getString(R.string.wifi_security_short_sae) :
- context.getString(R.string.wifi_security_sae);
- }
- case SECURITY_OWE_TRANSITION:
- if (mConfig != null && getSecurity(mConfig) == SECURITY_OWE) {
- return concise ? context.getString(R.string.wifi_security_short_owe) :
- context.getString(R.string.wifi_security_owe);
- }
- return concise ? "" : context.getString(R.string.wifi_security_none);
+ return concise ? context.getString(R.string.wifi_security_short_sae) :
+ context.getString(R.string.wifi_security_sae);
case SECURITY_OWE:
return concise ? context.getString(R.string.wifi_security_short_owe) :
context.getString(R.string.wifi_security_owe);
@@ -983,18 +1069,6 @@
return null;
}
- public boolean isCarrierAp() {
- return mIsCarrierAp;
- }
-
- public int getCarrierApEapType() {
- return mCarrierApEapType;
- }
-
- public String getCarrierName() {
- return mCarrierName;
- }
-
public String getSavedNetworkSummary() {
WifiConfiguration config = mConfig;
if (config != null) {
@@ -1070,15 +1144,9 @@
summary.append(mContext.getString(R.string.tap_to_sign_up));
}
} else if (isActive()) {
- if (getDetailedState() == DetailedState.CONNECTED && mIsCarrierAp) {
- // This is the active connection on a carrier AP
- summary.append(String.format(mContext.getString(R.string.connected_via_carrier),
- mCarrierName));
- } else {
- summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
- mInfo != null && mInfo.isEphemeral(),
- mInfo != null ? mInfo.getAppPackageName() : null));
- }
+ summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
+ mInfo != null && mInfo.isEphemeral(),
+ mInfo != null ? mInfo.getAppPackageName() : null));
} else { // not active
if (mConfig != null && mConfig.hasNoInternetAccess()) {
int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
@@ -1102,9 +1170,6 @@
summary.append(mContext.getString(R.string.wifi_disabled_generic));
break;
}
- } else if (mIsCarrierAp) {
- summary.append(String.format(mContext.getString(
- R.string.available_via_carrier), mCarrierName));
} else if (!isReachable()) { // Wifi out of range
summary.append(mContext.getString(R.string.wifi_not_in_range));
} else { // In range, not disabled.
@@ -1250,7 +1315,7 @@
if (networkId != WifiConfiguration.INVALID_NETWORK_ID) {
return networkId == info.getNetworkId();
} else if (config != null) {
- return isKeyEqual(getKey(config));
+ return matches(config, info);
} else {
// Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
// (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
@@ -1284,7 +1349,7 @@
mConfig = new WifiConfiguration();
mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
- if (security == SECURITY_NONE || !getWifiManager().isEasyConnectSupported()) {
+ if (security == SECURITY_NONE) {
mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
} else {
mConfig.allowedKeyManagement.set(KeyMgmt.OWE);
@@ -1316,49 +1381,17 @@
if (mProviderFriendlyName != null) {
savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
}
- savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
- savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
- savedState.putString(KEY_CARRIER_NAME, mCarrierName);
savedState.putLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS,
mSubscriptionExpirationTimeInMillis);
savedState.putInt(KEY_PASSPOINT_CONFIGURATION_VERSION, mPasspointConfigurationVersion);
+ savedState.putBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE, mIsPskSaeTransitionMode);
+ savedState.putBoolean(KEY_IS_OWE_TRANSITION_MODE, mIsOweTransitionMode);
}
public void setListener(AccessPointListener listener) {
mAccessPointListener = listener;
}
- private static final String sPskSuffix = "," + String.valueOf(SECURITY_PSK);
- private static final String sSaeSuffix = "," + String.valueOf(SECURITY_SAE);
- private static final String sPskSaeSuffix = "," + String.valueOf(SECURITY_PSK_SAE_TRANSITION);
- private static final String sOweSuffix = "," + String.valueOf(SECURITY_OWE);
- private static final String sOpenSuffix = "," + String.valueOf(SECURITY_NONE);
- private static final String sOweTransSuffix = "," + String.valueOf(SECURITY_OWE_TRANSITION);
-
- private boolean isKeyEqual(String compareTo) {
- if (mKey == null) {
- return false;
- }
-
- if (compareTo.endsWith(sPskSuffix) || compareTo.endsWith(sSaeSuffix)) {
- if (mKey.endsWith(sPskSaeSuffix)) {
- // Special handling for PSK-SAE transition mode. If the AP has advertised both,
- // we compare the key with both PSK and SAE for a match.
- return TextUtils.equals(mKey.substring(0, mKey.lastIndexOf(',')),
- compareTo.substring(0, compareTo.lastIndexOf(',')));
- }
- }
- if (compareTo.endsWith(sOpenSuffix) || compareTo.endsWith(sOweSuffix)) {
- if (mKey.endsWith(sOweTransSuffix)) {
- // Special handling for OWE/Open networks. If AP advertises OWE in transition mode
- // and we have an Open network saved, allow this connection to be established.
- return TextUtils.equals(mKey.substring(0, mKey.lastIndexOf(',')),
- compareTo.substring(0, compareTo.lastIndexOf(',')));
- }
- }
- return mKey.equals(compareTo);
- }
-
/**
* Sets {@link #mScanResults} to the given collection and updates info based on the best RSSI
* scan result.
@@ -1375,11 +1408,10 @@
// Passpoint networks are not bound to a specific SSID/BSSID, so skip this for passpoint.
if (mKey != null && !isPasspoint() && !isOsuProvider()) {
for (ScanResult result : scanResults) {
- String scanResultKey = AccessPoint.getKey(result);
- if (!isKeyEqual(scanResultKey)) {
+ if (!matches(result)) {
Log.d(TAG, String.format(
- "ScanResult %s\nkey of %s did not match current AP key %s",
- result, scanResultKey, mKey));
+ "ScanResult %s\nkey of %s did not match current AP key %s",
+ result, getKey(mContext, result), mKey));
return;
}
}
@@ -1653,11 +1685,8 @@
private static int getPskType(ScanResult result) {
boolean wpa = result.capabilities.contains("WPA-PSK");
boolean wpa2 = result.capabilities.contains("RSN-PSK");
- boolean wpa3TransitionMode = result.capabilities.contains("PSK+SAE");
boolean wpa3 = result.capabilities.contains("RSN-SAE");
- if (wpa3TransitionMode) {
- return PSK_SAE;
- } else if (wpa2 && wpa) {
+ if (wpa2 && wpa) {
return PSK_WPA_WPA2;
} else if (wpa2) {
return PSK_WPA2;
@@ -1684,22 +1713,37 @@
return EAP_UNKNOWN;
}
- private static int getSecurity(ScanResult result) {
- if (result.capabilities.contains("WEP")) {
+ private static int getSecurity(Context context, ScanResult result) {
+ final boolean isWep = result.capabilities.contains("WEP");
+ final boolean isSae = result.capabilities.contains("SAE");
+ final boolean isPsk = result.capabilities.contains("PSK");
+ final boolean isEapSuiteB192 = result.capabilities.contains("EAP_SUITE_B_192");
+ final boolean isEap = result.capabilities.contains("EAP");
+ final boolean isOwe = result.capabilities.contains("OWE");
+ final boolean isOweTransition = result.capabilities.contains("OWE_TRANSITION");
+
+ if (isSae && isPsk) {
+ final WifiManager wifiManager = (WifiManager)
+ context.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager.isWpa3SaeSupported() ? SECURITY_SAE : SECURITY_PSK;
+ }
+ if (isOweTransition) {
+ final WifiManager wifiManager = (WifiManager)
+ context.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager.isEnhancedOpenSupported() ? SECURITY_OWE : SECURITY_NONE;
+ }
+
+ if (isWep) {
return SECURITY_WEP;
- } else if (result.capabilities.contains("PSK+SAE")) {
- return SECURITY_PSK_SAE_TRANSITION;
- } else if (result.capabilities.contains("SAE")) {
+ } else if (isSae) {
return SECURITY_SAE;
- } else if (result.capabilities.contains("PSK")) {
+ } else if (isPsk) {
return SECURITY_PSK;
- } else if (result.capabilities.contains("EAP_SUITE_B_192")) {
+ } else if (isEapSuiteB192) {
return SECURITY_EAP_SUITE_B;
- } else if (result.capabilities.contains("EAP")) {
+ } else if (isEap) {
return SECURITY_EAP;
- } else if (result.capabilities.contains("OWE_TRANSITION")) {
- return SECURITY_OWE_TRANSITION;
- } else if (result.capabilities.contains("OWE")) {
+ } else if (isOwe) {
return SECURITY_OWE;
}
return SECURITY_NONE;
@@ -1745,10 +1789,6 @@
return "SUITE_B";
} else if (security == SECURITY_OWE) {
return "OWE";
- } else if (security == SECURITY_PSK_SAE_TRANSITION) {
- return "PSK+SAE";
- } else if (security == SECURITY_OWE_TRANSITION) {
- return "OWE_TRANSITION";
}
return "NONE";
}
@@ -1776,8 +1816,7 @@
* Return true if this is an open network AccessPoint.
*/
public boolean isOpenNetwork() {
- return security == SECURITY_NONE || security == SECURITY_OWE
- || security == SECURITY_OWE_TRANSITION;
+ return security == SECURITY_NONE || security == SECURITY_OWE;
}
/**
@@ -1926,4 +1965,61 @@
}
}
}
+
+ public boolean isPskSaeTransitionMode() {
+ return mIsPskSaeTransitionMode;
+ }
+
+ public boolean isOweTransitionMode() {
+ return mIsOweTransitionMode;
+ }
+
+ private static boolean isPskSaeTransitionMode(ScanResult scanResult) {
+ return scanResult.capabilities.contains("PSK")
+ && scanResult.capabilities.contains("SAE");
+ }
+
+ private static boolean isOweTransitionMode(ScanResult scanResult) {
+ return scanResult.capabilities.contains("OWE_TRANSITION");
+ }
+
+ private boolean isSameSsidOrBssid(ScanResult scanResult) {
+ if (scanResult == null) {
+ return false;
+ }
+
+ if (TextUtils.equals(ssid, scanResult.SSID)) {
+ return true;
+ } else if (scanResult.BSSID != null && TextUtils.equals(bssid, scanResult.BSSID)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSameSsidOrBssid(WifiInfo wifiInfo) {
+ if (wifiInfo == null) {
+ return false;
+ }
+
+ if (TextUtils.equals(ssid, removeDoubleQuotes(wifiInfo.getSSID()))) {
+ return true;
+ } else if (wifiInfo.getBSSID() != null && TextUtils.equals(bssid, wifiInfo.getBSSID())) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSameSsidOrBssid(AccessPoint accessPoint) {
+ if (accessPoint == null) {
+ return false;
+ }
+
+ if (TextUtils.equals(ssid, accessPoint.getSsid())) {
+ return true;
+ } else if (accessPoint.getBssid() != null
+ && TextUtils.equals(bssid, accessPoint.getBssid())) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index dae5464..6269a71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -201,8 +201,7 @@
return;
}
if ((mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE)
- && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)
- && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE_TRANSITION)) {
+ && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)) {
mFrictionSld.setState(STATE_SECURED);
} else if (mAccessPoint.isMetered()) {
mFrictionSld.setState(STATE_METERED);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 17a73ac..4a4f0e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -56,8 +56,6 @@
private int mSecurity = AccessPoint.SECURITY_NONE;
private WifiConfiguration mWifiConfig;
private WifiInfo mWifiInfo;
- private boolean mIsCarrierAp = false;
- private String mCarrierName = null;
Context mContext;
private ArrayList<ScanResult> mScanResults;
@@ -99,10 +97,6 @@
}
bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity);
bundle.putInt(AccessPoint.KEY_SPEED, mSpeed);
- bundle.putBoolean(AccessPoint.KEY_IS_CARRIER_AP, mIsCarrierAp);
- if (mCarrierName != null) {
- bundle.putString(AccessPoint.KEY_CARRIER_NAME, mCarrierName);
- }
AccessPoint ap = new AccessPoint(mContext, bundle);
ap.setRssi(mRssi);
@@ -241,16 +235,6 @@
return this;
}
- public TestAccessPointBuilder setIsCarrierAp(boolean isCarrierAp) {
- mIsCarrierAp = isCarrierAp;
- return this;
- }
-
- public TestAccessPointBuilder setCarrierName(String carrierName) {
- mCarrierName = carrierName;
- return this;
- }
-
public TestAccessPointBuilder setScoredNetworkCache(
ArrayList<TimestampedScoredNetwork> scoredNetworkCache) {
mScoredNetworkCache = scoredNetworkCache;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 22f47f1..440315f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -152,6 +152,14 @@
// TODO(b/70983952): Fill this method in
}
+ /**
+ * Result of the sign-in request indecated by the WifiEntry.SIGNIN_STATUS constants
+ */
+ public void onSignInResult(int status) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+
private void updateIcon(int level) {
if (level == -1) {
setIcon(null);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 23b16e8..ba6a8ea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -70,8 +70,10 @@
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
/**
* Tracks saved or available wifi networks and their state.
@@ -475,7 +477,7 @@
continue;
}
- String apKey = AccessPoint.getKey(result);
+ String apKey = AccessPoint.getKey(mContext, result);
List<ScanResult> resultList;
if (scanResultsByApKey.containsKey(apKey)) {
resultList = scanResultsByApKey.get(apKey);
@@ -547,14 +549,6 @@
private void updateAccessPoints(final List<ScanResult> newScanResults,
List<WifiConfiguration> configs) {
- // Map configs and scan results necessary to make AccessPoints
- final Map<String, WifiConfiguration> configsByKey = new ArrayMap(configs.size());
- if (configs != null) {
- for (WifiConfiguration config : configs) {
- configsByKey.put(AccessPoint.getKey(config), config);
- }
- }
-
WifiConfiguration connectionConfig = null;
if (mLastInfo != null) {
connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), configs);
@@ -586,7 +580,26 @@
getCachedOrCreate(entry.getValue(), cachedAccessPoints);
// Update the matching config if there is one, to populate saved network info
- accessPoint.update(configsByKey.get(entry.getKey()));
+ final List<WifiConfiguration> matchedConfigs = configs.stream()
+ .filter(config -> accessPoint.matches(config))
+ .collect(Collectors.toList());
+
+ final int matchedConfigCount = matchedConfigs.size();
+ if (matchedConfigCount == 0) {
+ accessPoint.update(null);
+ } else if (matchedConfigCount == 1) {
+ accessPoint.update(matchedConfigs.get(0));
+ } else {
+ // We may have 2 matched configured WifiCongiguration if the AccessPoint is
+ // of PSK/SAE transition mode or open/OWE transition mode.
+ Optional<WifiConfiguration> preferredConfig = matchedConfigs.stream()
+ .filter(config -> isSaeOrOwe(config)).findFirst();
+ if (preferredConfig.isPresent()) {
+ accessPoint.update(preferredConfig.get());
+ } else {
+ accessPoint.update(matchedConfigs.get(0));
+ }
+ }
accessPoints.add(accessPoint);
}
@@ -652,6 +665,11 @@
conditionallyNotifyListeners();
}
+ private static boolean isSaeOrOwe(WifiConfiguration config) {
+ final int security = AccessPoint.getSecurity(config);
+ return security == AccessPoint.SECURITY_SAE || security == AccessPoint.SECURITY_OWE;
+ }
+
@VisibleForTesting
List<AccessPoint> updatePasspointAccessPoints(
List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> passpointConfigsAndScans,
@@ -700,7 +718,8 @@
private AccessPoint getCachedOrCreate(
List<ScanResult> scanResults,
List<AccessPoint> cache) {
- AccessPoint accessPoint = getCachedByKey(cache, AccessPoint.getKey(scanResults.get(0)));
+ AccessPoint accessPoint = getCachedByKey(cache,
+ AccessPoint.getKey(mContext, scanResults.get(0)));
if (accessPoint == null) {
accessPoint = new AccessPoint(mContext, scanResults);
} else {
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 7b1c382..61cbbd3 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
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -39,7 +40,7 @@
import android.net.WifiKey;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -472,51 +473,6 @@
}
@Test
- public void testSummaryString_showsAvaiableViaCarrier() {
- String carrierName = "Test Carrier";
- ScanResult result = new ScanResult();
- result.BSSID = "00:11:22:33:44:55";
- result.capabilities = "EAP";
- result.isCarrierAp = true;
- result.carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
- result.carrierName = carrierName;
-
- AccessPoint ap = new AccessPoint(mContext, Collections.singletonList(result));
- assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
- R.string.available_via_carrier), carrierName));
- assertThat(ap.isCarrierAp()).isEqualTo(true);
- assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.SIM);
- assertThat(ap.getCarrierName()).isEqualTo(carrierName);
- }
-
- @Test
- public void testSummaryString_showsConnectedViaCarrier() {
- int networkId = 123;
- int rssi = -55;
- String carrierName = "Test Carrier";
- WifiConfiguration config = new WifiConfiguration();
- config.networkId = networkId;
- WifiInfo wifiInfo = new WifiInfo();
- wifiInfo.setNetworkId(networkId);
- wifiInfo.setRssi(rssi);
-
- NetworkInfo networkInfo =
- new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
- networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
-
- AccessPoint ap = new TestAccessPointBuilder(mContext)
- .setNetworkInfo(networkInfo)
- .setNetworkId(networkId)
- .setRssi(rssi)
- .setWifiInfo(wifiInfo)
- .setIsCarrierAp(true)
- .setCarrierName(carrierName)
- .build();
- assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
- R.string.connected_via_carrier), carrierName));
- }
-
- @Test
public void testSummaryString_showsDisconnected() {
AccessPoint ap = createAccessPointWithScanResultCache();
ap.update(new WifiConfiguration());
@@ -579,31 +535,6 @@
assertThat(ap.getSummary()).isEqualTo("Connected via Test App");
}
- @Test
- public void testSetScanResultWithCarrierInfo() {
- String ssid = "ssid";
- AccessPoint ap = new TestAccessPointBuilder(mContext).setSsid(ssid).build();
- assertThat(ap.isCarrierAp()).isEqualTo(false);
- assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.NONE);
- assertThat(ap.getCarrierName()).isEqualTo(null);
-
- int carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
- String carrierName = "Test Carrier";
- ScanResult scanResult = new ScanResult();
- scanResult.SSID = ssid;
- scanResult.BSSID = "00:11:22:33:44:55";
- scanResult.capabilities = "";
- scanResult.isCarrierAp = true;
- scanResult.carrierApEapType = carrierApEapType;
- scanResult.carrierName = carrierName;
-
-
- ap.setScanResults(Collections.singletonList(scanResult));
- assertThat(ap.isCarrierAp()).isEqualTo(true);
- assertThat(ap.getCarrierApEapType()).isEqualTo(carrierApEapType);
- assertThat(ap.getCarrierName()).isEqualTo(carrierName);
- }
-
private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
return buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve);
}
@@ -1273,7 +1204,7 @@
@Test
public void testGetKey_matchesKeysCorrectly() {
AccessPoint ap = new AccessPoint(mContext, mScanResults);
- assertThat(ap.getKey()).isEqualTo(AccessPoint.getKey(mScanResults.get(0)));
+ assertThat(ap.getKey()).isEqualTo(AccessPoint.getKey(mContext, mScanResults.get(0)));
WifiConfiguration spyConfig = spy(new WifiConfiguration());
when(spyConfig.isPasspoint()).thenReturn(true);
@@ -1295,6 +1226,44 @@
}
/**
+ * Test that getKey returns a key of SAE type for a PSK/SAE transition mode ScanResult.
+ */
+ @Test
+ public void testGetKey_supportSaeTransitionMode_shouldGetSaeKey() {
+ ScanResult scanResult = createScanResult(TEST_SSID, TEST_BSSID, DEFAULT_RSSI);
+ scanResult.capabilities =
+ "[WPA2-FT/PSK-CCMP][RSN-FT/PSK+PSK-SHA256+SAE+FT/SAE-CCMP][ESS][WPS]";
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true);
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ StringBuilder key = new StringBuilder();
+ key.append(AccessPoint.KEY_PREFIX_AP);
+ key.append(TEST_SSID);
+ key.append(',');
+ key.append(AccessPoint.SECURITY_SAE);
+
+ assertThat(AccessPoint.getKey(mMockContext, scanResult)).isEqualTo(key.toString());
+ }
+
+ /**
+ * Test that getKey returns a key of PSK type for a PSK/SAE transition mode ScanResult.
+ */
+ @Test
+ public void testGetKey_notSupportSaeTransitionMode_shouldGetPskKey() {
+ ScanResult scanResult = createScanResult(TEST_SSID, TEST_BSSID, DEFAULT_RSSI);
+ scanResult.capabilities =
+ "[WPA2-FT/PSK-CCMP][RSN-FT/PSK+PSK-SHA256+SAE+FT/SAE-CCMP][ESS][WPS]";
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(false);
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ StringBuilder key = new StringBuilder();
+ key.append(AccessPoint.KEY_PREFIX_AP);
+ key.append(TEST_SSID);
+ key.append(',');
+ key.append(AccessPoint.SECURITY_PSK);
+
+ assertThat(AccessPoint.getKey(mMockContext, scanResult)).isEqualTo(key.toString());
+ }
+
+ /**
* Verifies that the Passpoint AccessPoint constructor creates AccessPoints whose isPasspoint()
* returns true.
*/
@@ -1396,8 +1365,15 @@
*/
@Test
public void testOsuAccessPointSummary_showsProvisioningUpdates() {
- AccessPoint osuAccessPoint = new AccessPoint(mContext, createOsuProvider(),
+ OsuProvider provider = createOsuProvider();
+ Context spyContext = spy(new ContextWrapper(mContext));
+ AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider,
mScanResults);
+ Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>();
+ osuProviderConfigMap.put(provider, null);
+ when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.getMatchingPasspointConfigsForOsuProviders(
+ Collections.singleton(provider))).thenReturn(osuProviderConfigMap);
osuAccessPoint.setListener(mMockAccessPointListener);
@@ -1537,12 +1513,158 @@
bundle.putInt("key_security", i);
ap = new AccessPoint(InstrumentationRegistry.getTargetContext(), bundle);
- if (i == AccessPoint.SECURITY_NONE || i == AccessPoint.SECURITY_OWE
- || i == AccessPoint.SECURITY_OWE_TRANSITION) {
+ if (i == AccessPoint.SECURITY_NONE || i == AccessPoint.SECURITY_OWE) {
assertThat(ap.isOpenNetwork()).isTrue();
} else {
assertThat(ap.isOpenNetwork()).isFalse();
}
}
}
+
+ /**
+ * Verifies that matches(AccessPoint other) matches a PSK/SAE transition mode AP to a PSK or a
+ * SAE AP.
+ */
+ @Test
+ public void testMatches1_transitionModeApMatchesNotTransitionModeAp_shouldMatchCorrectly() {
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true);
+ AccessPoint pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ // Transition mode AP matches a SAE AP.
+ AccessPoint saeAccessPoint = new TestAccessPointBuilder(mContext)
+ .setSsid(AccessPoint.removeDoubleQuotes(TEST_SSID))
+ .setSecurity(AccessPoint.SECURITY_SAE)
+ .build();
+ assertThat(pskSaeTransitionModeAp.matches(saeAccessPoint)).isTrue();
+
+ // Transition mode AP matches a PSK AP.
+ AccessPoint pskAccessPoint = new TestAccessPointBuilder(mContext)
+ .setSsid(AccessPoint.removeDoubleQuotes(TEST_SSID))
+ .setSecurity(AccessPoint.SECURITY_PSK)
+ .build();
+
+ assertThat(pskSaeTransitionModeAp.matches(pskAccessPoint)).isTrue();
+
+ // Transition mode AP does not match a SAE AP if the device does not support SAE.
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(false);
+ pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+ saeAccessPoint = new TestAccessPointBuilder(mContext)
+ .setSsid(AccessPoint.removeDoubleQuotes(TEST_SSID))
+ .setSecurity(AccessPoint.SECURITY_SAE)
+ .build();
+
+ assertThat(pskSaeTransitionModeAp.matches(saeAccessPoint)).isFalse();
+ }
+
+ /**
+ * Verifies that matches(WifiConfiguration config) matches a PSK/SAE transition mode AP to a PSK
+ * or a SAE WifiConfiguration.
+ */
+ @Test
+ public void testMatches2_transitionModeApMatchesNotTransitionModeAp_shouldMatchCorrectly() {
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true);
+ AccessPoint pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ // Transition mode AP matches a SAE WifiConfiguration.
+ WifiConfiguration saeConfig = new WifiConfiguration();
+ saeConfig.SSID = TEST_SSID;
+ saeConfig.allowedKeyManagement.set(KeyMgmt.SAE);
+
+ assertThat(pskSaeTransitionModeAp.matches(saeConfig)).isTrue();
+
+ // Transition mode AP matches a PSK WifiConfiguration.
+ WifiConfiguration pskConfig = new WifiConfiguration();
+ pskConfig.SSID = TEST_SSID;
+ pskConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+
+ assertThat(pskSaeTransitionModeAp.matches(pskConfig)).isTrue();
+
+ // Transition mode AP does not matches a SAE WifiConfiguration if the device does not
+ // support SAE.
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(false);
+ pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ assertThat(pskSaeTransitionModeAp.matches(saeConfig)).isFalse();
+ }
+
+ /**
+ * Verifies that matches(ScanResult scanResult) matches a PSK/SAE transition mode AP to a PSK
+ * or a SAE ScanResult.
+ */
+ @Test
+ public void testMatches3_transitionModeApMatchesNotTransitionModeAp_shouldMatchCorrectly() {
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(true);
+ AccessPoint pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ // Transition mode AP matches a SAE ScanResult.
+ ScanResult saeScanResult = createScanResult(AccessPoint.removeDoubleQuotes(TEST_SSID),
+ TEST_BSSID, DEFAULT_RSSI);
+ saeScanResult.capabilities = "[SAE-CCMP][ESS][WPS]";
+
+ assertThat(pskSaeTransitionModeAp.matches(saeScanResult)).isTrue();
+
+ // Transition mode AP matches a PSK ScanResult.
+ ScanResult pskScanResult = createScanResult(AccessPoint.removeDoubleQuotes(TEST_SSID),
+ TEST_BSSID, DEFAULT_RSSI);
+ pskScanResult.capabilities = "[RSN-PSK-CCMP][ESS][WPS]";
+
+ assertThat(pskSaeTransitionModeAp.matches(pskScanResult)).isTrue();
+
+ // Transition mode AP does not matches a SAE ScanResult if the device does not support SAE.
+ when(mMockWifiManager.isWpa3SaeSupported()).thenReturn(false);
+ pskSaeTransitionModeAp = getPskSaeTransitionModeAp();
+
+ assertThat(pskSaeTransitionModeAp.matches(saeScanResult)).isFalse();
+ }
+
+ @Test
+ public void testGetSecurityString_oweTransitionMode_shouldReturnCorrectly() {
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.isEnhancedOpenSupported()).thenReturn(true);
+ AccessPoint oweTransitionModeAp = getOweTransitionModeAp();
+
+ assertThat(oweTransitionModeAp.getSecurityString(true /* concise */))
+ .isEqualTo(mContext.getString(R.string.wifi_security_short_none_owe));
+ assertThat(oweTransitionModeAp.getSecurityString(false /* concise */))
+ .isEqualTo(mContext.getString(R.string.wifi_security_none_owe));
+ }
+
+ private AccessPoint getPskSaeTransitionModeAp() {
+ ScanResult scanResult = createScanResult(AccessPoint.removeDoubleQuotes(TEST_SSID),
+ TEST_BSSID, DEFAULT_RSSI);
+ scanResult.capabilities =
+ "[WPA2-FT/PSK-CCMP][RSN-FT/PSK+PSK-SHA256+SAE+FT/SAE-CCMP][ESS][WPS]";
+ return new TestAccessPointBuilder(mMockContext)
+ .setScanResults(new ArrayList<ScanResult>(Arrays.asList(scanResult)))
+ .build();
+ }
+
+ private AccessPoint getOweTransitionModeAp() {
+ ScanResult scanResult = createScanResult(AccessPoint.removeDoubleQuotes(TEST_SSID),
+ TEST_BSSID, DEFAULT_RSSI);
+ scanResult.capabilities = "[OWE_TRANSITION]";
+ return new TestAccessPointBuilder(mContext)
+ .setScanResults(new ArrayList<ScanResult>(Arrays.asList(scanResult)))
+ .build();
+ }
+
+ @Test
+ public void testGenerateOpenNetworkConfig_oweNotSupported_shouldGetCorrectSecurity() {
+ when(mMockContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ AccessPoint oweAccessPoint = new TestAccessPointBuilder(mMockContext)
+ .setSecurity(AccessPoint.SECURITY_OWE).build();
+ AccessPoint noneAccessPoint = new TestAccessPointBuilder(mMockContext)
+ .setSecurity(AccessPoint.SECURITY_NONE).build();
+
+ oweAccessPoint.generateOpenNetworkConfig();
+ noneAccessPoint.generateOpenNetworkConfig();
+
+ assertThat(oweAccessPoint.getConfig().allowedKeyManagement.get(KeyMgmt.NONE)).isFalse();
+ assertThat(oweAccessPoint.getConfig().allowedKeyManagement.get(KeyMgmt.OWE)).isTrue();
+ assertThat(noneAccessPoint.getConfig().allowedKeyManagement.get(KeyMgmt.NONE)).isTrue();
+ assertThat(noneAccessPoint.getConfig().allowedKeyManagement.get(KeyMgmt.OWE)).isFalse();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index fc69b1a..f18ffe1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -34,6 +34,8 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.text.TextUtils;
@@ -69,6 +71,8 @@
private LocationManager mLocationManager;
@Mock
private ServiceState mServiceState;
+ @Mock
+ private NetworkRegistrationInfo mNetworkRegistrationInfo;
@Before
public void setUp() {
@@ -207,6 +211,7 @@
@Test
public void isInService_voiceInService_returnTrue() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isTrue();
}
@@ -214,15 +219,23 @@
public void isInService_voiceOutOfServiceDataInService_returnTrue() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+
assertThat(Utils.isInService(mServiceState)).isTrue();
}
@Test
public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
- when(mServiceState.getDataNetworkType())
- .thenReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -230,12 +243,14 @@
public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@Test
public void isInService_ServiceStatePowerOff_returnFalse() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+
assertThat(Utils.isInService(mServiceState)).isFalse();
}
@@ -248,6 +263,7 @@
@Test
public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_POWER_OFF);
}
@@ -255,6 +271,7 @@
@Test
public void getCombinedServiceState_voiceInService_returnInService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_IN_SERVICE);
}
@@ -263,14 +280,33 @@
public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_IN_SERVICE);
}
@Test
+ public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
+ when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+ when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+ when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+ assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
+ ServiceState.STATE_OUT_OF_SERVICE);
+ }
+
+ @Test
public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
when(mServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+
assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
ServiceState.STATE_OUT_OF_SERVICE);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 8a0ae91..ed0857c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -26,6 +26,8 @@
import android.content.Context;
import android.content.Intent;
+import androidx.preference.Preference;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import org.junit.Before;
@@ -60,19 +62,42 @@
}
@Test
- public void logDashboardStartIntent_intentEmpty_shouldNotLog() {
- mProvider.logDashboardStartIntent(mContext, null /* intent */,
+ public void logClickedPreference_preferenceEmpty_shouldNotLog() {
+ final boolean loggable = mProvider.logClickedPreference(null /* preference */,
MetricsEvent.SETTINGS_GESTURES);
+ assertThat(loggable).isFalse();
verifyNoMoreInteractions(mLogWriter);
}
@Test
- public void logDashboardStartIntent_intentHasNoComponent_shouldLog() {
+ public void logClickedPreference_preferenceHasKey_shouldLog() {
+ final String key = "abc";
+ final Preference preference = new Preference(mContext);
+ preference.setKey(key);
+
+ final boolean loggable = mProvider.logClickedPreference(preference,
+ MetricsEvent.SETTINGS_GESTURES);
+
+ assertThat(loggable).isTrue();
+ verify(mLogWriter).action(
+ MetricsEvent.SETTINGS_GESTURES,
+ MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+ SettingsEnums.PAGE_UNKNOWN,
+ key,
+ 0);
+ }
+
+ @Test
+ public void logClickedPreference_preferenceHasIntent_shouldLog() {
+ final Preference preference = new Preference(mContext);
final Intent intent = new Intent(Intent.ACTION_ASSIST);
+ preference.setIntent(intent);
- mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
+ final boolean loggable = mProvider.logClickedPreference(preference,
+ MetricsEvent.SETTINGS_GESTURES);
+ assertThat(loggable).isTrue();
verify(mLogWriter).action(
MetricsEvent.SETTINGS_GESTURES,
MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
@@ -82,11 +107,54 @@
}
@Test
- public void logDashboardStartIntent_intentIsExternal_shouldLog() {
+ public void logClickedPreference_preferenceHasFragment_shouldLog() {
+ final Preference preference = new Preference(mContext);
+ final String fragment = "com.android.settings.tts.TextToSpeechSettings";
+ preference.setFragment(fragment);
+
+ final boolean loggable = mProvider.logClickedPreference(preference,
+ MetricsEvent.SETTINGS_GESTURES);
+
+ assertThat(loggable).isTrue();
+ verify(mLogWriter).action(
+ MetricsEvent.SETTINGS_GESTURES,
+ MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+ SettingsEnums.PAGE_UNKNOWN,
+ fragment,
+ 0);
+ }
+
+ @Test
+ public void logStartedIntent_intentEmpty_shouldNotLog() {
+ final boolean loggable = mProvider.logStartedIntent(null /* intent */,
+ MetricsEvent.SETTINGS_GESTURES);
+
+ assertThat(loggable).isFalse();
+ verifyNoMoreInteractions(mLogWriter);
+ }
+
+ @Test
+ public void logStartedIntent_intentHasNoComponent_shouldLog() {
+ final Intent intent = new Intent(Intent.ACTION_ASSIST);
+
+ final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES);
+
+ assertThat(loggable).isTrue();
+ verify(mLogWriter).action(
+ MetricsEvent.SETTINGS_GESTURES,
+ MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
+ SettingsEnums.PAGE_UNKNOWN,
+ Intent.ACTION_ASSIST,
+ 0);
+ }
+
+ @Test
+ public void logStartedIntent_intentIsExternal_shouldLog() {
final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
- mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
+ final boolean loggable = mProvider.logStartedIntent(intent, MetricsEvent.SETTINGS_GESTURES);
+ assertThat(loggable).isTrue();
verify(mLogWriter).action(
MetricsEvent.SETTINGS_GESTURES,
MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java
index e936c35..68a3729 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAccessesTest.java
@@ -17,8 +17,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
-import android.util.LongSparseLongArray;
-import android.util.Pair;
+import android.util.LongSparseArray;
import org.junit.Before;
import org.junit.Ignore;
@@ -159,14 +158,11 @@
private OpEntry createOpEntryWithTime(int op, long time) {
// Slot for background access timestamp.
- final LongSparseLongArray accessTimes = new LongSparseLongArray();
- accessTimes.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_BACKGROUND,
- AppOpsManager.OP_FLAG_SELF), time);
+ final LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>();
+ accessEvents.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_BACKGROUND,
+ AppOpsManager.OP_FLAG_SELF), new AppOpsManager.NoteOpEvent(time, -1, null));
- OpFeatureEntry.Builder featureEntry = new OpFeatureEntry.Builder(false, accessTimes,
- null /*rejectTimes*/, null /*durations*/, null /* proxyUids */,
- null /* proxyPackages */, null /* proxyFeatureIds */);
- return new OpEntry(op, AppOpsManager.MODE_ALLOWED,
- new Pair[]{new Pair(null, featureEntry)});
+ return new OpEntry(op, AppOpsManager.MODE_ALLOWED, Collections.singletonMap(null,
+ new OpFeatureEntry(op, false, accessEvents, null)));
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
index 3fde48b..3f8d758 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
@@ -17,6 +17,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
import android.util.Pair;
@@ -158,17 +159,11 @@
}
private OpEntry createOpEntryWithTime(int op, long time, int duration) {
- final LongSparseLongArray accessTimes = new LongSparseLongArray();
- accessTimes.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_TOP,
- AppOpsManager.OP_FLAG_SELF), time);
- final LongSparseLongArray durations = new LongSparseLongArray();
- durations.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_TOP,
- AppOpsManager.OP_FLAG_SELF), duration);
+ final LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>();
+ accessEvents.put(AppOpsManager.makeKey(AppOpsManager.UID_STATE_TOP,
+ AppOpsManager.OP_FLAG_SELF), new AppOpsManager.NoteOpEvent(time, duration, null));
- OpFeatureEntry.Builder featureEntry = new OpFeatureEntry.Builder(false, accessTimes,
- null /*rejectTimes*/, durations, null /* proxyUids */,
- null /* proxyPackages */, null /* proxyFeatureIds */);
- return new OpEntry(op, AppOpsManager.MODE_ALLOWED,
- new Pair[]{new Pair(null, featureEntry)});
+ return new OpEntry(op, AppOpsManager.MODE_ALLOWED, Collections.singletonMap(null,
+ new OpFeatureEntry(op, false, accessEvents, null)));
}
}
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 5cae611..d98f50b 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
@@ -87,7 +87,7 @@
public void getMobileTemplate_groupUuidNull_returnMobileAll() {
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
when(mInfo1.getGroupUuid()).thenReturn(null);
- when(mTelephonyManager.getMergedSubscriberIdsFromGroup())
+ when(mTelephonyManager.getMergedImsisFromGroup())
.thenReturn(new String[] {SUBSCRIBER_ID});
final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
@@ -99,7 +99,7 @@
public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
- when(mTelephonyManager.getMergedSubscriberIdsFromGroup())
+ when(mTelephonyManager.getMergedImsisFromGroup())
.thenReturn(new String[] {SUBSCRIBER_ID, SUBSCRIBER_ID_2});
final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
diff --git a/packages/SettingsProvider/res/values-af/strings.xml b/packages/SettingsProvider/res/values-af/strings.xml
index 2c45484..8c2f8b3 100644
--- a/packages/SettingsProvider/res/values-af/strings.xml
+++ b/packages/SettingsProvider/res/values-af/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Instellingsberging"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Veranderings aan jou warmkolinstellings"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Jou warmkolband het verander."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Hierdie toestel steun nie jou voorkeur vir net 5 GHz nie. Hierdie toestel sal pleks daarvan die 5 GHz-band gebruik wanneer dit beskikbaar is."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-am/strings.xml b/packages/SettingsProvider/res/values-am/strings.xml
index 0630560..640301a 100644
--- a/packages/SettingsProvider/res/values-am/strings.xml
+++ b/packages/SettingsProvider/res/values-am/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"የቅንብሮች ማከማቻ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"በእርስዎ ሆትስፖት ቅንብሮች ላይ ለውጦች"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"የእርስዎ ሆትስፖት ባንድ ተለውጧል።"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ይህ መሣሪያ የእርስዎን ምርጫ ለ5GHz ብቻ አይደግፍም። በምትኩ፣ ይህ መሣሪያ ሲገኝ 5GHz ባንድ ይጠቀማል።"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ar/strings.xml b/packages/SettingsProvider/res/values-ar/strings.xml
index 19306a6..2675839 100644
--- a/packages/SettingsProvider/res/values-ar/strings.xml
+++ b/packages/SettingsProvider/res/values-ar/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"تخزين الإعدادات"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"التغييرات التي طرأت على إعدادات نقطة الاتصال"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"تمّ تغيير نطاق نقطة الاتصال الخاصة بك."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"لا يتوافق هذا الجهاز مع إعدادك المفضّل الخاص باستخدام النطاق 5 غيغاهرتز فقط. وسيستخدم الجهاز بدلاً من ذلك النطاق 5 غيغاهرتز عندما يكون متاحًا."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-as/strings.xml b/packages/SettingsProvider/res/values-as/strings.xml
index 13b90d6..b70146a 100644
--- a/packages/SettingsProvider/res/values-as/strings.xml
+++ b/packages/SettingsProvider/res/values-as/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"ছেটিংছসমূহৰ সঞ্চয়াগাৰ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"আপোনাৰ হটস্পট ছেটিংসমূহত হোৱা সালসলনিসমূহ"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"আপোনাৰ হটস্পটৰ বেণ্ড সলনি কৰা হৈছে।"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"আপোনাৰ কেৱল ৫ গিগাহাৰ্টজৰ প্ৰতি অগ্ৰাধিকাৰ এই ডিভাচইচটোৱে সমৰ্থন নকৰে। ইয়াৰ পৰিৱৰ্তে, ডিভাচইচটোৱে যেতিয়া ৫ গিগাহাৰ্টজ বেণ্ড উপলব্ধ হ’ব তেতিয়া তাক ব্যৱহাৰ কৰিব।"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-az/strings.xml b/packages/SettingsProvider/res/values-az/strings.xml
index 4737a1e..8b4f75b 100644
--- a/packages/SettingsProvider/res/values-az/strings.xml
+++ b/packages/SettingsProvider/res/values-az/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Ayarlar Deposu"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot ayarlarınızda dəyişiklik"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot diapazonu dəyişib."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Bu cihaz yalnız 5GHz üçün tərcihinizi dəstəkləmir. Əvəzində, əlçatan olduqda bu cihaz 5GHz diapazonundan istifadə edəcək."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml b/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
index 032dbc5..9c3a00e 100644
--- a/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Podešavanja skladišta"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promene podešavanja za hotspot"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Opseg hotspota je promenjen."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava podešavanje samo za 5 GHz. Uređaj će koristiti opseg od 5 GHz kada bude dostupan."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-be/strings.xml b/packages/SettingsProvider/res/values-be/strings.xml
index 60bdb2d..0e098e7 100644
--- a/packages/SettingsProvider/res/values-be/strings.xml
+++ b/packages/SettingsProvider/res/values-be/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Сховішча налад"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Змяненні ў наладах хот-спота"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Частата хот-спота змянілася."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Прылада не можа працаваць толькі на частаце 5 ГГц. Гэта частата будзе выкарыстоўвацца, калі гэта магчыма."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-bg/strings.xml b/packages/SettingsProvider/res/values-bg/strings.xml
index c3a994b..30526f2 100644
--- a/packages/SettingsProvider/res/values-bg/strings.xml
+++ b/packages/SettingsProvider/res/values-bg/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Настройки за хранилище"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промени в настройките ви за точка за достъп"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Честотната лента на точката ви за достъп е променена."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Това устройство не поддържа предпочитанието ви за използване само на честотната лента от 5 ГХц. Вместо това то ще я ползва, когато е възможно."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-bn/strings.xml b/packages/SettingsProvider/res/values-bn/strings.xml
index 71ee99b..8fc6bbb 100644
--- a/packages/SettingsProvider/res/values-bn/strings.xml
+++ b/packages/SettingsProvider/res/values-bn/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"সেটিংস স্টোরেজ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"আপনার হটস্পট সেটিংসে করা পরিবর্তনগুলি"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"আপনার হটস্পট ব্যান্ড পরিবর্তন করা হয়েছে।"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"এই ডিভাইসটি শুধুমাত্র 5GHz এর জন্য আপনার পছন্দ সমর্থন করে না। পরিবর্তে, এই ডিভাইসটি 5GHz ব্যান্ড ব্যবহার করবে।"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-bs/strings.xml b/packages/SettingsProvider/res/values-bs/strings.xml
index 09d4c68..ddacb32 100644
--- a/packages/SettingsProvider/res/values-bs/strings.xml
+++ b/packages/SettingsProvider/res/values-bs/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Postavke za pohranu podataka"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promjene postavki pristupne tačke"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Opseg pristupne tačke je promijenjen."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava vašu postavku za mreže od isključivo 5 GHz. Uređaj će koristiti opseg of 5 GHz kada bude dostupan."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ca/strings.xml b/packages/SettingsProvider/res/values-ca/strings.xml
index ee6b51e..0c2ad73 100644
--- a/packages/SettingsProvider/res/values-ca/strings.xml
+++ b/packages/SettingsProvider/res/values-ca/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Configuració de l\'emmagatzematge"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Canvis en la configuració del punt d\'accés Wi‑Fi"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ha canviat la teva banda del punt d\'accés Wi‑Fi."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Aquest dispositiu no admet utilitzar exclusivament una banda de 5 GHz. El dispositiu utilitzarà una banda de 5 GHz quan estigui disponible."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-cs/strings.xml b/packages/SettingsProvider/res/values-cs/strings.xml
index f134e05..ab474b1 100644
--- a/packages/SettingsProvider/res/values-cs/strings.xml
+++ b/packages/SettingsProvider/res/values-cs/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Paměť pro nastavení"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Změny nastavení hotspotu"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pásmo hotspotu se změnilo."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Toto zařízení nepodporuje vaše nastavení jen 5GHz pásma. Zařízení použije pásmo 5 GHz, jen když bude dostupné."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-da/strings.xml b/packages/SettingsProvider/res/values-da/strings.xml
index 99988a5..719614c 100644
--- a/packages/SettingsProvider/res/values-da/strings.xml
+++ b/packages/SettingsProvider/res/values-da/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Lagring af indstillinger"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ændringer af dine indstillinger for hotspot"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Dit hotspotbånd er ændret."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Denne enhed understøtter ikke din præference om kun 5 GHz. Denne enhed vil i stedet bruge 5 GHz-båndet, når det er muligt."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-de/strings.xml b/packages/SettingsProvider/res/values-de/strings.xml
index ddcabaa..6e253e0 100644
--- a/packages/SettingsProvider/res/values-de/strings.xml
+++ b/packages/SettingsProvider/res/values-de/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Einstellungsspeicher"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Änderungen an deinen Hotspot-Einstellungen"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Dein Hotspot-Band hat sich geändert."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dieses Gerät unterstützt die ausschließliche Nutzung von 5 GHz nicht. Es greift aber immer auf das 5-GHz-Band zurück, wenn dieses verfügbar ist."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-el/strings.xml b/packages/SettingsProvider/res/values-el/strings.xml
index 924fab5..c47fea2 100644
--- a/packages/SettingsProvider/res/values-el/strings.xml
+++ b/packages/SettingsProvider/res/values-el/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Αποθηκευτικός χώρος ρυθμίσεων"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Αλλαγές στις ρυθμίσεις σημείου πρόσβασης Wi-Fi"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Το εύρος σημείου πρόσβασης Wi-Fi άλλαξε."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Αυτή η συσκευή δεν υποστηρίζει την προτίμησή σας για αποκλειστική χρήση του εύρους 5 GHz. Αντ\' αυτού, αυτή η συσκευή θα χρησιμοποιεί το εύρος 5 GHz όταν είναι διαθέσιμο."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-en-rAU/strings.xml b/packages/SettingsProvider/res/values-en-rAU/strings.xml
index 05cd54e..fac51d83 100644
--- a/packages/SettingsProvider/res/values-en-rAU/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rAU/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-en-rCA/strings.xml b/packages/SettingsProvider/res/values-en-rCA/strings.xml
index 05cd54e..fac51d83 100644
--- a/packages/SettingsProvider/res/values-en-rCA/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rCA/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-en-rGB/strings.xml b/packages/SettingsProvider/res/values-en-rGB/strings.xml
index 05cd54e..fac51d83 100644
--- a/packages/SettingsProvider/res/values-en-rGB/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rGB/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-en-rIN/strings.xml b/packages/SettingsProvider/res/values-en-rIN/strings.xml
index 05cd54e..fac51d83 100644
--- a/packages/SettingsProvider/res/values-en-rIN/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rIN/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-es-rUS/strings.xml b/packages/SettingsProvider/res/values-es-rUS/strings.xml
index d5e6da9..af90257 100644
--- a/packages/SettingsProvider/res/values-es-rUS/strings.xml
+++ b/packages/SettingsProvider/res/values-es-rUS/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Almacenamiento de configuración"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios en la configuración de tu hotspot"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Cambió la banda de tu hotspot."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Si bien este dispositivo no admite la opción para conectarse exclusivamente a bandas de 5 GHz, las usará cuando estén disponibles."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-es/strings.xml b/packages/SettingsProvider/res/values-es/strings.xml
index d5e6da9..ff4ee38 100644
--- a/packages/SettingsProvider/res/values-es/strings.xml
+++ b/packages/SettingsProvider/res/values-es/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Almacenamiento de configuración"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios en los ajustes del punto de acceso"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La banda de tu punto de acceso ha cambiado."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo no admite la opción de conectarse únicamente a bandas de 5 GHz, pero las usará cuando estén disponibles."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-et/strings.xml b/packages/SettingsProvider/res/values-et/strings.xml
index a3e4db6..a0ec593 100644
--- a/packages/SettingsProvider/res/values-et/strings.xml
+++ b/packages/SettingsProvider/res/values-et/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Seadete talletusruum"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Muudatused teie kuumkoha seadetes"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Teie kuumkoha sagedusriba on muutunud."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"See seade ei toeta teie eelistatud ainult 5 GHz riba. Seade kasutab 5 GHz riba ainult siis, kui see on saadaval."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-eu/strings.xml b/packages/SettingsProvider/res/values-eu/strings.xml
index 8d410de1..220b486 100644
--- a/packages/SettingsProvider/res/values-eu/strings.xml
+++ b/packages/SettingsProvider/res/values-eu/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Ezarpenen biltegia"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Aldaketak egin dira sare publikoaren ezarpenetan"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Aldatu da sare publikoaren banda."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Gailuak ez du onartzen 5 GHz-ko banda soilik erabiltzeko hobespena. Horren ordez, erabilgarri dagoen bakoitzean erabiliko da 5 GHz-ko banda."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-fa/strings.xml b/packages/SettingsProvider/res/values-fa/strings.xml
index ed9d168..6819d2f 100644
--- a/packages/SettingsProvider/res/values-fa/strings.xml
+++ b/packages/SettingsProvider/res/values-fa/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"تنظیم محل ذخیره"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"تغییرات در تنظیمات نقطه اتصال"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"نوار نقطه اتصال شما تغییر کرد."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"این دستگاه از اولویت فقط ۵ گیگاهرتز شما پشتیبانی نمیکند. هرزمان نوار ۵ گیگاهرتزی دردسترس باشد، این دستگاه از آن استفاده خواهد کرد."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-fi/strings.xml b/packages/SettingsProvider/res/values-fi/strings.xml
index 9ff2c41..9ad01eb 100644
--- a/packages/SettingsProvider/res/values-fi/strings.xml
+++ b/packages/SettingsProvider/res/values-fi/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Asetuksien tallennus"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot-asetustesi muutokset"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot-taajuutesi on muuttunut."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Tämä laite ei tue asetustasi (vain 5 GHz). Sen sijaan laite käyttää 5 GHz:n taajuutta sen ollessa käytettävissä."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-fr-rCA/strings.xml b/packages/SettingsProvider/res/values-fr-rCA/strings.xml
index fdfdb1d..62951bd 100644
--- a/packages/SettingsProvider/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsProvider/res/values-fr-rCA/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Stockage des paramètres"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifications apportées à vos paramètres de point d\'accès"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La bande de votre point d\'accès a changé."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Cet appareil ne prend pas en charge votre préférence pour la bande de 5 GHz seulement. Au lieu de cela, cet appareil utilisera la bande de 5 GHz lorsqu\'elle sera disponible."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-fr/strings.xml b/packages/SettingsProvider/res/values-fr/strings.xml
index fdfdb1d..56bc65b 100644
--- a/packages/SettingsProvider/res/values-fr/strings.xml
+++ b/packages/SettingsProvider/res/values-fr/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Stockage des paramètres"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifications apportées à vos paramètres de point d\'accès"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La bande utilisée par votre point d\'accès a été modifiée."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Cet appareil n\'est pas conçu pour utiliser exclusivement la bande 5 GHz, mais il l\'utilisera chaque fois que disponible."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-gl/strings.xml b/packages/SettingsProvider/res/values-gl/strings.xml
index 7b1599e..771fade 100644
--- a/packages/SettingsProvider/res/values-gl/strings.xml
+++ b/packages/SettingsProvider/res/values-gl/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Almacenamento da configuración"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios na configuración da zona wifi"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Modificouse a banda da zona wifi."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo non admite a opción de conectarse só a bandas de 5 GHz, pero usaraas se están dispoñibles."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-gu/strings.xml b/packages/SettingsProvider/res/values-gu/strings.xml
index 00246f9..a561924 100644
--- a/packages/SettingsProvider/res/values-gu/strings.xml
+++ b/packages/SettingsProvider/res/values-gu/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"સેટિંગ્સ સંગ્રહ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"તમારા હૉટસ્પૉટ સેટિંગને બદલે છે"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"તમારું હૉટસ્પૉટ બૅન્ડ બદલાઈ ગયું છે."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"આ ડિવાઇસ તમારી ફક્ત 5GHz માટેની પસંદગીને સપોર્ટ કરતું નથી. તેના બદલે, જ્યારે 5GHz બૅન્ડ ઉપલબ્ધ હશે ત્યારે આ ડિવાઇસ તેનો ઉપયોગ કરશે."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-hi/strings.xml b/packages/SettingsProvider/res/values-hi/strings.xml
index 35ed78f..199a546 100644
--- a/packages/SettingsProvider/res/values-hi/strings.xml
+++ b/packages/SettingsProvider/res/values-hi/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"सेटिंग मेमोरी"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"आपकी हॉटस्पॉट की सेटिंग में किए गए बदलाव"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"आपका हॉटस्पॉट का बैंड बदल दिया गया है."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"इस डिवाइस पर आप \'सिर्फ़ 5 गीगाहर्ट्ज़\' वाला विकल्प नहीं चुन सकते. इसके बजाय, 5 गीगाहर्ट्ज़ बैंड उपलब्ध होने पर यह डिवाइस उसका इस्तेमाल करेगा."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-hr/strings.xml b/packages/SettingsProvider/res/values-hr/strings.xml
index 070d061..9129a04 100644
--- a/packages/SettingsProvider/res/values-hr/strings.xml
+++ b/packages/SettingsProvider/res/values-hr/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Postavke pohrane"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promjene postavki vaše žarišne točke"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Promijenila se frekvencija vaše žarišne točke."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava vašu postavku za upotrebu samo 5 GHz. Upotrebljavat će frekvenciju od 5 GHz kada je dostupna."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-hu/strings.xml b/packages/SettingsProvider/res/values-hu/strings.xml
index 97124ae..a1ed494 100644
--- a/packages/SettingsProvider/res/values-hu/strings.xml
+++ b/packages/SettingsProvider/res/values-hu/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Beállítástároló"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"A hotspot beállításainak módosítása"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"A hotspot sávja megváltozott."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ez az eszköz nem támogatja a csak 5 GHz-es sávra vonatkozó beállítást. Az eszköz akkor használ 5 GHz-es sávot, ha a sáv rendelkezésre áll."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-hy/strings.xml b/packages/SettingsProvider/res/values-hy/strings.xml
index d494012..6d716f8 100644
--- a/packages/SettingsProvider/res/values-hy/strings.xml
+++ b/packages/SettingsProvider/res/values-hy/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Կարգավորումների պահուստ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Փոփոխություններ թեժ կետի կարգավորումներում"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ձեր թեժ կետի հաճախականությունը փոխվել է։"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Սարքը չի կարող աշխատել միայն 5 ԳՀց հաճախականությամբ։ Այդ հաճախականությունը կօգտագործվի հնարավորության դեպքում։"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-in/strings.xml b/packages/SettingsProvider/res/values-in/strings.xml
index 4ec8f84..8b1b1f4 100644
--- a/packages/SettingsProvider/res/values-in/strings.xml
+++ b/packages/SettingsProvider/res/values-in/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Setelan Penyimpanan"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Perubahan pada setelan hotspot Anda"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Band hotspot Anda telah berubah."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Perangkat ini tidak mendukung preferensi Anda, yaitu hanya 5GHz. Sebagai gantinya, perangkat ini akan menggunakan band 5GHz jika tersedia."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-is/strings.xml b/packages/SettingsProvider/res/values-is/strings.xml
index d75abae..150c084 100644
--- a/packages/SettingsProvider/res/values-is/strings.xml
+++ b/packages/SettingsProvider/res/values-is/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Stillingageymsla"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Breytingar á stillingum heits reits"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Tíðnisvið heita reitsins hefur breyst."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Þetta tæki styður ekki val þitt fyrir aðeins 5 GHz. Í staðinn mun þetta tæki nota 5 GHz tíðnisvið þegar það er í boði."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-it/strings.xml b/packages/SettingsProvider/res/values-it/strings.xml
index a1e3901..6715c3c 100644
--- a/packages/SettingsProvider/res/values-it/strings.xml
+++ b/packages/SettingsProvider/res/values-it/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Memoria impostazioni"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifiche alle tue impostazioni dell\'hotspot"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La banda dell\'hotspot è cambiata."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Questo dispositivo non supporta la tua preferenza esclusiva per 5 GHz. Utilizzerà la banda a 5 GHz solo quando è disponibile."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-iw/strings.xml b/packages/SettingsProvider/res/values-iw/strings.xml
index c14f776..dd44329 100644
--- a/packages/SettingsProvider/res/values-iw/strings.xml
+++ b/packages/SettingsProvider/res/values-iw/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"אחסון הגדרות"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"שינויים להגדרות של הנקודה לשיתוף אינטרנט"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"התדר של הנקודה לשיתוף אינטרנט השתנה."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"מכשיר זה לא תומך בהעדפות שלך ל-5GHz בלבד. במקום זאת, מכשיר זה ישתמש בתדר 5GHz כשיהיה זמין."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ja/strings.xml b/packages/SettingsProvider/res/values-ja/strings.xml
index b11ec3b..7bbcd46 100644
--- a/packages/SettingsProvider/res/values-ja/strings.xml
+++ b/packages/SettingsProvider/res/values-ja/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"ストレージの設定"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"アクセス ポイントの設定の変更"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"アクセス ポイントの帯域幅が変更されました。"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"このデバイスは 5 GHz のみという設定に対応していません。ただし、5 GHz 周波数帯が利用できるときには利用します。"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ka/strings.xml b/packages/SettingsProvider/res/values-ka/strings.xml
index d08e71e..86db4f3 100644
--- a/packages/SettingsProvider/res/values-ka/strings.xml
+++ b/packages/SettingsProvider/res/values-ka/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"პარამეტრების საცავი"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"თქვენი უსადენო ქსელის პარამეტრების ცვლილება"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"თქვენი უსადენო ქსელის დიაპაზონი შეიცვალა."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ამ მოწყობილობას არ შეუძლია მხოლოდ 5 გჰც სიხშირეზე მუშაობა. აღნიშნული სიხშირის გამოყენება მოხდება მაშინ, როცა ეს შესაძლებელია."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-kk/strings.xml b/packages/SettingsProvider/res/values-kk/strings.xml
index c07264c..a093d08 100644
--- a/packages/SettingsProvider/res/values-kk/strings.xml
+++ b/packages/SettingsProvider/res/values-kk/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Параметрлер жады"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Хотспот параметрлеріне өзгерістер енгізілді"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Хотспот жолағы өзгертілді."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Бұл құрылғы тек 5 ГГц жиілікте жұмыс істей алмайды. Бұл жиілік мүмкін болған жағдайда ғана қолданылады."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-km/strings.xml b/packages/SettingsProvider/res/values-km/strings.xml
index c880ead..f0a2712 100644
--- a/packages/SettingsProvider/res/values-km/strings.xml
+++ b/packages/SettingsProvider/res/values-km/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"កំណត់ការផ្ទុក"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"ប្ដូរទៅការកំណត់ហតស្ប៉តរបស់អ្នក"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"កម្រិតបញ្ជូនហតស្ប៉តរបស់អ្នកបានផ្លាស់ប្ដូរ។"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ឧបករណ៍នេះមិនស្គាល់ចំណូលចិត្តរបស់អ្នកសម្រាប់តែ 5GHz ប៉ុណ្ណោះ។ ផ្ទុយមកវិញ ឧបករណ៍នេះនឹងប្រើកម្រិតបញ្ជូន 5GHz នៅពេលដែលអាចប្រើបាន។"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-kn/strings.xml b/packages/SettingsProvider/res/values-kn/strings.xml
index d823323..f2c1d5e 100644
--- a/packages/SettingsProvider/res/values-kn/strings.xml
+++ b/packages/SettingsProvider/res/values-kn/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"ಸೆಟ್ಟಿಂಗ್ಗಳ ಸಂಗ್ರಹಣೆ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"ನಿಮ್ಮ ಹಾಟ್ಸ್ಪಾಟ್ ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿನ ಬದಲಾವಣೆಗಳು"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ನಿಮ್ಮ ಹಾಟ್ಸ್ಪಾಟ್ ಬ್ಯಾಂಡ್ ಬದಲಾಗಿದೆ."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ಈ ಸಾಧನವು 5GHz ಗೆ ಮಾತ್ರ ನಿಮ್ಮ ಆದ್ಯತೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ. ಬದಲಿಗೆ, ಈ ಸಾಧನವು 5GHz ಬ್ಯಾಂಡ್ ಅನ್ನು ಲಭ್ಯವಿರುವಾಗ ಬಳಸುತ್ತದೆ."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ko/strings.xml b/packages/SettingsProvider/res/values-ko/strings.xml
index ab8fb2b..841832d 100644
--- a/packages/SettingsProvider/res/values-ko/strings.xml
+++ b/packages/SettingsProvider/res/values-ko/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"설정 저장소"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"핫스팟 설정 변경"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"핫스팟 대역이 변경되었습니다."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"이 기기에서는 5GHz 전용 환경설정이 지원되지 않습니다. 대신 가능할 때만 기기에서 5GHz 대역이 사용됩니다."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ky/strings.xml b/packages/SettingsProvider/res/values-ky/strings.xml
index 566c481..014c66c 100644
--- a/packages/SettingsProvider/res/values-ky/strings.xml
+++ b/packages/SettingsProvider/res/values-ky/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Жөндөөлөрдү сактоо"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Байланыш түйүнүңүздүн жөндөөлөрү өзгөрүлдү"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Байланыш түйүнүңүздүн жыштыгы өзгөрдү."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Бул түзмөк 5ГГцти гана колдонуу жөндөөсүн колдоого албайт. Анын ордуна, бул түзмөк 5ГГц жыштыгын ал жеткиликтүү болгондо колдонот."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-lo/strings.xml b/packages/SettingsProvider/res/values-lo/strings.xml
index 1aa5fe9..9d60ba1 100644
--- a/packages/SettingsProvider/res/values-lo/strings.xml
+++ b/packages/SettingsProvider/res/values-lo/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"ບ່ອນເກັບຂໍ້ມູນການຕັ້ງຄ່າ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"ການປ່ຽນແປງການຕັ້ງຄ່າຮັອດສະປອດຂອງທ່ານ"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ຄື້ນຄວາມຖີ່ຮັອດສະປອດຂອງທ່ານປ່ຽນແປງແລ້ວ."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ອຸປະກອນນີ້ບໍ່ຮອງຮັບການຕັ້ງຄ່າຂອງທ່ານສຳລັບ 5GHz ເທົ່ານັ້ນ. ແຕ່ວ່າອຸປະກອນນີ້ຈະໃຊ້ຄື້ນຄວາມຖີ່ 5GHz ເມື່ອສາມາດໃຊ້ໄດ້."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-lt/strings.xml b/packages/SettingsProvider/res/values-lt/strings.xml
index 47cd6b4..775d3b9 100644
--- a/packages/SettingsProvider/res/values-lt/strings.xml
+++ b/packages/SettingsProvider/res/values-lt/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Nustatymų saugykla"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Viešosios interneto prieigos taško nustatymų pakeitimai"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Viešosios prieigos taško dažnio juosta pasikeitė."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Šiame įrenginyje nepalaikoma tik 5 GHz nuostata. Vietoj to šiame įrenginyje bus naudojama 5 GHz dažnio juosta, kai bus pasiekiama."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-lv/strings.xml b/packages/SettingsProvider/res/values-lv/strings.xml
index d221d46..f7f3117 100644
--- a/packages/SettingsProvider/res/values-lv/strings.xml
+++ b/packages/SettingsProvider/res/values-lv/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Iestatījumu krātuve"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Izmaiņas tīklāja iestatījumos"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ir mainīts tīklāja joslas platums."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Šajā ierīcē netiek atbalstīta jūsu preference par tikai 5 GHz joslu. 5 GHz josla ierīcē tiks izmantota, kad tā būs pieejama."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-mk/strings.xml b/packages/SettingsProvider/res/values-mk/strings.xml
index dc74c4a..a245e5f 100644
--- a/packages/SettingsProvider/res/values-mk/strings.xml
+++ b/packages/SettingsProvider/res/values-mk/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Поставки за меморија"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промени на поставките за точка на пристап"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Опсегот за точка на пристап е променет."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Уредов не ги поддржува вашите поставки за само 5 GHz. Наместо тоа, ќе го користи опсегот од 5 GHz кога е достапен."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ml/strings.xml b/packages/SettingsProvider/res/values-ml/strings.xml
index a11e236..b63f20e 100644
--- a/packages/SettingsProvider/res/values-ml/strings.xml
+++ b/packages/SettingsProvider/res/values-ml/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"സംഭരണ ക്രമീകരണം"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"നിങ്ങളുടെ ഹോട്ട്സ്പോട്ട് ക്രമീകരണത്തിൽ വരുത്തിയ മാറ്റങ്ങൾ"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"നിങ്ങളുടെ ഹോട്ട്സ്പോട്ട് ബാൻഡ് മാറിയിരിക്കുന്നു."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"നിങ്ങളുടെ മുൻഗണനയനുസരിച്ചുള്ള, 5GHz മാത്രം എന്നത് ഈ ഉപകരണം പിന്തുണയ്ക്കുന്നില്ല. പകരം, 5GHz ബാൻഡ് ലഭ്യമാകുമ്പോൾ അത് ഉപയോഗിക്കും."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-mn/strings.xml b/packages/SettingsProvider/res/values-mn/strings.xml
index f6437c75..c839177 100644
--- a/packages/SettingsProvider/res/values-mn/strings.xml
+++ b/packages/SettingsProvider/res/values-mn/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Тохиргооны Сан"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Таны сүлжээний цэгийн тохиргооны өөрчлөлт"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Таны сүлжээний цэгийн хязгаарыг өөрчилсөн."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Энэ төхөөрөмж нь зөвхөн 5Гц гэсэн таны сонголтыг дэмждэггүй. Оронд нь энэ төхөөрөмж 5Гц-н хязгаарыг боломжтой үед нь ашиглах болно."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-mr/strings.xml b/packages/SettingsProvider/res/values-mr/strings.xml
index 7888488..0c7041e 100644
--- a/packages/SettingsProvider/res/values-mr/strings.xml
+++ b/packages/SettingsProvider/res/values-mr/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"सेटिंग्ज संचयन"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"तुमच्या हॉटस्पॉट सेटिंग्जमधील बदल"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"तुमचा हॉटस्पॉट बँड बदलला गेला आहे."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"हे डिव्हाइस फक्त ५GHz च्या तुमच्या प्राधान्याला सपोर्ट करत नाही. त्याऐवजी, उपलब्ध असेल तेव्हा हे डिव्हाइस ५GHz बँड वापरेल."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ms/strings.xml b/packages/SettingsProvider/res/values-ms/strings.xml
index bcde71c..a1574df 100644
--- a/packages/SettingsProvider/res/values-ms/strings.xml
+++ b/packages/SettingsProvider/res/values-ms/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Storan Tetapan"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Perubahan kepada tetapan tempat liputan anda"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Jalur tempat liputan anda telah berubah."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Peranti ini tidak menyokong pilihan anda untuk 5GHz sahaja. Sebaliknya, peranti ini akan menggunakan jalur 5GHz apabila tersedia."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-my/strings.xml b/packages/SettingsProvider/res/values-my/strings.xml
index b2e670e..48d4dba 100644
--- a/packages/SettingsProvider/res/values-my/strings.xml
+++ b/packages/SettingsProvider/res/values-my/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"သိုလှောင်မှုဆက်တင်များ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"သင်၏ဟော့စပေါ့ ဆက်တင်များ ပြောင်းလဲမှု"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"သင်၏ ဟော့စပေါ့လိုင်း ပြောင်းသွားပါပြီ။"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ဤစက်ပစ္စည်းသည် သင်၏ 5GHz သီးသန့်ရွေးချယ်မှုအတွက် ပံ့ပိုးမထားပါ။ ၎င်းအစား ဤစက်ပစ္စည်းသည် ရနိုင်သည့်အခါ 5GHz လိုင်းကို သုံးသွားပါမည်။"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-nb/strings.xml b/packages/SettingsProvider/res/values-nb/strings.xml
index 5c60ad7..e0cbd7e 100644
--- a/packages/SettingsProvider/res/values-nb/strings.xml
+++ b/packages/SettingsProvider/res/values-nb/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Lagring av innstillinger"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Endres til innstillingene dine for Wi-Fi-soner"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Båndet ditt for Wi-Fi-sone er endret."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Denne enheten støtter ikke innstillingen din for bare 5 GHz. I stedet bruker enheten 5 GHz-båndet når det er tilgjengelig."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ne/strings.xml b/packages/SettingsProvider/res/values-ne/strings.xml
index 8c4f8c8..2fd9b00 100644
--- a/packages/SettingsProvider/res/values-ne/strings.xml
+++ b/packages/SettingsProvider/res/values-ne/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"सेटिङहरू भण्डारण"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"तपाईंका हटस्पट सेटिङहरूमा गरिएका परिवर्तनहरू"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"तपाईंको हटस्पट ब्यान्ड परिवर्तन भएको छ।"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"यो यन्त्रले तपाईंको 5GHz मात्रको प्राथमिकतालाई समर्थन गर्दैन। बरु, उपलब्ध भएको खण्डमा यो यन्त्रले 5GHz ब्यान्ड प्रयोग गर्ने छ।"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-nl/strings.xml b/packages/SettingsProvider/res/values-nl/strings.xml
index 7ad4588..0b843ae 100644
--- a/packages/SettingsProvider/res/values-nl/strings.xml
+++ b/packages/SettingsProvider/res/values-nl/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Opslagruimte voor instellingen"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Wijzigingen in je hotspot-instellingen"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Je hotspot-band is gewijzigd."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dit apparaat biedt geen ondersteuning voor je voorkeur voor alleen 5 GHz. In plaats daarvan gebruikt dit apparaat de 5-GHz-band wanneer deze beschikbaar is."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-or/strings.xml b/packages/SettingsProvider/res/values-or/strings.xml
index 1837219..53f6104 100644
--- a/packages/SettingsProvider/res/values-or/strings.xml
+++ b/packages/SettingsProvider/res/values-or/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"ସେଟିଙ୍ଗ ଷ୍ଟୋରେଜ୍"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"ଆପଣଙ୍କର ହଟ୍ସ୍ପଟ୍ ସେଟିଂସ୍କୁ ବଦଳାଇଥାଏ"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ଆପଣଙ୍କର ହଟ୍ସ୍ପଟ୍ ପରିବର୍ତ୍ତନ କରାଯାଇଛି।"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"କେବଳ 5GHz ପାଇଁ, ଏହି ଡିଭାଇସ୍ ଆପଣଙ୍କର ପସନ୍ଦକୁ ସମର୍ଥନ କରେ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ, ଉପଲବ୍ଧ ହେଲେ ଏହି ଡିଭାଇସ୍ 5GHz ବ୍ୟାଣ୍ଡ ବ୍ୟବହାର କରିବ।"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-pa/strings.xml b/packages/SettingsProvider/res/values-pa/strings.xml
index 2961435..9a41e36 100644
--- a/packages/SettingsProvider/res/values-pa/strings.xml
+++ b/packages/SettingsProvider/res/values-pa/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"ਸੈਟਿੰਗਾਂ ਸਟੋਰੇਜ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"ਤੁਹਾਡੀਆਂ ਹੌਟਸਪੌਟ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲਾਅ"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ਤੁਹਾਡਾ ਹੌਟਸਪੌਟ ਬੈਂਡ ਬਦਲ ਗਿਆ ਹੈ।"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ਇਹ ਡੀਵਾਈਸ ਸਿਰਫ਼ 5GHz ਦੀ ਤੁਹਾਡੀ ਤਰਜੀਹ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ ਹੈ। ਇਸਦੀ ਬਜਾਏ, ਇਹ ਡੀਵਾਈਸ ਉਪਲਬਧ ਹੋਣ \'ਤੇ 5GHz ਬੈਂਡ ਵਰਤੇਗਾ।"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-pl/strings.xml b/packages/SettingsProvider/res/values-pl/strings.xml
index 9963aee..7fd2b13 100644
--- a/packages/SettingsProvider/res/values-pl/strings.xml
+++ b/packages/SettingsProvider/res/values-pl/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Pamięć ustawień"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Zmieniono ustawienia hotspotu"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Zmieniono pasmo hotspotu."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"To urządzenie nie może korzystać tylko z częstotliwości 5 GHz. Będzie korzystać z tego pasma, jeśli będzie ono dostępne."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-pt-rBR/strings.xml b/packages/SettingsProvider/res/values-pt-rBR/strings.xml
index e25e82e..18db8f6 100644
--- a/packages/SettingsProvider/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsProvider/res/values-pt-rBR/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Armazenamento de configurações"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mudanças nas suas configurações de ponto de acesso"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Sua banda de ponto de acesso foi modificada."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não é compatível com sua preferência apenas por 5 GHz. Em vez disso, o dispositivo usará a banda de 5 GHz quando ela estiver disponível."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-pt-rPT/strings.xml b/packages/SettingsProvider/res/values-pt-rPT/strings.xml
index 3b95a31..be88cce 100644
--- a/packages/SettingsProvider/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsProvider/res/values-pt-rPT/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Armazenamento de definições"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Alterações às definições de zona Wi-Fi"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"A banda da sua zona Wi-Fi foi alterada."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não suporta a sua preferência apenas para 5 GHz. Em alternativa, este dispositivo vai utilizar a banda de 5 GHz quando estiver disponível."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-pt/strings.xml b/packages/SettingsProvider/res/values-pt/strings.xml
index e25e82e..18db8f6 100644
--- a/packages/SettingsProvider/res/values-pt/strings.xml
+++ b/packages/SettingsProvider/res/values-pt/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Armazenamento de configurações"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mudanças nas suas configurações de ponto de acesso"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Sua banda de ponto de acesso foi modificada."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não é compatível com sua preferência apenas por 5 GHz. Em vez disso, o dispositivo usará a banda de 5 GHz quando ela estiver disponível."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ro/strings.xml b/packages/SettingsProvider/res/values-ro/strings.xml
index 5a5ac6d..3a234e1 100644
--- a/packages/SettingsProvider/res/values-ro/strings.xml
+++ b/packages/SettingsProvider/res/values-ro/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Stocare setări"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modificări aduse setărilor pentru hotspot"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"S-a schimbat banda de frecvență a hotspotului."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dispozitivul nu acceptă doar preferința pentru 5 GHz. Dispozitivul va folosi banda de 5 GHz când este disponibilă."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ru/strings.xml b/packages/SettingsProvider/res/values-ru/strings.xml
index 15ca313..184afdd 100644
--- a/packages/SettingsProvider/res/values-ru/strings.xml
+++ b/packages/SettingsProvider/res/values-ru/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Хранилище настроек"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Изменения в настройках точки доступа"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Частота точки доступа изменена."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Устройство не может работать только на частоте 5 ГГц. Эта частота будет использоваться, когда это возможно."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-si/strings.xml b/packages/SettingsProvider/res/values-si/strings.xml
index feff43c..69e04f1 100644
--- a/packages/SettingsProvider/res/values-si/strings.xml
+++ b/packages/SettingsProvider/res/values-si/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"සැකසීම් ගබඩාව"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"ඔබගේ හොට්ස්පොට් සැකසීම්වලට වෙනස් කිරීම්"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ඔබගේ හොට්ස්පොට් කලාපය වෙනස් වී ඇත."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"මෙම උපාංගය 5GHz සඳහා ඔබේ මනාපවලට සහාය නොදක්වයි. ඒ වෙනුවට, මෙම උපාංගය ලබා ගත හැකි විට 5GHz කලාපය භාවිතා කරනු ඇත."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-sk/strings.xml b/packages/SettingsProvider/res/values-sk/strings.xml
index 1207a94..a53178d 100644
--- a/packages/SettingsProvider/res/values-sk/strings.xml
+++ b/packages/SettingsProvider/res/values-sk/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Ukladací priestor nastavení"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Zmeny nastavení hotspotu"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pásmo vášho hotspotu sa zmenilo."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Toto zariadenie nepodporuje vašu predvoľbu používať iba 5 GHz. Namiesto toho bude pásmo 5 GHz používať vtedy, keď bude k dispozícii."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-sl/strings.xml b/packages/SettingsProvider/res/values-sl/strings.xml
index 28a9937..ea697fe 100644
--- a/packages/SettingsProvider/res/values-sl/strings.xml
+++ b/packages/SettingsProvider/res/values-sl/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Shramba nastavitev"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Spremembe nastavitev dostopne točke"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pas dostopne točke je spremenjen."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ta naprava ne podpira prednostne nastavitve samo za 5-GHz pas. Namesto tega bo ta naprava uporabljala 5-GHz pas, ko bo na voljo."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-sq/strings.xml b/packages/SettingsProvider/res/values-sq/strings.xml
index 977fe02..a111576 100644
--- a/packages/SettingsProvider/res/values-sq/strings.xml
+++ b/packages/SettingsProvider/res/values-sq/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Hapësira ruajtëse e \"Cilësimeve\""</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ndryshimet në cilësimet e zonës së qasjes për internet"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Brezi yt i zonës së qasjes për internet ka ndryshuar."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Kjo pajisje nuk e mbështet preferencën për vetëm 5 GHz. Përkundrazi, pajisja do të përdorë brezin 5 GHz nëse ka."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-sr/strings.xml b/packages/SettingsProvider/res/values-sr/strings.xml
index 74b9dde..d473102 100644
--- a/packages/SettingsProvider/res/values-sr/strings.xml
+++ b/packages/SettingsProvider/res/values-sr/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Подешавања складишта"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промене подешавања за хотспот"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Опсег хотспота је промењен."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Овај уређај не подржава подешавање само за 5 GHz. Уређај ће користити опсег од 5 GHz када буде доступан."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-sv/strings.xml b/packages/SettingsProvider/res/values-sv/strings.xml
index 1444626..fea3e5e 100644
--- a/packages/SettingsProvider/res/values-sv/strings.xml
+++ b/packages/SettingsProvider/res/values-sv/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Lagring av inställningar"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ändringar i inställningarna för surfzon"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Frekvensbandet för surfzonen har ändrats."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Den här enheten har inte stöd för inställningen för att endast använda 5 GHz. I stället används 5 GHz när det är möjligt."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-sw/strings.xml b/packages/SettingsProvider/res/values-sw/strings.xml
index c244d88..4d05817 100644
--- a/packages/SettingsProvider/res/values-sw/strings.xml
+++ b/packages/SettingsProvider/res/values-sw/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Hifadhi ya Mipangilio"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mabadiliko kwenye mipangilio ya mtandaopepe"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Bendi ya mtandaopepe wako imebadilika."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Kifaa hiki hakitumii mapendeleo yako ya GHz 5 pekee. Badala yake, kifaa hiki kitatumia bendi ya GHz 5 itakapopatikana."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ta/strings.xml b/packages/SettingsProvider/res/values-ta/strings.xml
index b26c875..f518a78 100644
--- a/packages/SettingsProvider/res/values-ta/strings.xml
+++ b/packages/SettingsProvider/res/values-ta/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"அமைப்புகளின் சேமிப்பிடம்"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"உங்கள் ஹாட்ஸ்பாட் அமைப்புகளில் செய்யப்பட்டுள்ள மாற்றங்கள்"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"உங்கள் ஹாட்ஸ்பாட்டின் அலைவரிசை வரம்பு மாறிவிட்டது."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"இந்தச் சாதனத்தில், ’5GHz மட்டும்’ எனும் முன்னுரிமைத் தேர்வு ஆதரிக்கப்படவில்லை. எனினும் 5GHz அலைவரிசை வரம்பிற்குள் இருக்கும்போது சாதனம் அதைப் பயன்படுத்திக்கொள்ளும்."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-te/strings.xml b/packages/SettingsProvider/res/values-te/strings.xml
index 2a4971a..6c59223 100644
--- a/packages/SettingsProvider/res/values-te/strings.xml
+++ b/packages/SettingsProvider/res/values-te/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"సెట్టింగ్ల నిల్వ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"మీ హాట్స్పాట్ సెట్టింగ్లకు మార్పులు"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"మీ హాట్స్పాట్ బ్యాండ్ మార్చబడింది."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ఈ పరికరం 5GHz కోసం మాత్రమే మీ ప్రాధాన్యతకు మద్దతు ఇవ్వదు. బదులుగా, ఈ పరికరం అందుబాటులో ఉన్నప్పుడు 5GHz బ్యాండ్ను ఉపయోగిస్తుంది."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-th/strings.xml b/packages/SettingsProvider/res/values-th/strings.xml
index db47654..4bf148f 100644
--- a/packages/SettingsProvider/res/values-th/strings.xml
+++ b/packages/SettingsProvider/res/values-th/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"ที่เก็บข้อมูลการตั้งค่า"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"มีการเปลี่ยนแปลงการตั้งค่าฮอตสปอต"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ย่านความถี่ฮอตสปอตมีการเปลี่ยนแปลง"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"อุปกรณ์นี้ไม่รองรับค่ากำหนดของคุณเฉพาะสำหรับ 5 GHz เท่านั้น และจะใช้ย่านความถี่ 5 GHz แทน เมื่อใช้ได้"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-tl/strings.xml b/packages/SettingsProvider/res/values-tl/strings.xml
index 71c6266..2a36d58 100644
--- a/packages/SettingsProvider/res/values-tl/strings.xml
+++ b/packages/SettingsProvider/res/values-tl/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Storage ng Mga Setting"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mga pagbabago sa mga setting ng iyong hotspot"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Nagbago ang band ng iyong hotspot."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Hindi sinusuportahan ng device na ito ang kagustuhan mong gumamit lang ng 5GHz. Sa halip, gagamitin ng device na ito ang 5GHz na band kapag available."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-tr/strings.xml b/packages/SettingsProvider/res/values-tr/strings.xml
index 4737a1e..add1fdb 100644
--- a/packages/SettingsProvider/res/values-tr/strings.xml
+++ b/packages/SettingsProvider/res/values-tr/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Ayarlar Deposu"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot ayarlarınız değişti"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot bandınız değişti."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Bu cihaz yalnızca 5 GHz bandının kullanılmasına yönelik tercihinizi desteklemiyor. Bunun yerine, bu cihaz 5 GHz bandını mevcut olduğunda kullanacak."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-uk/strings.xml b/packages/SettingsProvider/res/values-uk/strings.xml
index 2c77c6a61..cd678bc 100644
--- a/packages/SettingsProvider/res/values-uk/strings.xml
+++ b/packages/SettingsProvider/res/values-uk/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Сховище налаштувань"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Зміни в налаштуваннях точки доступу"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Діапазон частот точки доступу змінено."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"На цьому пристрої не підтримується налаштування \"Лише 5 ГГц\". Натомість буде використано діапазон частот 5 ГГц (якщо доступно)."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-ur/strings.xml b/packages/SettingsProvider/res/values-ur/strings.xml
index 31694e0..2241ce9 100644
--- a/packages/SettingsProvider/res/values-ur/strings.xml
+++ b/packages/SettingsProvider/res/values-ur/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"ترتیبات کا اسٹوریج"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"اپنے ہاٹ اسپاٹ کی ترتیبات میں تبدیلیاں"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"آپ کا ہاٹ اسپات بینڈ تبدیل ہو گیا ہے۔"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"یہ آلہ صرف 5GHz کے لیے آپ کی ترجیح کو سپورٹ نہیں کرے گا۔ بلکہ 5GHz بینڈ کے دستیاب ہونے پر اس کا استعمال کرے گا۔"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-uz/strings.xml b/packages/SettingsProvider/res/values-uz/strings.xml
index 86a980e..a266bf0 100644
--- a/packages/SettingsProvider/res/values-uz/strings.xml
+++ b/packages/SettingsProvider/res/values-uz/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Sozlamalar xotirasi"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot sozlamalari o‘zgartirildi"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot chastotasi oʻzgartirildi."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Qurilma faqat 5 GGs chastotada ishlay olmaydi. Bu chastotadan imkoniyatga qarab foydalaniladi."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-vi/strings.xml b/packages/SettingsProvider/res/values-vi/strings.xml
index 6476927..74f93b2 100644
--- a/packages/SettingsProvider/res/values-vi/strings.xml
+++ b/packages/SettingsProvider/res/values-vi/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Lưu trữ bộ nhớ"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Những thay đổi trong mục cài đặt điểm phát sóng của bạn"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Bằng tần của điểm phát sóng đã thay đổi."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Thiết bị này không hỗ trợ tùy chọn chỉ sử dụng băng tần 5 GHz. Thay vào đó, thiết bị này sẽ sử dụng băng tần 5 GHz khi có thể."</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-zh-rCN/strings.xml b/packages/SettingsProvider/res/values-zh-rCN/strings.xml
index 1395912..95b15e0 100644
--- a/packages/SettingsProvider/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rCN/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"设置存储"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"您的热点设置已变更"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"您的热点频段已变更。"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"此设备不支持您的偏好设置(仅限 5GHz),而且会在 5GHz 频段可用时使用该频段。"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-zh-rHK/strings.xml b/packages/SettingsProvider/res/values-zh-rHK/strings.xml
index 2845264..41ebe27 100644
--- a/packages/SettingsProvider/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rHK/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"設定儲存空間"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"您的熱點設定變更"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"您的熱點頻段已變更。"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"此裝置不支援只限 5 GHz 的偏好設定,但會在 5 GHz 頻段可用時採用。"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-zh-rTW/strings.xml b/packages/SettingsProvider/res/values-zh-rTW/strings.xml
index 2845264..d0a30f5 100644
--- a/packages/SettingsProvider/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rTW/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"設定儲存空間"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"無線基地台設定變更"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"你的無線基地台頻帶已變更。"</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"這部裝置不支援你的偏好設定 (僅限 5GHz),而是會在 5GHz 可用時使用該頻帶。"</string>
</resources>
diff --git a/packages/SettingsProvider/res/values-zu/strings.xml b/packages/SettingsProvider/res/values-zu/strings.xml
index 8c1d9e0..0440b3b 100644
--- a/packages/SettingsProvider/res/values-zu/strings.xml
+++ b/packages/SettingsProvider/res/values-zu/strings.xml
@@ -20,10 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4567566098528588863">"Izilungiselelo zesitoreji"</string>
- <!-- no translation found for wifi_softap_config_change (5338670993556993667) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_summary (7600005249167787750) -->
- <skip />
- <!-- no translation found for wifi_softap_config_change_detailed (2504664754843959730) -->
- <skip />
+ <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ushintsho kuzilungiselelo zakho ze-hotspot"</string>
+ <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ibhendi yakho ye-hotspot ishintshile."</string>
+ <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Le divayisi ayisekeli okuncamelayo kwe-5GHz kuphela. Kunalokho, le divayisi izosebenzisa ibhendi ye-5GHz uma itholakala."</string>
</resources>
diff --git a/packages/SystemUI/res/drawable/tv_gradient_protection.xml b/packages/SettingsProvider/res/values/blocked_settings.xml
similarity index 65%
copy from packages/SystemUI/res/drawable/tv_gradient_protection.xml
copy to packages/SettingsProvider/res/values/blocked_settings.xml
index ee5cbc7..b54b74e 100644
--- a/packages/SystemUI/res/drawable/tv_gradient_protection.xml
+++ b/packages/SettingsProvider/res/values/blocked_settings.xml
@@ -15,8 +15,10 @@
~ limitations under the License.
-->
-<!-- gradient protection for cards -->
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/tv_card_gradient_protection"
- android:tileMode="repeat"
-/>
+<!-- These are arrays of settings which should not be restored to this device -->
+<resources>
+ <string-array name="restore_blocked_device_specific_settings" />
+ <string-array name="restore_blocked_global_settings" />
+ <string-array name="restore_blocked_secure_settings" />
+ <string-array name="restore_blocked_system_settings" />
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 21b3ba3..c9e1944b 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -224,7 +224,7 @@
<bool name="def_charging_sounds_enabled">true</bool>
<!-- Default for Settings.Secure.NOTIFICATION_BUBBLES -->
- <bool name="def_notification_bubbles">false</bool>
+ <bool name="def_notification_bubbles">true</bool>
<!-- Default for Settings.Secure.AWARE_ENABLED -->
<bool name="def_aware_enabled">false</bool>
diff --git a/packages/SettingsProvider/res/values/overlayable.xml b/packages/SettingsProvider/res/values/overlayable.xml
new file mode 100644
index 0000000..dc41a77
--- /dev/null
+++ b/packages/SettingsProvider/res/values/overlayable.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<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/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 1f68742..987e82e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -72,5 +72,6 @@
Settings.Global.NOTIFICATION_BUBBLES,
Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,
Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER,
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 3b929b9..22d843b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -142,6 +142,8 @@
Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST,
Settings.Secure.SKIP_DIRECTION,
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
Settings.Secure.NAVIGATION_MODE,
Settings.Secure.SKIP_GESTURE_COUNT,
Settings.Secure.SKIP_TOUCH_COUNT,
@@ -155,6 +157,7 @@
Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED,
Settings.Secure.AWARE_LOCK_ENABLED,
Settings.Secure.AWARE_TAP_PAUSE_GESTURE_COUNT,
- Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT
+ Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
+ Settings.Secure.PEOPLE_STRIP,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 3d278db..72923a3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -151,5 +151,6 @@
VALIDATORS.put(Global.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 090af98..4b10557 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -216,6 +216,10 @@
VALIDATORS.put(Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, JSON_OBJECT_VALIDATOR);
VALIDATORS.put(
Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
+ VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
+ VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+ new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
VALIDATORS.put(Secure.AWARE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SKIP_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.SKIP_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
@@ -234,5 +238,6 @@
VALIDATORS.put(Secure.AWARE_LOCK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DISPLAY_DENSITY_FORCED, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 2027345..375a650 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -951,8 +951,7 @@
(1 << AudioManager.STREAM_NOTIFICATION) |
(1 << AudioManager.STREAM_SYSTEM) |
(1 << AudioManager.STREAM_SYSTEM_ENFORCED);
- if (!mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable)) {
+ if (!getTelephonyManager().isVoiceCapable()) {
ringerModeAffectedStreams |= (1 << AudioManager.STREAM_MUSIC);
}
db.execSQL("DELETE FROM system WHERE name='"
@@ -2548,7 +2547,7 @@
StringBuilder val = new StringBuilder();
List<Integer> defaultNetworks = TelephonyProperties.default_network();
int phoneCount = 1;
- TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ TelephonyManager telephonyManager = getTelephonyManager();
if (telephonyManager != null) {
phoneCount = telephonyManager.getSupportedModemCount();
}
@@ -2663,4 +2662,8 @@
private String getDefaultDeviceName() {
return mContext.getResources().getString(R.string.def_device_name_simple, Build.MODEL);
}
+
+ private TelephonyManager getTelephonyManager() {
+ return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 443288c..fb558ab 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -28,7 +28,7 @@
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.Uri;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.ParcelFileDescriptor;
@@ -66,6 +66,7 @@
import java.io.OutputStream;
import java.time.DateTimeException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
@@ -250,7 +251,13 @@
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
- ParcelFileDescriptor newState) throws IOException {
+ ParcelFileDescriptor newState) {
+ throw new RuntimeException("SettingsBackupAgent has been migrated to use key exclusion");
+ }
+
+ @Override
+ public void onRestore(BackupDataInput data, long appVersionCode,
+ ParcelFileDescriptor newState, Set<String> dynamicBlockList) throws IOException {
if (DEBUG) {
Log.d(TAG, "onRestore(): appVersionCode: " + appVersionCode
@@ -266,7 +273,7 @@
}
// versionCode of com.android.providers.settings corresponds to SDK_INT
- mRestoredFromSdkInt = appVersionCode;
+ mRestoredFromSdkInt = (int) appVersionCode;
HashSet<String> movedToGlobal = new HashSet<String>();
Settings.System.getMovedToGlobalSettings(movedToGlobal);
@@ -292,16 +299,29 @@
switch (key) {
case KEY_SYSTEM :
restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal,
- movedToSecure);
+ movedToSecure, R.array.restore_blocked_system_settings,
+ dynamicBlockList);
mSettingsHelper.applyAudioSettings();
break;
case KEY_SECURE :
- restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal, null);
+ restoreSettings(
+ data,
+ Settings.Secure.CONTENT_URI,
+ movedToGlobal,
+ null,
+ R.array.restore_blocked_secure_settings,
+ dynamicBlockList);
break;
case KEY_GLOBAL :
- restoreSettings(data, Settings.Global.CONTENT_URI, null, movedToSecure);
+ restoreSettings(
+ data,
+ Settings.Global.CONTENT_URI,
+ null,
+ movedToSecure,
+ R.array.restore_blocked_global_settings,
+ dynamicBlockList);
break;
case KEY_WIFI_SUPPLICANT :
@@ -345,7 +365,10 @@
case KEY_DEVICE_SPECIFIC_CONFIG:
byte[] restoredDeviceSpecificConfig = new byte[size];
data.readEntityData(restoredDeviceSpecificConfig, 0, size);
- restoreDeviceSpecificConfig(restoredDeviceSpecificConfig);
+ restoreDeviceSpecificConfig(
+ restoredDeviceSpecificConfig,
+ R.array.restore_blocked_device_specific_settings,
+ dynamicBlockList);
break;
default :
@@ -394,14 +417,22 @@
byte[] buffer = new byte[nBytes];
in.readFully(buffer, 0, nBytes);
restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal,
- movedToSecure);
+ movedToSecure, R.array.restore_blocked_system_settings,
+ Collections.emptySet());
// secure settings
nBytes = in.readInt();
if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
if (nBytes > buffer.length) buffer = new byte[nBytes];
in.readFully(buffer, 0, nBytes);
- restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal, null);
+ restoreSettings(
+ buffer,
+ nBytes,
+ Settings.Secure.CONTENT_URI,
+ movedToGlobal,
+ null,
+ R.array.restore_blocked_secure_settings,
+ Collections.emptySet());
// Global only if sufficiently new
if (version >= FULL_BACKUP_ADDED_GLOBAL) {
@@ -411,7 +442,8 @@
in.readFully(buffer, 0, nBytes);
movedToGlobal.clear(); // no redirection; this *is* the global namespace
restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal,
- movedToSecure);
+ movedToSecure, R.array.restore_blocked_global_settings,
+ Collections.emptySet());
}
// locale
@@ -612,8 +644,13 @@
return baos.toByteArray();
}
- private void restoreSettings(BackupDataInput data, Uri contentUri,
- HashSet<String> movedToGlobal, Set<String> movedToSecure) {
+ private void restoreSettings(
+ BackupDataInput data,
+ Uri contentUri,
+ HashSet<String> movedToGlobal,
+ Set<String> movedToSecure,
+ int blockedSettingsArrayId,
+ Set<String> dynamicBlockList) {
byte[] settings = new byte[data.getDataSize()];
try {
data.readEntityData(settings, 0, settings.length);
@@ -621,16 +658,44 @@
Log.e(TAG, "Couldn't read entity data");
return;
}
- restoreSettings(settings, settings.length, contentUri, movedToGlobal, movedToSecure);
+ restoreSettings(
+ settings,
+ settings.length,
+ contentUri,
+ movedToGlobal,
+ movedToSecure,
+ blockedSettingsArrayId,
+ dynamicBlockList);
}
- private void restoreSettings(byte[] settings, int bytes, Uri contentUri,
- HashSet<String> movedToGlobal, Set<String> movedToSecure) {
- restoreSettings(settings, 0, bytes, contentUri, movedToGlobal, movedToSecure);
+ private void restoreSettings(
+ byte[] settings,
+ int bytes,
+ Uri contentUri,
+ HashSet<String> movedToGlobal,
+ Set<String> movedToSecure,
+ int blockedSettingsArrayId,
+ Set<String> dynamicBlockList) {
+ restoreSettings(
+ settings,
+ 0,
+ bytes,
+ contentUri,
+ movedToGlobal,
+ movedToSecure,
+ blockedSettingsArrayId,
+ dynamicBlockList);
}
- private void restoreSettings(byte[] settings, int pos, int bytes, Uri contentUri,
- HashSet<String> movedToGlobal, Set<String> movedToSecure) {
+ private void restoreSettings(
+ byte[] settings,
+ int pos,
+ int bytes,
+ Uri contentUri,
+ HashSet<String> movedToGlobal,
+ Set<String> movedToSecure,
+ int blockedSettingsArrayId,
+ Set<String> dynamicBlockList) {
if (DEBUG) {
Log.i(TAG, "restoreSettings: " + contentUri);
}
@@ -662,9 +727,20 @@
SettingsHelper settingsHelper = mSettingsHelper;
ContentResolver cr = getContentResolver();
- final int whiteListSize = whitelist.length;
- for (int i = 0; i < whiteListSize; i++) {
- String key = whitelist[i];
+ Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId);
+
+ for (String key : whitelist) {
+ boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key);
+ if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri, key)) {
+ Log.i(
+ TAG,
+ "Key "
+ + key
+ + " removed from restore by "
+ + (isBlockedBySystem ? "system" : "dynamic")
+ + " block list");
+ continue;
+ }
String value = null;
boolean hasValueToRestore = false;
@@ -722,6 +798,19 @@
}
}
+ private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) {
+ String contentKey = Uri.withAppendedPath(areaUri, key).toString();
+ return dynamicBlockList.contains(contentKey);
+ }
+
+ // There may be other sources of blocked settings, so I'm separating out this
+ // code to make it easy to modify in the future.
+ @VisibleForTesting
+ protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
+ String[] blockedSettings = getResources().getStringArray(blockedSettingsArrayId);
+ return new HashSet<>(Arrays.asList(blockedSettings));
+ }
+
private boolean isValidSettingValue(String key, String value,
Map<String, Validator> validators) {
if (key == null || validators == null) {
@@ -875,32 +964,23 @@
}
private byte[] getSoftAPConfiguration() {
- try {
- return mWifiManager.getWifiApConfiguration().getBytesForBackup();
- } catch (IOException ioe) {
- Log.e(TAG, "Failed to marshal SoftAPConfiguration" + ioe.getMessage());
- return new byte[0];
- }
+ return mWifiManager.retrieveSoftApBackupData();
}
private void restoreSoftApConfiguration(byte[] data) {
- try {
- WifiConfiguration config = WifiConfiguration
- .getWifiConfigFromBackup(new DataInputStream(new ByteArrayInputStream(data)));
- if (DEBUG) Log.d(TAG, "Successfully unMarshaled WifiConfiguration ");
- int originalApBand = config.apBand;
- mWifiManager.setWifiApConfiguration(config);
+ SoftApConfiguration config = mWifiManager.restoreSoftApBackupData(data);
+ if (config != null) {
+ int originalApBand = config.getBand();
+ if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration ");
// Depending on device hardware, we may need to notify the user of a setting change for
// the apBand preference
boolean dualMode = mWifiManager.isDualModeSupported();
- int storedApBand = mWifiManager.getWifiApConfiguration().apBand;
+ int storedApBand = mWifiManager.getSoftApConfiguration().getBand();
if (dualMode && storedApBand != originalApBand) {
Log.d(TAG, "restored ap configuration requires a conversion, notify the user");
WifiSoftApBandChangedNotifier.notifyUserOfApBandConversion(this);
}
- } catch (IOException | BackupUtils.BadVersionException e) {
- Log.e(TAG, "Failed to unMarshal SoftAPConfiguration " + e.getMessage());
}
}
@@ -1007,10 +1087,13 @@
* Restore the device specific settings.
*
* @param data The byte array holding a backed up version of another devices settings.
+ * @param blockedSettingsArrayId The string array resource holding the settings not to restore.
+ * @param dynamicBlocklist The dynamic list of settings not to restore fed into this agent.
* @return true if the restore succeeded, false if it was stopped.
*/
@VisibleForTesting
- boolean restoreDeviceSpecificConfig(byte[] data) {
+ boolean restoreDeviceSpecificConfig(byte[] data, int blockedSettingsArrayId,
+ Set<String> dynamicBlocklist) {
// We're using an AtomicInteger to wrap the position int and allow called methods to
// modify it.
AtomicInteger pos = new AtomicInteger(0);
@@ -1022,7 +1105,14 @@
int dataStart = pos.get();
restoreSettings(
- data, dataStart, data.length, Settings.Secure.CONTENT_URI, null, null);
+ data,
+ dataStart,
+ data.length,
+ Settings.Secure.CONTENT_URI,
+ null,
+ null,
+ blockedSettingsArrayId,
+ dynamicBlocklist);
updateWindowManagerIfNeeded(originalDensity);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 0a2dd38..016896f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -691,6 +691,9 @@
dumpRepeatedSetting(s, p,
Settings.Global.ERROR_LOGCAT_PREFIX,
GlobalSettingsProto.ERROR_LOGCAT_LINES);
+ dumpRepeatedSetting(s, p,
+ Settings.Global.MAX_ERROR_BYTES_PREFIX,
+ GlobalSettingsProto.MAX_ERROR_BYTES);
final long euiccToken = p.start(GlobalSettingsProto.EUICC);
dumpSetting(s, p,
@@ -2188,6 +2191,14 @@
Settings.Secure.NAVIGATION_MODE,
SecureSettingsProto.NAVIGATION_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+ SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_LEFT);
+
+ dumpSetting(s, p,
+ Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+ SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_RIGHT);
+
final long nfcPaymentToken = p.start(SecureSettingsProto.NFC_PAYMENT);
dumpSetting(s, p,
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 93a1407..4309c80 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3264,7 +3264,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 184;
+ private static final int SETTINGS_VERSION = 185;
private final int mUserId;
@@ -4446,20 +4446,15 @@
}
if (currentVersion == 182) {
- // Remove secure bubble settings.
+ // Remove secure bubble settings; it's in global now.
getSecureSettingsLocked(userId).deleteSettingLocked("notification_bubbles");
- // Add global bubble settings.
- getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
- getContext().getResources().getBoolean(
- R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
- true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
-
+ // Removed. Updated NOTIFICATION_BUBBLES to be true by default, see 184.
currentVersion = 183;
}
if (currentVersion == 183) {
- // Version 184: Set default values for WIRELESS_CHARGING_STARTED_SOUND
+ // Version 183: Set default values for WIRELESS_CHARGING_STARTED_SOUND
// and CHARGING_STARTED_SOUND
final SettingsState globalSettings = getGlobalSettingsLocked();
@@ -4500,6 +4495,18 @@
currentVersion = 184;
}
+ if (currentVersion == 184) {
+ // 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(
+ R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
+ true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+ currentVersion = 185;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 4731e68..086b20f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -62,6 +62,8 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -766,6 +768,23 @@
}
} catch (Throwable t) {
Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t);
+ if (t instanceof IOException) {
+ // we failed to create a directory, so log the permissions and existence
+ // state for the settings file and directory
+ logSettingsDirectoryInformation(destination.getBaseFile());
+ if (t.getMessage().contains("Couldn't create directory")) {
+ // attempt to create the directory with Files.createDirectories, which
+ // throws more informative errors than File.mkdirs.
+ Path parentPath = destination.getBaseFile().getParentFile().toPath();
+ try {
+ Files.createDirectories(parentPath);
+ Slog.i(LOG_TAG, "Successfully created " + parentPath);
+ } catch (Throwable t2) {
+ Slog.e(LOG_TAG, "Failed to write " + parentPath
+ + " with Files.writeDirectories", t2);
+ }
+ }
+ }
destination.failWrite(out);
} finally {
IoUtils.closeQuietly(out);
@@ -779,6 +798,33 @@
}
}
+ private static void logSettingsDirectoryInformation(File settingsFile) {
+ File parent = settingsFile.getParentFile();
+ Slog.i(LOG_TAG, "directory info for directory/file " + settingsFile
+ + " with stacktrace ", new Exception());
+ File ancestorDir = parent;
+ while (ancestorDir != null) {
+ if (!ancestorDir.exists()) {
+ Slog.i(LOG_TAG, "ancestor directory " + ancestorDir
+ + " does not exist");
+ ancestorDir = ancestorDir.getParentFile();
+ } else {
+ Slog.i(LOG_TAG, "ancestor directory " + ancestorDir
+ + " exists");
+ Slog.i(LOG_TAG, "ancestor directory " + ancestorDir
+ + " permissions: r: " + ancestorDir.canRead() + " w: "
+ + ancestorDir.canWrite() + " x: " + ancestorDir.canExecute());
+ File ancestorParent = ancestorDir.getParentFile();
+ if (ancestorParent != null) {
+ Slog.i(LOG_TAG, "ancestor's parent directory " + ancestorParent
+ + " permissions: r: " + ancestorParent.canRead() + " w: "
+ + ancestorParent.canWrite() + " x: " + ancestorParent.canExecute());
+ }
+ break;
+ }
+ }
+ }
+
static void writeSingleSetting(int version, XmlSerializer serializer, String id,
String name, String value, String defaultValue, String packageName,
String tag, boolean defaultSysSet) throws IOException {
@@ -853,6 +899,7 @@
in = new AtomicFile(mStatePersistFile).openRead();
} catch (FileNotFoundException fnfe) {
Slog.i(LOG_TAG, "No settings state " + mStatePersistFile);
+ logSettingsDirectoryInformation(mStatePersistFile);
addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null);
return;
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c23a494..443811f 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -87,6 +87,7 @@
Settings.System.VOLUME_ACCESSIBILITY, // used internally, changing value will
// not change volume
Settings.System.VOLUME_ALARM, // deprecated since API 2?
+ Settings.System.VOLUME_ASSISTANT, // candidate for backup?
Settings.System.VOLUME_BLUETOOTH_SCO, // deprecated since API 2?
Settings.System.VOLUME_MASTER, // candidate for backup?
Settings.System.VOLUME_MUSIC, // deprecated since API 2?
@@ -215,7 +216,6 @@
Settings.Global.DEFAULT_DNS_SERVER,
Settings.Global.DEFAULT_INSTALL_LOCATION,
Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
- Settings.Global.DEFAULT_USER_ID_TO_BOOT_INTO,
Settings.Global.DESK_DOCK_SOUND,
Settings.Global.DESK_UNDOCK_SOUND,
Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
@@ -223,7 +223,6 @@
Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
Settings.Global.DEVELOPMENT_FORCE_RTL,
Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM,
- Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
Settings.Global.DEVICE_DEMO_MODE,
Settings.Global.DEVICE_IDLE_CONSTANTS,
Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS,
@@ -324,6 +323,7 @@
Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS,
Settings.Global.LTE_SERVICE_FORCED,
Settings.Global.LID_BEHAVIOR,
+ Settings.Global.MAX_ERROR_BYTES_PREFIX,
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
Settings.Global.MDC_INITIAL_MAX_RETRY,
@@ -734,7 +734,8 @@
Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
Settings.Secure.FACE_UNLOCK_RE_ENROLL,
- Settings.Secure.TAP_GESTURE);
+ Settings.Secure.TAP_GESTURE,
+ Settings.Secure.WINDOW_MAGNIFICATION);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 57e22db..e650882 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -37,14 +37,14 @@
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.annotations.VisibleForTesting;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -85,12 +85,33 @@
assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
- mAgentUnderTest.restoreDeviceSpecificConfig(settingsBackup);
+ mAgentUnderTest.restoreDeviceSpecificConfig(
+ settingsBackup,
+ R.array.restore_blocked_device_specific_settings,
+ Collections.emptySet());
assertEquals("Not all values were restored.", TEST_VALUES, helper.mWrittenValues);
}
@Test
+ public void testRoundTripDeviceSpecificSettingsWithBlock() throws IOException {
+ TestSettingsHelper helper = new TestSettingsHelper(mContext);
+ mAgentUnderTest.mSettingsHelper = helper;
+
+ byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration();
+
+ assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
+ mAgentUnderTest.setBlockedSettings(TEST_VALUES.keySet().toArray(new String[0]));
+
+ mAgentUnderTest.restoreDeviceSpecificConfig(
+ settingsBackup,
+ R.array.restore_blocked_device_specific_settings,
+ Collections.emptySet());
+
+ assertTrue("Not all values were blocked.", helper.mWrittenValues.isEmpty());
+ }
+
+ @Test
public void testGeneratedHeaderMatchesCurrentDevice() throws IOException {
mAgentUnderTest.mSettingsHelper = new TestSettingsHelper(mContext);
@@ -148,7 +169,10 @@
assertFalse(
"Blocking isSourceAcceptable did not stop restore",
- mAgentUnderTest.restoreDeviceSpecificConfig(data));
+ mAgentUnderTest.restoreDeviceSpecificConfig(
+ data,
+ R.array.restore_blocked_device_specific_settings,
+ Collections.emptySet()));
}
private byte[] generateUncorruptedHeader() throws IOException {
@@ -184,14 +208,34 @@
}
}
+ private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException {
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ os.write(SettingsBackupAgent.toByteArray(key));
+ os.write(SettingsBackupAgent.toByteArray(value));
+ return os.toByteArray();
+ }
+ }
+
private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent {
private Boolean mForcedDeviceInfoRestoreAcceptability = null;
+ private String[] mBlockedSettings = null;
void setForcedDeviceInfoRestoreAcceptability(boolean value) {
mForcedDeviceInfoRestoreAcceptability = value;
}
- @VisibleForTesting
+ void setBlockedSettings(String... blockedSettings) {
+ mBlockedSettings = blockedSettings;
+ }
+
+ @Override
+ protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
+ return mBlockedSettings == null
+ ? super.getBlockedSettings(blockedSettingsArrayId)
+ : new HashSet<>(Arrays.asList(mBlockedSettings));
+ }
+
+ @Override
boolean isSourceAcceptable(byte[] data, AtomicInteger pos) {
return mForcedDeviceInfoRestoreAcceptability == null
? super.isSourceAcceptable(data, pos)
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b89f141..aefdce4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -224,6 +224,12 @@
<!-- Permission requried for CTS test - CellBroadcastIntentsTest -->
<uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"/>
+ <!-- Permission required for CTS test - TetheringManagerTest -->
+ <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/>
+
+ <!-- Permission required for CTS test - CtsOsTestCases -->
+ <uses-permission android:name="android.permission.MANAGE_CRATES"/>
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
index 2d37b4c..15fd1f7 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -101,7 +101,7 @@
}
private Uri scanFile(@NonNull final File file) {
- return MediaStore.scanFile(this, file);
+ return MediaStore.scanFile(getContentResolver(), file);
}
private void set(@NonNull final String name, @NonNull final Uri uri) {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2a5bdc7..c238d7d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -42,6 +42,7 @@
"res",
],
static_libs: [
+ "WindowManager-Shell",
"SystemUIPluginLib",
"SystemUISharedLib",
"SettingsLib",
@@ -80,7 +81,7 @@
filegroup {
name: "SystemUI-tests-utils",
srcs: [
- "tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java",
+ "tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java",
"tests/src/com/android/systemui/statusbar/RankingBuilder.java",
"tests/src/com/android/systemui/statusbar/SbnBuilder.java",
],
@@ -127,7 +128,7 @@
"SystemUI-proto",
"metrics-helper-lib",
"androidx.test.rules", "hamcrest-library",
- "mockito-target-inline-minus-junit4",
+ "mockito-target-extended-minus-junit4",
"testables",
"truth-prebuilt",
"dagger2-2.19",
@@ -154,7 +155,7 @@
resource_dirs: [],
platform_apis: true,
- product_specific: true,
+ system_ext_specific: true,
certificate: "platform",
privileged: true,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6f68038..2a1e74e 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -28,11 +28,6 @@
android:glEsVersion="0x00020000"
android:required="true" />
- <!-- SysUI must be the one to define this permission; its name is
- referenced by the core OS. -->
- <permission android:name="android.permission.systemui.IDENTITY"
- android:protectionLevel="signature" />
-
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Used to read wallpaper -->
@@ -179,6 +174,9 @@
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
+ <!-- Adding Controls to SystemUI -->
+ <uses-permission android:name="android.permission.BIND_CONTROLS" />
+
<!-- Quick Settings tile: Night Mode / Dark Theme -->
<uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
@@ -325,6 +323,14 @@
android:permission="android.permission.BIND_WALLPAPER"
android:exported="true" />
+ <activity
+ android:name=".bubbles.BubbleOverflowActivity"
+ android:theme="@style/BubbleOverflow"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="always"
+ android:resizeableActivity="true">
+ </activity>
+
<activity android:name=".tuner.TunerActivity"
android:enabled="false"
android:icon="@drawable/tuner"
diff --git a/packages/Tethering/CleanSpec.mk b/packages/SystemUI/CleanSpec.mk
similarity index 93%
rename from packages/Tethering/CleanSpec.mk
rename to packages/SystemUI/CleanSpec.mk
index 70db351..2a2e4e4 100644
--- a/packages/Tethering/CleanSpec.mk
+++ b/packages/SystemUI/CleanSpec.mk
@@ -43,10 +43,8 @@
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
-
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Tethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
-
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/SystemUI)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/SystemUI)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/packages/SystemUI/plugin_core/Android.bp b/packages/SystemUI/plugin_core/Android.bp
index 42d6762..581fef7 100644
--- a/packages/SystemUI/plugin_core/Android.bp
+++ b/packages/SystemUI/plugin_core/Android.bp
@@ -16,4 +16,8 @@
sdk_version: "current",
name: "PluginCoreLib",
srcs: ["src/**/*.java"],
+
+ // 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/packages/SystemUI/res/drawable-xhdpi/tv_card_gradient_protection.png b/packages/SystemUI/res/drawable-xhdpi/tv_card_gradient_protection.png
deleted file mode 100644
index 135dabb..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/tv_card_gradient_protection.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml b/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml
new file mode 100644
index 0000000..80ce8c1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml
@@ -0,0 +1,22 @@
+<?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 android:height="96dp" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="96dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#757575" android:pathData="M15.54,5.54L13.77,7.3 12,5.54 10.23,7.3 8.46,5.54 12,2zM18.46,15.54l-1.76,-1.77L18.46,12l-1.76,-1.77 1.76,-1.77L22,12zM8.46,18.46l1.77,-1.76L12,18.46l1.77,-1.76 1.77,1.76L12,22zM5.54,8.46l1.76,1.77L5.54,12l1.76,1.77 -1.76,1.77L2,12z"/>
+ <path android:fillColor="#757575" android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/tv_bg_item_app_info.xml b/packages/SystemUI/res/drawable/tv_bg_item_app_info.xml
deleted file mode 100644
index 1bbb8c3..0000000
--- a/packages/SystemUI/res/drawable/tv_bg_item_app_info.xml
+++ /dev/null
@@ -1,22 +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.
- -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:radius="24dp"/>
- <solid android:color="@color/tv_audio_recording_bar_chip_background"/>
-</shape>
diff --git a/packages/SystemUI/res/drawable/circle_red.xml b/packages/SystemUI/res/drawable/tv_circle_dark.xml
similarity index 87%
copy from packages/SystemUI/res/drawable/circle_red.xml
copy to packages/SystemUI/res/drawable/tv_circle_dark.xml
index fd3c125..d1ba8e7 100644
--- a/packages/SystemUI/res/drawable/circle_red.xml
+++ b/packages/SystemUI/res/drawable/tv_circle_dark.xml
@@ -16,6 +16,9 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid android:color="@color/red"/>
+ android:shape="oval">
+
+ <solid
+ android:color="@color/tv_audio_recording_indicator_background" />
+
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/circle_red.xml b/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
similarity index 87%
rename from packages/SystemUI/res/drawable/circle_red.xml
rename to packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
index fd3c125..55d21de 100644
--- a/packages/SystemUI/res/drawable/circle_red.xml
+++ b/packages/SystemUI/res/drawable/tv_circle_white_translucent.xml
@@ -16,6 +16,9 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid android:color="@color/red"/>
+ android:shape="oval">
+
+ <solid
+ android:color="@color/tv_audio_recording_indicator_pulse" />
+
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_ic_mic_white.xml b/packages/SystemUI/res/drawable/tv_ic_mic_white.xml
index 1bea8a1..d887113 100644
--- a/packages/SystemUI/res/drawable/tv_ic_mic_white.xml
+++ b/packages/SystemUI/res/drawable/tv_ic_mic_white.xml
@@ -1,25 +1,27 @@
-<?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
- -->
+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 25.6666667C25.0433333 25.6666667 27.4816667 23.21 27.4816667 20.1666667L27.5 9.16666667C27.5 6.12333333 25.0433333 3.66666667 22 3.66666667C18.9566667 3.66666667 16.5 6.12333333 16.5 9.16666667L16.5 20.1666667C16.5 23.21 18.9566667 25.6666667 22 25.6666667ZM31.7166667 20.1666667C31.7166667 25.6666667 27.06 29.5166667 22 29.5166667C16.94 29.5166667 12.2833333 25.6666667 12.2833333 20.1666667L9.16666667 20.1666667C9.16666667 26.4183333 14.1533333 31.5883333 20.1666667 32.4866667L20.1666667 38.5L23.8333333 38.5L23.8333333 32.4866667C29.8466667 31.6066667 34.8333333 26.4366667 34.8333333 20.1666667L31.7166667 20.1666667Z"
- android:fillColor="@android:color/white" />
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12,14c1.66,0 3,-1.34 3,-3V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6C9,12.66 10.34,14 12,14zM11,5c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v6c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V5z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M17,11c0,2.76 -2.24,5 -5,5s-5,-2.24 -5,-5H5c0,3.53 2.61,6.43 6,6.92V21h2v-3.08c3.39,-0.49 6,-3.39 6,-6.92H17z"/>
</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/circle_red.xml b/packages/SystemUI/res/drawable/tv_rect_dark_left_rounded.xml
similarity index 79%
copy from packages/SystemUI/res/drawable/circle_red.xml
copy to packages/SystemUI/res/drawable/tv_rect_dark_left_rounded.xml
index fd3c125..9b48a70 100644
--- a/packages/SystemUI/res/drawable/circle_red.xml
+++ b/packages/SystemUI/res/drawable/tv_rect_dark_left_rounded.xml
@@ -16,6 +16,11 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid android:color="@color/red"/>
+ android:shape="rectangle">
+
+ <corners
+ android:bottomLeftRadius="8dp"
+ android:topLeftRadius="8dp" />
+ <solid android:color="@color/tv_audio_recording_indicator_background" />
+
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/circle_red.xml b/packages/SystemUI/res/drawable/tv_rect_dark_right_rounded.xml
similarity index 79%
copy from packages/SystemUI/res/drawable/circle_red.xml
copy to packages/SystemUI/res/drawable/tv_rect_dark_right_rounded.xml
index fd3c125..0334875 100644
--- a/packages/SystemUI/res/drawable/circle_red.xml
+++ b/packages/SystemUI/res/drawable/tv_rect_dark_right_rounded.xml
@@ -16,6 +16,11 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid android:color="@color/red"/>
+ android:shape="rectangle">
+
+ <corners
+ android:bottomRightRadius="8dp"
+ android:topRightRadius="8dp" />
+ <solid android:color="@color/tv_audio_recording_indicator_background" />
+
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/circle_red.xml b/packages/SystemUI/res/drawable/tv_ring_white.xml
similarity index 86%
copy from packages/SystemUI/res/drawable/circle_red.xml
copy to packages/SystemUI/res/drawable/tv_ring_white.xml
index fd3c125..0f7cc10 100644
--- a/packages/SystemUI/res/drawable/circle_red.xml
+++ b/packages/SystemUI/res/drawable/tv_ring_white.xml
@@ -16,6 +16,10 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="oval">
- <solid android:color="@color/red"/>
+ android:shape="oval">
+
+ <stroke
+ android:width="1dp"
+ android:color="@android:color/white" />
+
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_gradient_protection.xml b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
similarity index 66%
rename from packages/SystemUI/res/drawable/tv_gradient_protection.xml
rename to packages/SystemUI/res/layout/bubble_overflow_activity.xml
index ee5cbc7..4cee746 100644
--- a/packages/SystemUI/res/drawable/tv_gradient_protection.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_activity.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~
@@ -12,11 +11,10 @@
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT 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
-->
-
-<!-- gradient protection for cards -->
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/tv_card_gradient_protection"
- android:tileMode="repeat"
-/>
+<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/bubble_overflow_recycler"
+ android:scrollbars="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/layout/magnifier_controllers.xml b/packages/SystemUI/res/layout/magnifier_controllers.xml
new file mode 100644
index 0000000..0203cd4
--- /dev/null
+++ b/packages/SystemUI/res/layout/magnifier_controllers.xml
@@ -0,0 +1,65 @@
+<?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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/magnification_controls_size"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_height="@dimen/magnification_controls_size"
+ android:gravity="center">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:focusable="true"
+ android:id="@+id/controller"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_control_magnification_grey" />
+
+ <View
+ android:id="@+id/left_control"
+ android:layout_width="@dimen/magnifier_left_right_controls_width"
+ android:layout_height="@dimen/magnifier_left_right_controls_height"
+ android:layout_alignLeft="@+id/controller"
+ android:layout_centerVertical="true" />
+
+ <View
+ android:id="@+id/up_control"
+ android:layout_width="@dimen/magnifier_up_down_controls_width"
+ android:layout_height="@dimen/magnifier_up_down_controls_height"
+ android:layout_alignTop="@+id/controller"
+ android:layout_centerHorizontal="true" />
+
+ <View
+ android:id="@+id/right_control"
+ android:layout_width="@dimen/magnifier_left_right_controls_width"
+ android:layout_height="@dimen/magnifier_left_right_controls_height"
+ android:layout_alignRight="@+id/controller"
+ android:layout_centerVertical="true" />
+
+ <View
+ android:id="@+id/down_control"
+ android:layout_width="@dimen/magnifier_up_down_controls_width"
+ android:layout_height="@dimen/magnifier_up_down_controls_height"
+ android:layout_alignBottom="@+id/controller"
+ android:layout_centerHorizontal="true" />
+ </RelativeLayout>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
new file mode 100644
index 0000000..f04226e
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
@@ -0,0 +1,121 @@
+<?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="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="32dp">
+
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:id="@+id/icon_texts_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <FrameLayout
+ android:layout_width="90dp"
+ android:layout_height="94dp">
+
+ <View
+ android:id="@+id/icon_container_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/tv_rect_dark_left_rounded"/>
+
+ <FrameLayout
+ android:id="@+id/icon_mic"
+ android:layout_width="70dp"
+ android:layout_height="70dp"
+ android:layout_marginLeft="12dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginRight="8dp"
+ android:layout_marginBottom="12dp">
+
+ <View
+ android:layout_width="54dp"
+ android:layout_height="54dp"
+ android:layout_gravity="center"
+ android:background="@drawable/tv_circle_dark"/>
+
+ <ImageView
+ android:id="@+id/pulsating_circle"
+ android:layout_width="54dp"
+ android:layout_height="54dp"
+ android:layout_gravity="center"
+ android:background="@drawable/tv_circle_white_translucent"/>
+
+ <ImageView
+ android:layout_width="54dp"
+ android:layout_height="54dp"
+ android:layout_gravity="center"
+ android:src="@drawable/tv_ring_white"/>
+
+ <ImageView
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="center"
+ android:background="@drawable/tv_ic_mic_white"/>
+ </FrameLayout>
+
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/texts_container"
+ android:layout_width="wrap_content"
+ android:layout_height="94dp"
+ android:background="@color/tv_audio_recording_indicator_background"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:visibility="visible">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/mic_active"
+ android:textColor="@android:color/white"
+ android:fontFamily="sans-serif"
+ android:textSize="20dp"/>
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:text="SomeApplication accessed your microphone"
+ android:textColor="@android:color/white"
+ android:fontFamily="sans-serif"
+ android:textSize="16dp"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </FrameLayout>
+
+ <View
+ android:id="@+id/bg_right"
+ android:layout_width="24dp"
+ android:layout_height="94dp"
+ android:background="@drawable/tv_rect_dark_right_rounded"
+ android:visibility="visible"/>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_item_app_info.xml b/packages/SystemUI/res/layout/tv_item_app_info.xml
deleted file mode 100644
index b40589e..0000000
--- a/packages/SystemUI/res/layout/tv_item_app_info.xml
+++ /dev/null
@@ -1,41 +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.
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:layout_marginLeft="8dp"
- android:paddingHorizontal="12dp"
- android:gravity="center_vertical"
- android:background="@drawable/tv_bg_item_app_info">
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginRight="8dp"/>
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="@color/tv_audio_recording_bar_text"
- android:fontFamily="sans-serif"
- android:textSize="14sp"/>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_status_bar_audio_recording.xml b/packages/SystemUI/res/layout/tv_status_bar_audio_recording.xml
deleted file mode 100644
index b9dffbb..0000000
--- a/packages/SystemUI/res/layout/tv_status_bar_audio_recording.xml
+++ /dev/null
@@ -1,63 +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.
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:orientation="vertical">
-
- <!-- Gradient Protector -->
- <View
- android:layout_width="match_parent"
- android:layout_height="102.5dp"
- android:background="@drawable/tv_gradient_protection"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="72dp"
- android:background="@color/tv_audio_recording_bar_background"
- android:gravity="center_vertical"
- android:orientation="horizontal">
-
- <ImageView
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginLeft="42dp"
- android:layout_marginVertical="12dp"
- android:padding="8dp"
- android:background="@drawable/circle_red"
- android:scaleType="centerInside"
- android:src="@drawable/tv_ic_mic_white"/>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="24dp"
- android:text="Audio recording by"
- android:textColor="@color/tv_audio_recording_bar_text"
- android:fontFamily="sans-serif"
- android:textSize="14sp"/>
-
- <LinearLayout
- android:id="@+id/container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
new file mode 100644
index 0000000..f818612
--- /dev/null
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -0,0 +1,71 @@
+<?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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <SurfaceView
+ android:layout_marginStart="@dimen/magnification_border_size"
+ android:layout_marginTop="@dimen/magnification_border_size"
+ android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <View
+ android:id="@+id/left_handle"
+ android:layout_width="@dimen/magnification_border_size"
+ android:layout_height="match_parent"
+ android:layout_above="@+id/drag_handle"
+ android:background="@color/magnification_border_color" />
+
+ <View
+ android:id="@+id/top_handle"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/magnification_border_size"
+ android:background="@color/magnification_border_color" />
+
+ <View
+ android:id="@+id/right_handle"
+ android:layout_width="@dimen/magnification_border_size"
+ android:layout_height="match_parent"
+ android:layout_above="@+id/drag_handle"
+ android:layout_alignParentEnd="true"
+ android:background="@color/magnification_border_color" />
+
+ <View
+ android:id="@+id/bottom_handle"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/magnification_border_size"
+ android:layout_above="@+id/drag_handle"
+ android:background="@color/magnification_border_color" />
+
+ <View
+ android:id="@+id/drag_handle"
+ android:layout_width="@dimen/magnification_drag_view_width"
+ android:layout_height="@dimen/magnification_drag_view_height"
+ android:layout_alignParentBottom="true"
+ android:layout_centerHorizontal="true"
+ android:background="@color/magnification_border_color" />
+
+ </RelativeLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 6becd21..79629e4 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -154,12 +154,5 @@
<declare-styleable name="CaptionsToggleImageButton">
<attr name="optedOut" format="boolean" />
</declare-styleable>
-
- <!-- Theme attributes used to style the appearance of expanded Bubbles -->
- <declare-styleable name="BubbleExpandedView">
- <attr name="android:colorBackgroundFloating" />
- <attr name="android:dialogCornerRadius" />
- </declare-styleable>
-
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 92c7477..c142465 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -211,4 +211,5 @@
<color name="GM2_green_500">#FF34A853</color>
<color name="GM2_blue_500">#FF4285F4</color>
+ <color name="magnification_border_color">#FF9900</color>
</resources>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index db22542..53cd971 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -22,13 +22,9 @@
<color name="recents_tv_dismiss_text_color">#7FEEEEEE</color>
<color name="recents_tv_text_shadow_color">#7F000000</color>
-
- <!-- Text color used in audio recording bar: G50 -->
- <color name="tv_audio_recording_bar_text">#FFF8F9FA</color>
- <!-- Background color for a chip in audio recording bar: G800 -->
- <color name="tv_audio_recording_bar_chip_background">#FF3C4043</color>
- <!-- Audio recording bar background color: G900 -->
- <color name="tv_audio_recording_bar_background">#FF202124</color>
+ <!-- Background color for audio recording indicator (G800) -->
+ <color name="tv_audio_recording_indicator_background">#FF3C4043</color>
+ <color name="tv_audio_recording_indicator_pulse">#4DFFFFFF</color>
<color name="red">#FFCC0000</color>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e896c16..640f31b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -295,6 +295,7 @@
<item>com.android.systemui.SizeCompatModeActivityController</item>
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
<item>com.android.systemui.theme.ThemeOverlayController</item>
+ <item>com.android.systemui.accessibility.WindowMagnification</item>
</string-array>
<!-- SystemUI vender service, used in config_systemUIServiceComponents. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c948116..da0323a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1139,4 +1139,16 @@
<dimen name="qs_media_width">350dp</dimen>
<dimen name="qs_media_padding">8dp</dimen>
<dimen name="qs_media_corner_radius">10dp</dimen>
+
+ <dimen name="magnification_border_size">5dp</dimen>
+ <dimen name="magnification_frame_move_short">5dp</dimen>
+ <dimen name="magnification_frame_move_long">25dp</dimen>
+ <dimen name="magnification_drag_view_width">100dp</dimen>
+ <dimen name="magnification_drag_view_height">35dp</dimen>
+ <dimen name="magnification_controls_size">90dp</dimen>
+ <dimen name="magnifier_left_right_controls_width">35dp</dimen>
+ <dimen name="magnifier_left_right_controls_height">45dp</dimen>
+ <dimen name="magnifier_up_down_controls_width">45dp</dimen>
+ <dimen name="magnifier_up_down_controls_height">40dp</dimen>
+
</resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index deae7e2..c1cf7b4 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -32,4 +32,6 @@
<!-- Ratio of "right" end of status bar that will swipe to QS. -->
<integer name="qs_split_fraction">2</integer>
+ <integer name="magnification_default_scale">2</integer>
+
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1053750..380dcfd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -914,8 +914,8 @@
<string name="quick_settings_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
<!-- QuickSettings: Label for the toggle to activate dark theme (A.K.A Dark Mode). [CHAR LIMIT=20] -->
<string name="quick_settings_ui_mode_night_label">Dark theme</string>
- <!-- QuickSettings: Label for the dark theme tile when enabled by battery saver. [CHAR LIMIT=40] -->
- <string name="quick_settings_ui_mode_night_label_battery_saver">Dark theme\nBattery saver</string>
+ <!-- QuickSettings: Secondary text for the dark theme tile when enabled by battery saver. [CHAR LIMIT=20] -->
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver">Battery Saver</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be enabled at sunset. [CHAR LIMIT=20] -->
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string>
<!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] -->
@@ -1140,13 +1140,16 @@
<string name="battery_saver_notification_action_text">Turn off Battery Saver</string>
<!-- Media projection permission dialog warning text. [CHAR LIMIT=NONE] -->
- <string name="media_projection_dialog_text">While recording or casting, <xliff:g id="app_seeking_permission" example="Hangouts">%s</xliff:g> can capture any sensitive information that is displayed on your screen or played from your device, including sensitive information such as audio, passwords, payment info, photos and messages.</string>
+ <string name="media_projection_dialog_text"><xliff:g id="app_seeking_permission" example="Hangouts">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play.</string>
<!-- Media projection permission dialog warning text for system services. [CHAR LIMIT=NONE] -->
- <string name="media_projection_dialog_service_text">While recording or casting, the service providing this function can capture any sensitive information that is displayed on your screen or played from your device, including sensitive information such as audio, passwords, payment info, photos and messages.</string>
+ <string name="media_projection_dialog_service_text">The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play.</string>
+
+ <!-- Media projection permission dialog warning title for system services. [CHAR LIMIT=NONE] -->
+ <string name="media_projection_dialog_service_title">Start recording or casting ?</string>
<!-- Media projection permission dialog warning title. [CHAR LIMIT=NONE] -->
- <string name="media_projection_dialog_title">Exposing sensitive info during casting/recording </string>
+ <string name="media_projection_dialog_title">Start recording or casting with <xliff:g id="app_seeking_permission" example="Hangouts">%s</xliff:g>?</string>
<!-- Media projection permission dialog permanent grant check box. [CHAR LIMIT=NONE] -->
<string name="media_projection_remember_text">Don\'t show again</string>
@@ -2492,4 +2495,12 @@
<!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] -->
<string name="inattentive_sleep_warning_title">Standby</string>
+
+ <!-- Window Magnification strings -->
+ <!-- Title for Magnification Overlay Window [CHAR LIMIT=NONE] -->
+ <string name="magnification_overlay_title">Magnification Overlay Window</string>
+ <!-- Title for Magnification Window [CHAR LIMIT=NONE] -->
+ <string name="magnification_window_title">Magnification Window</string>
+ <!-- Title for Magnification Controls Window [CHAR LIMIT=NONE] -->
+ <string name="magnification_controls_title">Magnification Window Controls</string>
</resources>
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index a9bdb71..6d61ff9 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -31,4 +31,8 @@
<string name="pip_close">Close PIP</string>
<!-- Button to move picture-in-picture (PIP) screen to the fullscreen in PIP menu [CHAR LIMIT=30] -->
<string name="pip_fullscreen">Full screen</string>
+
+ <!-- Title and subtitle for AudioRecordingIndicator -->
+ <string name="mic_active">Microphone Active</string>
+ <string name="app_accessed_mic">%1$s accessed your microphone</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 926d016..3ff8243 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -15,6 +15,7 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="BubbleOverflow" parent="@android:style/Theme.NoTitleBar"></style>
<style name="ClearAllButtonDefaultMargins">
<item name="android:layout_marginStart">0dp</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
index 18dc185..0c7e56e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
@@ -37,6 +37,13 @@
}
/**
+ * @see Activity#unregisterRemoteAnimations
+ */
+ public void unregisterRemoteAnimations() {
+ mWrapped.unregisterRemoteAnimations();
+ }
+
+ /**
* @see android.view.ViewDebug#dumpv2(View, ByteArrayOutputStream)
*/
public boolean encodeViewHierarchy(ByteArrayOutputStream out) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 70f8e15..deaafca 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -28,6 +28,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.annotation.NonNull;
+import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManager.RunningTaskInfo;
@@ -161,6 +162,23 @@
}
/**
+ * Removes the outdated snapshot of home task.
+ */
+ public void invalidateHomeTaskSnapshot(final Activity homeActivity) {
+ mBackgroundExecutor.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ ActivityTaskManager.getService().invalidateHomeTaskSnapshot(
+ homeActivity.getActivityToken());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to invalidate home snapshot", e);
+ }
+ }
+ });
+ }
+
+ /**
* @return the activity label, badging if necessary.
*/
public String getBadgedActivityLabel(ActivityInfo info, int userId) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskDescriptionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskDescriptionCompat.java
index eaf8d9b..35952f5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskDescriptionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskDescriptionCompat.java
@@ -17,6 +17,7 @@
package com.android.systemui.shared.system;
import android.app.ActivityManager;
+import android.graphics.Bitmap;
public class TaskDescriptionCompat {
@@ -37,4 +38,12 @@
? mTaskDescription.getBackgroundColor()
: 0;
}
+
+ public static Bitmap getIcon(ActivityManager.TaskDescription desc, int userId) {
+ if (desc.getInMemoryIcon() != null) {
+ return desc.getInMemoryIcon();
+ }
+ return ActivityManager.TaskDescription.loadTaskDescriptionIcon(
+ desc.getIconFilename(), userId);
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 22d1675c..9f13718 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -215,4 +216,23 @@
public void removePinnedStackListener(PinnedStackListener listener) {
mPinnedStackListenerForwarder.removeListener(listener);
}
+
+ /**
+ * Mirrors a specified display. The SurfaceControl returned is the root of the mirrored
+ * hierarchy.
+ *
+ * @param displayId The id of the display to mirror
+ * @return The SurfaceControl for the root of the mirrored hierarchy.
+ */
+ public SurfaceControl mirrorDisplay(final int displayId) {
+ try {
+ SurfaceControl outSurfaceControl = new SurfaceControl();
+ WindowManagerGlobal.getWindowManagerService().mirrorDisplay(displayId,
+ outSurfaceControl);
+ return outSurfaceControl;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reach window manager", e);
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index d66a53c..255693b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -43,7 +43,7 @@
import com.android.settingslib.WirelessUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import java.util.List;
@@ -163,8 +163,7 @@
public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
boolean showMissingSim) {
mContext = context;
- mIsEmergencyCallCapable = context.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable);
+ mIsEmergencyCallCapable = getTelephonyManager().isVoiceCapable();
mShowAirplaneMode = showAirplaneMode;
mShowMissingSim = showMissingSim;
@@ -609,7 +608,7 @@
private boolean mShowMissingSim;
@Inject
- public Builder(Context context, @MainResources Resources resources) {
+ public Builder(Context context, @Main Resources resources) {
mContext = context;
mSeparator = resources.getString(
com.android.internal.R.string.kg_text_message_separator);
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 867014b6..c2cedad 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -28,7 +28,9 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
@@ -86,13 +88,16 @@
public EmergencyButton(Context context, AttributeSet attrs) {
super(context, attrs);
- mIsVoiceCapable = context.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable);
+ mIsVoiceCapable = getTelephonyManager().isVoiceCapable();
mEnableEmergencyCallWhileSimLocked = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
}
+ private TelephonyManager getTelephonyManager() {
+ return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -110,11 +115,7 @@
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- takeEmergencyCallAction();
- }
- });
+ setOnClickListener(v -> takeEmergencyCallAction());
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
@@ -164,9 +165,9 @@
*/
public void takeEmergencyCallAction() {
MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
- // TODO: implement a shorter timeout once new PowerManager API is ready.
- // should be the equivalent to the old userActivity(EMERGENCY_CALL_TIMEOUT)
- mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ if (mPowerManager != null) {
+ mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+ }
try {
ActivityTaskManager.getService().stopSystemLockTaskMode();
} catch (RemoteException e) {
@@ -178,10 +179,19 @@
mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
}
} else {
- Dependency.get(KeyguardUpdateMonitor.class).reportEmergencyCallAction(
- true /* bypassHandler */);
+ KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+ if (updateMonitor != null) {
+ updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+ } else {
+ Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
+ }
+ TelecomManager telecomManager = getTelecommManager();
+ if (telecomManager == null) {
+ Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+ return;
+ }
Intent emergencyDialIntent =
- getTelecommManager().createLaunchEmergencyDialerIntent(null /* number*/)
+ telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_CLEAR_TOP)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 9380eb4..4e7956d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -290,6 +290,7 @@
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ getWindow().setFitWindowInsetsTypes(0 /* types */);
getWindow().setNavigationBarContrastEnforced(false);
getWindow().setNavigationBarColor(Color.TRANSPARENT);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9bba2aa..694c623 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -92,7 +92,6 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
@@ -102,7 +101,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.MainLooper;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -1060,7 +1059,7 @@
if (DEBUG_SIM_STATES) {
Log.v(TAG, "action " + action
+ " state: " + intent.getStringExtra(
- IccCardConstants.INTENT_KEY_ICC_STATE)
+ Intent.EXTRA_SIM_STATE)
+ " slotId: " + args.slotId + " subid: " + args.subId);
}
mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState)
@@ -1236,38 +1235,38 @@
if (!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
}
- String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+ String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE);
int slotId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0);
int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
+ if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) {
final String absentReason = intent
- .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
+ .getStringExtra(Intent.EXTRA_SIM_LOCKED_REASON);
- if (IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals(
+ if (Intent.SIM_ABSENT_ON_PERM_DISABLED.equals(
absentReason)) {
state = TelephonyManager.SIM_STATE_PERM_DISABLED;
} else {
state = TelephonyManager.SIM_STATE_ABSENT;
}
- } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
+ } else if (Intent.SIM_STATE_READY.equals(stateExtra)) {
state = TelephonyManager.SIM_STATE_READY;
- } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
+ } else if (Intent.SIM_STATE_LOCKED.equals(stateExtra)) {
final String lockedReason = intent
- .getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
- if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
+ .getStringExtra(Intent.EXTRA_SIM_LOCKED_REASON);
+ if (Intent.SIM_LOCKED_ON_PIN.equals(lockedReason)) {
state = TelephonyManager.SIM_STATE_PIN_REQUIRED;
- } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
+ } else if (Intent.SIM_LOCKED_ON_PUK.equals(lockedReason)) {
state = TelephonyManager.SIM_STATE_PUK_REQUIRED;
} else {
state = TelephonyManager.SIM_STATE_UNKNOWN;
}
- } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
+ } else if (Intent.SIM_LOCKED_NETWORK.equals(stateExtra)) {
state = TelephonyManager.SIM_STATE_NETWORK_LOCKED;
- } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+ } else if (Intent.SIM_STATE_CARD_IO_ERROR.equals(stateExtra)) {
state = TelephonyManager.SIM_STATE_CARD_IO_ERROR;
- } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)
- || IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(stateExtra)) {
+ } else if (Intent.SIM_STATE_LOADED.equals(stateExtra)
+ || Intent.SIM_STATE_IMSI.equals(stateExtra)) {
// This is required because telephony doesn't return to "READY" after
// these state transitions. See bug 7197471.
state = TelephonyManager.SIM_STATE_READY;
@@ -1497,7 +1496,7 @@
@VisibleForTesting
@Inject
- protected KeyguardUpdateMonitor(Context context, @MainLooper Looper mainLooper,
+ protected KeyguardUpdateMonitor(Context context, @Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
DumpController dumpController) {
mContext = context;
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java
index 812f215..0210e08 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java
@@ -25,12 +25,12 @@
final class ClockInfo {
private final String mName;
- private final String mTitle;
+ private final Supplier<String> mTitle;
private final String mId;
private final Supplier<Bitmap> mThumbnail;
private final Supplier<Bitmap> mPreview;
- private ClockInfo(String name, String title, String id,
+ private ClockInfo(String name, Supplier<String> title, String id,
Supplier<Bitmap> thumbnail, Supplier<Bitmap> preview) {
mName = name;
mTitle = title;
@@ -50,7 +50,7 @@
* Gets the name (title) of the clock face to be shown in the picker app.
*/
String getTitle() {
- return mTitle;
+ return mTitle.get();
}
/**
@@ -80,7 +80,7 @@
static class Builder {
private String mName;
- private String mTitle;
+ private Supplier<String> mTitle;
private String mId;
private Supplier<Bitmap> mThumbnail;
private Supplier<Bitmap> mPreview;
@@ -94,7 +94,7 @@
return this;
}
- public Builder setTitle(String title) {
+ public Builder setTitle(Supplier<String> title) {
mTitle = title;
return this;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index dfabe69..9cd4aec 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -244,11 +244,12 @@
mPreviewClocks.reloadCurrentClock();
mListeners.forEach((listener, clocks) -> {
clocks.reloadCurrentClock();
- ClockPlugin clock = clocks.getCurrentClock();
- if (clock instanceof DefaultClockController) {
- listener.onClockChanged(null);
+ final ClockPlugin clock = clocks.getCurrentClock();
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ listener.onClockChanged(clock instanceof DefaultClockController ? null : clock);
} else {
- listener.onClockChanged(clock);
+ mMainHandler.post(() -> listener.onClockChanged(
+ clock instanceof DefaultClockController ? null : clock));
}
});
}
@@ -323,7 +324,7 @@
mClocks.put(plugin.getClass().getName(), plugin);
mClockInfo.add(ClockInfo.builder()
.setName(plugin.getName())
- .setTitle(plugin.getTitle())
+ .setTitle(plugin::getTitle)
.setId(id)
.setThumbnail(plugin::getThumbnail)
.setPreview(() -> plugin.getPreview(mWidth, mHeight))
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index 41a7bc4..47a10af 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -19,117 +19,99 @@
import android.view.View;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
+import dagger.Lazy;
+
/**
* Single common instance of ActivityStarter that can be gotten and referenced from anywhere, but
- * delegates to an actual implementation such as StatusBar, assuming it exists.
+ * delegates to an actual implementation (StatusBar).
*/
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Singleton
public class ActivityStarterDelegate implements ActivityStarter {
- private ActivityStarter mActualStarter;
+ private Optional<Lazy<StatusBar>> mActualStarter;
@Inject
- public ActivityStarterDelegate() {
+ public ActivityStarterDelegate(Optional<Lazy<StatusBar>> statusBar) {
+ mActualStarter = statusBar;
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.startPendingIntentDismissingKeyguard(intent);
+ mActualStarter.ifPresent(
+ starter -> starter.get().startPendingIntentDismissingKeyguard(intent));
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent,
Runnable intentSentCallback) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.startPendingIntentDismissingKeyguard(intent, intentSentCallback);
+ mActualStarter.ifPresent(
+ starter -> starter.get().startPendingIntentDismissingKeyguard(intent,
+ intentSentCallback));
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent,
Runnable intentSentCallback, View associatedView) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.startPendingIntentDismissingKeyguard(intent, intentSentCallback,
- associatedView);
+ mActualStarter.ifPresent(
+ starter -> starter.get().startPendingIntentDismissingKeyguard(intent,
+ intentSentCallback, associatedView));
}
@Override
public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
int flags) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.startActivity(intent, onlyProvisioned, dismissShade, flags);
+ mActualStarter.ifPresent(
+ starter -> starter.get().startActivity(intent, onlyProvisioned, dismissShade,
+ flags));
}
@Override
public void startActivity(Intent intent, boolean dismissShade) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.startActivity(intent, dismissShade);
+ mActualStarter.ifPresent(starter -> starter.get().startActivity(intent, dismissShade));
}
@Override
public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.startActivity(intent, onlyProvisioned, dismissShade);
+ mActualStarter.ifPresent(
+ starter -> starter.get().startActivity(intent, onlyProvisioned, dismissShade));
}
@Override
public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.startActivity(intent, dismissShade, callback);
+ mActualStarter.ifPresent(
+ starter -> starter.get().startActivity(intent, dismissShade, callback));
}
@Override
public void postStartActivityDismissingKeyguard(Intent intent, int delay) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.postStartActivityDismissingKeyguard(intent, delay);
+ mActualStarter.ifPresent(
+ starter -> starter.get().postStartActivityDismissingKeyguard(intent, delay));
}
@Override
public void postStartActivityDismissingKeyguard(PendingIntent intent) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.postStartActivityDismissingKeyguard(intent);
+ mActualStarter.ifPresent(
+ starter -> starter.get().postStartActivityDismissingKeyguard(intent));
}
@Override
public void postQSRunnableDismissingKeyguard(Runnable runnable) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.postQSRunnableDismissingKeyguard(runnable);
+ mActualStarter.ifPresent(
+ starter -> starter.get().postQSRunnableDismissingKeyguard(runnable));
}
@Override
public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancel,
boolean afterKeyguardGone) {
- if (mActualStarter == null) {
- return;
- }
- mActualStarter.dismissKeyguardThenExecute(action, cancel, afterKeyguardGone);
- }
-
- public void setActivityStarterImpl(ActivityStarter starter) {
- mActualStarter = starter;
+ mActualStarter.ifPresent(starter -> starter.get().dismissKeyguardThenExecute(action, cancel,
+ afterKeyguardGone));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 2afcb12..d68fe15 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -42,6 +42,7 @@
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -288,6 +289,7 @@
public void onTuningChanged(String key, String newValue) {
if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
+ setVisibility(icons.contains(mSlotBattery) ? View.GONE : View.VISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
index 8e472f9..8503396 100644
--- a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
+++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
@@ -57,6 +57,7 @@
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(getStrokePx());
+ setLayerType(View.LAYER_TYPE_SOFTWARE, mPaint);
final int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
final int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
@@ -114,10 +115,17 @@
* appropriately. Intention is to match the home handle color.
*/
public void updateDarkness(float darkIntensity) {
+ // Handle color is same as home handle color.
int color = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
mLightColor, mDarkColor);
+ // Shadow color is inverse of handle color.
+ int shadowColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+ mDarkColor, mLightColor);
if (mPaint.getColor() != color) {
mPaint.setColor(color);
+ mPaint.setShadowLayer(/** radius */ getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.assist_handle_shadow_radius), /** shadowDx */ 0,
+ /** shadowDy */ 0, /** color */ shadowColor);
if (getVisibility() == VISIBLE && getAlpha() > 0) {
invalidate();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 6821265..a9ca04b 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -39,10 +39,8 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.BgHandler;
-import com.android.systemui.dagger.qualifiers.BgLooper;
-import com.android.systemui.dagger.qualifiers.MainHandler;
-import com.android.systemui.dagger.qualifiers.MainLooper;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -124,6 +122,7 @@
import com.android.systemui.util.leak.LeakReporter;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.wm.DisplayWindowController;
+import com.android.systemui.wm.SystemWindows;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -209,7 +208,6 @@
private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
@Inject Lazy<ActivityStarter> mActivityStarter;
- @Inject Lazy<ActivityStarterDelegate> mActivityStarterDelegate;
@Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
@Inject Lazy<AsyncSensorManager> mAsyncSensorManager;
@Inject Lazy<BluetoothController> mBluetoothController;
@@ -297,10 +295,10 @@
@Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
@Inject Lazy<AutoHideController> mAutoHideController;
@Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
- @Inject @BgLooper Lazy<Looper> mBgLooper;
- @Inject @BgHandler Lazy<Handler> mBgHandler;
- @Inject @MainLooper Lazy<Looper> mMainLooper;
- @Inject @MainHandler Lazy<Handler> mMainHandler;
+ @Inject @Background Lazy<Looper> mBgLooper;
+ @Inject @Background Lazy<Handler> mBgHandler;
+ @Inject @Main Lazy<Looper> mMainLooper;
+ @Inject @Main Lazy<Handler> mMainHandler;
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Nullable
@Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
@@ -323,6 +321,7 @@
@Inject Lazy<Recents> mRecents;
@Inject Lazy<StatusBar> mStatusBar;
@Inject Lazy<DisplayWindowController> mDisplayWindowController;
+ @Inject Lazy<SystemWindows> mSystemWindows;
@Inject
public Dependency() {
@@ -339,7 +338,6 @@
mProviders.put(MAIN_LOOPER, mMainLooper::get);
mProviders.put(MAIN_HANDLER, mMainHandler::get);
mProviders.put(ActivityStarter.class, mActivityStarter::get);
- mProviders.put(ActivityStarterDelegate.class, mActivityStarterDelegate::get);
mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get);
@@ -513,6 +511,7 @@
mProviders.put(Recents.class, mRecents::get);
mProviders.put(StatusBar.class, mStatusBar::get);
mProviders.put(DisplayWindowController.class, mDisplayWindowController::get);
+ mProviders.put(SystemWindows.class, mSystemWindows::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
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index ecf4c0a..e2b12da 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -179,7 +179,8 @@
mTouchSlop = configuration.getScaledTouchSlop();
mSGD = new ScaleGestureDetector(context, mScaleGestureListener);
- mFlingAnimationUtils = new FlingAnimationUtils(context, EXPAND_DURATION);
+ mFlingAnimationUtils = new FlingAnimationUtils(mContext.getResources().getDisplayMetrics(),
+ EXPAND_DURATION);
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
index 41dd5bbf..82e665b 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
@@ -24,7 +24,7 @@
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.util.Assert;
@@ -50,7 +50,7 @@
@Inject
public ForegroundServiceController(NotificationEntryManager entryManager,
- AppOpsController appOpsController, @MainHandler Handler mainHandler) {
+ AppOpsController appOpsController, @Main Handler mainHandler) {
mEntryManager = entryManager;
mMainHandler = mainHandler;
appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 29a7167..e50d08c 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -108,12 +108,13 @@
if (mController != null) {
mController.addCallback(this /* StateListener */);
}
- mEglHelper = new EglHelper();
- mRenderer = new ImageWallpaperRenderer(context, this /* SurfaceProxy */);
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
+ mEglHelper = new EglHelper();
+ // Deferred init renderer because we need to get wallpaper by display context.
+ mRenderer = new ImageWallpaperRenderer(getDisplayContext(), this /* SurfaceProxy */);
setFixedSizeAllowed(true);
setOffsetNotificationsEnabled(true);
updateSurfaceSize();
@@ -177,14 +178,13 @@
mRenderer = null;
mEglHelper.finish();
mEglHelper = null;
- getSurfaceHolder().getSurface().hwuiDestroy();
});
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
mWorker.getThreadHandler().post(() -> {
- mEglHelper.init(holder);
+ mEglHelper.init(holder, needSupportWideColorGamut());
mRenderer.onSurfaceCreated();
});
}
@@ -257,7 +257,7 @@
// Check if we need to recreate egl surface.
if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) {
- if (!mEglHelper.createEglSurface(getSurfaceHolder())) {
+ if (!mEglHelper.createEglSurface(getSurfaceHolder(), needSupportWideColorGamut())) {
Log.w(TAG, "recreate egl surface failed!");
}
}
@@ -340,6 +340,10 @@
&& mController.getState() == StatusBarState.KEYGUARD;
}
+ private boolean needSupportWideColorGamut() {
+ return mRenderer.isWcgContent();
+ }
+
@Override
protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
super.dump(prefix, fd, out, args);
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index ab7eec5..0e736dc 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -68,7 +68,7 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.RegionInterceptingFrameLayout.RegionInterceptableView;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.plugins.qs.QS;
@@ -147,7 +147,7 @@
@Inject
public ScreenDecorations(Context context,
Lazy<StatusBar> statusBarLazy,
- @MainHandler Handler handler,
+ @Main Handler handler,
BroadcastDispatcher broadcastDispatcher,
TunerService tunerService) {
super(context);
@@ -495,6 +495,7 @@
lp.gravity = Gravity.TOP | Gravity.LEFT;
}
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.setFitWindowInsetsTypes(0 /* types */);
if (isLandscape(mRotation)) {
lp.width = WRAP_CONTENT;
lp.height = MATCH_PARENT;
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index 72a4030..4749add 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -280,6 +280,7 @@
R.layout.size_compat_mode_hint, null /* root */);
PopupWindow popupWindow = new PopupWindow(popupView,
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ popupWindow.setWindowLayoutType(mWinParams.type);
popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
popupWindow.setClippingEnabled(false);
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 58c52a1..4728327 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -93,13 +93,11 @@
private boolean mTouchAboveFalsingThreshold;
private boolean mDisableHwLayers;
private final boolean mFadeDependingOnAmountSwiped;
- private final Context mContext;
private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
public SwipeHelper(
int swipeDirection, Callback callback, Context context, FalsingManager falsingManager) {
- mContext = context;
mCallback = callback;
mHandler = new Handler();
mSwipeDirection = swipeDirection;
@@ -114,7 +112,8 @@
mFalsingThreshold = res.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
mFadeDependingOnAmountSwiped = res.getBoolean(R.bool.config_fadeDependingOnAmountSwiped);
mFalsingManager = falsingManager;
- mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
+ mFlingAnimationUtils = new FlingAnimationUtils(res.getDisplayMetrics(),
+ getMaxEscapeAnimDuration() / 1000f);
}
public void setDensityScale(float densityScale) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 1645957..41d8314 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -27,7 +27,7 @@
import android.util.Slog;
import com.android.internal.os.BinderInternal;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
@@ -41,7 +41,7 @@
private final Handler mMainHandler;
@Inject
- public SystemUIService(@MainHandler Handler mainHandler) {
+ public SystemUIService(@Main Handler mainHandler) {
super();
mMainHandler = mainHandler;
}
diff --git a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
index a726b42..d5a46de 100644
--- a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
+++ b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
@@ -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 com.android.systemui;
@@ -36,7 +36,7 @@
public UiOffloadThread() {
}
- public Future<?> submit(Runnable runnable) {
+ public Future<?> execute(Runnable runnable) {
return mExecutorService.submit(runnable);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
new file mode 100644
index 0000000..895207d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -0,0 +1,83 @@
+/*
+ * 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.accessibility;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Class to handle changes to setting window_magnification value.
+ */
+@Singleton
+public class WindowMagnification extends SystemUI {
+ private WindowMagnificationController mWindowMagnificationController;
+ private final Handler mHandler;
+
+ @Inject
+ public WindowMagnification(Context context, @Main Handler mainHandler) {
+ super(context);
+ mHandler = mainHandler;
+ }
+
+ @Override
+ public void start() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.WINDOW_MAGNIFICATION),
+ true, new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateWindowMagnification();
+ }
+ });
+ }
+
+ private void updateWindowMagnification() {
+ try {
+ boolean enable = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.WINDOW_MAGNIFICATION) != 0;
+ if (enable) {
+ enableMagnification();
+ } else {
+ disableMagnification();
+ }
+ } catch (Settings.SettingNotFoundException e) {
+ disableMagnification();
+ }
+ }
+
+ private void enableMagnification() {
+ if (mWindowMagnificationController == null) {
+ mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler);
+ }
+ mWindowMagnificationController.createWindowMagnification();
+ }
+
+ private void disableMagnification() {
+ if (mWindowMagnificationController != null) {
+ mWindowMagnificationController.deleteWindowMagnification();
+ }
+ mWindowMagnificationController = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
new file mode 100644
index 0000000..bfac4fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -0,0 +1,427 @@
+/*
+ * 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.accessibility;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+/**
+ * Class to handle adding and removing a window magnification.
+ */
+public class WindowMagnificationController implements View.OnClickListener,
+ View.OnLongClickListener, View.OnTouchListener, SurfaceHolder.Callback {
+ private final int mBorderSize;
+ private final int mMoveFrameAmountShort;
+ private final int mMoveFrameAmountLong;
+
+ private final Context mContext;
+ private final Point mDisplaySize = new Point();
+ private final int mDisplayId;
+ private final Handler mHandler;
+ private final Rect mMagnificationFrame = new Rect();
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ private final WindowManager mWm;
+
+ private float mScale;
+
+ private final Rect mTmpRect = new Rect();
+
+ // The root of the mirrored content
+ private SurfaceControl mMirrorSurface;
+
+ private boolean mIsPressedDown;
+
+ private View mLeftControl;
+ private View mUpControl;
+ private View mRightControl;
+ private View mBottomControl;
+
+ private View mDragView;
+ private View mLeftDrag;
+ private View mTopDrag;
+ private View mRightDrag;
+ private View mBottomDrag;
+
+ private final PointF mLastDrag = new PointF();
+ private final Point mMoveWindowOffset = new Point();
+
+ private View mMirrorView;
+ private SurfaceView mMirrorSurfaceView;
+ private View mControlsView;
+ private View mOverlayView;
+
+ private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable();
+
+ WindowMagnificationController(Context context, Handler handler) {
+ mContext = context;
+ mHandler = handler;
+ Display display = mContext.getDisplay();
+ display.getSize(mDisplaySize);
+ mDisplayId = mContext.getDisplayId();
+
+ mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+ Resources r = context.getResources();
+ mBorderSize = (int) r.getDimension(R.dimen.magnification_border_size);
+ mMoveFrameAmountShort = (int) r.getDimension(R.dimen.magnification_frame_move_short);
+ mMoveFrameAmountLong = (int) r.getDimension(R.dimen.magnification_frame_move_long);
+
+ mScale = r.getInteger(R.integer.magnification_default_scale);
+ }
+
+ /**
+ * Creates a magnification window if it doesn't already exist.
+ */
+ void createWindowMagnification() {
+ if (mMirrorView != null) {
+ return;
+ }
+ createOverlayWindow();
+ }
+
+ private void createOverlayWindow() {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.gravity = Gravity.TOP | Gravity.LEFT;
+ params.token = new Binder();
+ params.setTitle(mContext.getString(R.string.magnification_overlay_title));
+
+ mOverlayView = new View(mContext);
+ mOverlayView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mOverlayView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ createMirrorWindow();
+ createControls();
+ }
+
+ @Override
+ public void onWindowDetached() {
+
+ }
+ });
+
+ mOverlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+
+ mWm.addView(mOverlayView, params);
+ }
+
+ /**
+ * Deletes the magnification window.
+ */
+ void deleteWindowMagnification() {
+ if (mMirrorSurface != null) {
+ mTransaction.remove(mMirrorSurface).apply();
+ mMirrorSurface = null;
+ }
+
+ if (mOverlayView != null) {
+ mWm.removeView(mOverlayView);
+ mOverlayView = null;
+ }
+
+ if (mMirrorView != null) {
+ mWm.removeView(mMirrorView);
+ mMirrorView = null;
+ }
+
+ if (mControlsView != null) {
+ mWm.removeView(mControlsView);
+ mControlsView = null;
+ }
+ }
+
+ private void createMirrorWindow() {
+ setInitialStartBounds();
+
+ // The window should be the size the mirrored surface will be but also add room for the
+ // border and the drag handle.
+ int dragViewHeight = (int) mContext.getResources().getDimension(
+ R.dimen.magnification_drag_view_height);
+ int windowWidth = mMagnificationFrame.width() + 2 * mBorderSize;
+ int windowHeight = mMagnificationFrame.height() + dragViewHeight + 2 * mBorderSize;
+
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ windowWidth, windowHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.gravity = Gravity.TOP | Gravity.LEFT;
+ params.token = mOverlayView.getWindowToken();
+ params.x = mMagnificationFrame.left;
+ params.y = mMagnificationFrame.top;
+ params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ params.setTitle(mContext.getString(R.string.magnification_window_title));
+
+ mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
+ mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
+ // This places the SurfaceView's SurfaceControl above the ViewRootImpl's SurfaceControl to
+ // ensure the mirrored area can get touch instead of going to the window
+ mMirrorSurfaceView.setZOrderOnTop(true);
+
+ mMirrorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+ mWm.addView(mMirrorView, params);
+
+ SurfaceHolder holder = mMirrorSurfaceView.getHolder();
+ holder.addCallback(this);
+ holder.setFormat(PixelFormat.RGBA_8888);
+
+ addDragTouchListeners();
+ }
+
+ private void createControls() {
+ int controlsSize = (int) mContext.getResources().getDimension(
+ R.dimen.magnification_controls_size);
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(controlsSize, controlsSize,
+ WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.RGBA_8888);
+ lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ lp.token = mOverlayView.getWindowToken();
+ lp.setTitle(mContext.getString(R.string.magnification_controls_title));
+
+ mControlsView = LayoutInflater.from(mContext).inflate(R.layout.magnifier_controllers, null);
+ mWm.addView(mControlsView, lp);
+
+ mLeftControl = mControlsView.findViewById(R.id.left_control);
+ mUpControl = mControlsView.findViewById(R.id.up_control);
+ mRightControl = mControlsView.findViewById(R.id.right_control);
+ mBottomControl = mControlsView.findViewById(R.id.down_control);
+
+ mLeftControl.setOnClickListener(this);
+ mUpControl.setOnClickListener(this);
+ mRightControl.setOnClickListener(this);
+ mBottomControl.setOnClickListener(this);
+
+ mLeftControl.setOnLongClickListener(this);
+ mUpControl.setOnLongClickListener(this);
+ mRightControl.setOnLongClickListener(this);
+ mBottomControl.setOnLongClickListener(this);
+
+ mLeftControl.setOnTouchListener(this);
+ mUpControl.setOnTouchListener(this);
+ mRightControl.setOnTouchListener(this);
+ mBottomControl.setOnTouchListener(this);
+ }
+
+ private void setInitialStartBounds() {
+ // Sets the initial frame area for the mirror and places it in the center of the display.
+ int initSize = Math.min(mDisplaySize.x, mDisplaySize.y) / 2;
+ int initX = mDisplaySize.x / 2 - initSize / 2;
+ int initY = mDisplaySize.y / 2 - initSize / 2;
+ mMagnificationFrame.set(initX, initY, initX + initSize, initY + initSize);
+ }
+
+ /**
+ * This is called once the surfaceView is created so the mirrored content can be placed as a
+ * child of the surfaceView.
+ */
+ private void createMirror() {
+ mMirrorSurface = WindowManagerWrapper.getInstance().mirrorDisplay(mDisplayId);
+ if (!mMirrorSurface.isValid()) {
+ return;
+ }
+ mTransaction.show(mMirrorSurface)
+ .reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl());
+
+ modifyWindowMagnification(mTransaction);
+ mTransaction.apply();
+ }
+
+ private void addDragTouchListeners() {
+ mDragView = mMirrorView.findViewById(R.id.drag_handle);
+ mLeftDrag = mMirrorView.findViewById(R.id.left_handle);
+ mTopDrag = mMirrorView.findViewById(R.id.top_handle);
+ mRightDrag = mMirrorView.findViewById(R.id.right_handle);
+ mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
+
+ mDragView.setOnTouchListener(this);
+ mLeftDrag.setOnTouchListener(this);
+ mTopDrag.setOnTouchListener(this);
+ mRightDrag.setOnTouchListener(this);
+ mBottomDrag.setOnTouchListener(this);
+ }
+
+ /**
+ * Modifies the placement of the mirrored content.
+ */
+ private void modifyWindowMagnification(SurfaceControl.Transaction t) {
+ Rect sourceBounds = getSourceBounds(mMagnificationFrame, mScale);
+ // The final destination for the magnification surface should be at 0,0 since the
+ // ViewRootImpl's position will change
+ mTmpRect.set(0, 0, mMagnificationFrame.width(), mMagnificationFrame.height());
+
+ WindowManager.LayoutParams params =
+ (WindowManager.LayoutParams) mMirrorView.getLayoutParams();
+ params.x = mMagnificationFrame.left;
+ params.y = mMagnificationFrame.top;
+ mWm.updateViewLayout(mMirrorView, params);
+
+ t.setGeometry(mMirrorSurface, sourceBounds, mTmpRect, Surface.ROTATION_0);
+ }
+
+ @Override
+ public void onClick(View v) {
+ setMoveOffset(v, mMoveFrameAmountShort);
+ moveMirrorFromControls();
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ mIsPressedDown = true;
+ setMoveOffset(v, mMoveFrameAmountLong);
+ mHandler.post(mMoveMirrorRunnable);
+ return true;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (v == mLeftControl || v == mUpControl || v == mRightControl || v == mBottomControl) {
+ return handleControlTouchEvent(event);
+ } else if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
+ || v == mBottomDrag) {
+ return handleDragTouchEvent(event);
+ }
+ return false;
+ }
+
+ private boolean handleControlTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mIsPressedDown = false;
+ break;
+ }
+ return false;
+ }
+
+ private boolean handleDragTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLastDrag.set(event.getRawX(), event.getRawY());
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ int xDiff = (int) (event.getRawX() - mLastDrag.x);
+ int yDiff = (int) (event.getRawY() - mLastDrag.y);
+ mMagnificationFrame.offset(xDiff, yDiff);
+ mLastDrag.set(event.getRawX(), event.getRawY());
+ modifyWindowMagnification(mTransaction);
+ mTransaction.apply();
+ return true;
+ }
+ return false;
+ }
+
+ private void setMoveOffset(View v, int moveFrameAmount) {
+ mMoveWindowOffset.set(0, 0);
+
+ if (v == mLeftControl) {
+ mMoveWindowOffset.x = -moveFrameAmount;
+ } else if (v == mUpControl) {
+ mMoveWindowOffset.y = -moveFrameAmount;
+ } else if (v == mRightControl) {
+ mMoveWindowOffset.x = moveFrameAmount;
+ } else if (v == mBottomControl) {
+ mMoveWindowOffset.y = moveFrameAmount;
+ }
+ }
+
+ private void moveMirrorFromControls() {
+ mMagnificationFrame.offset(mMoveWindowOffset.x, mMoveWindowOffset.y);
+
+ modifyWindowMagnification(mTransaction);
+ mTransaction.apply();
+ }
+
+ /**
+ * Calculates the desired source bounds. This will be the area under from the center of the
+ * displayFrame, factoring in scale.
+ */
+ private Rect getSourceBounds(Rect displayFrame, float scale) {
+ int halfWidth = displayFrame.width() / 2;
+ int halfHeight = displayFrame.height() / 2;
+ int left = displayFrame.left + (halfWidth - (int) (halfWidth / scale));
+ int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
+ int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
+ int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
+ return new Rect(left, top, right, bottom);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ createMirror();
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+
+ class MoveMirrorRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (mIsPressedDown) {
+ moveMirrorFromControls();
+ mHandler.postDelayed(mMoveMirrorRunnable, 100);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index bad6b54..b083123 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -30,7 +30,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.Background;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -81,7 +81,7 @@
};
@Inject
- public AppOpsControllerImpl(Context context, @BgLooper Looper bgLooper,
+ public AppOpsControllerImpl(Context context, @Background Looper bgLooper,
DumpController dumpController) {
this(context, bgLooper, new PermissionFlagsCache(context), dumpController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index 79d4f8d..659629b 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -70,6 +70,7 @@
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
PixelFormat.TRANSLUCENT);
lp.setTitle("AssistDisclosure");
+ lp.setFitWindowInsetsTypes(0 /* types */);
mWm.addView(mView, lp);
mViewAdded = true;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 4cb1708..eb615a0 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -85,6 +85,7 @@
PixelFormat.TRANSLUCENT);
mLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
mLayoutParams.gravity = Gravity.BOTTOM;
+ mLayoutParams.setFitWindowInsetsTypes(0 /* types */);
mLayoutParams.setTitle("Assist");
mInvocationLightsView = (InvocationLightsView)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 3948416..36c89fd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -35,6 +35,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -590,6 +591,7 @@
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("BiometricPrompt");
lp.token = windowToken;
+ lp.setFitWindowInsetsTypes(lp.getFitWindowInsetsTypes() & ~Type.statusBars());
return lp;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 6b0d3c8..e0ca1ac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -18,6 +18,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -29,7 +30,6 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -341,6 +341,10 @@
if (DEBUG) Log.d(TAG, "hideAuthenticationDialog");
mCurrentDialog.dismissFromSystemServer();
+
+ // BiometricService will have already sent the callback to the client in this case.
+ // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
+ mCurrentDialog = null;
}
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
@@ -416,7 +420,7 @@
// TODO: Clean this up
Bundle bundle = (Bundle) mCurrentDialogArgs.arg1;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
- Authenticator.TYPE_CREDENTIAL);
+ Authenticators.DEVICE_CREDENTIAL);
}
showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index d6f830d..7d237c4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -16,23 +16,21 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
import android.annotation.IntDef;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
import android.os.UserManager;
import android.util.DisplayMetrics;
-import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -71,12 +69,12 @@
static boolean isDeviceCredentialAllowed(Bundle biometricPromptBundle) {
final int authenticators = getAuthenticators(biometricPromptBundle);
- return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
+ return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
}
static boolean isBiometricAllowed(Bundle biometricPromptBundle) {
final int authenticators = getAuthenticators(biometricPromptBundle);
- return (authenticators & Authenticator.TYPE_BIOMETRIC) != 0;
+ return (authenticators & Authenticators.BIOMETRIC_WEAK) != 0;
}
static int getAuthenticators(Bundle biometricPromptBundle) {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index adb288a..8cb0cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -28,8 +28,8 @@
import android.util.SparseArray
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
-import com.android.systemui.dagger.qualifiers.BgLooper
-import com.android.systemui.dagger.qualifiers.MainHandler
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import java.io.FileDescriptor
import java.io.PrintWriter
import javax.inject.Inject
@@ -46,7 +46,7 @@
private const val MSG_REMOVE_RECEIVER = 1
private const val MSG_REMOVE_RECEIVER_FOR_USER = 2
private const val TAG = "BroadcastDispatcher"
-private const val DEBUG = false
+private const val DEBUG = true
/**
* SystemUI master Broadcast Dispatcher.
@@ -62,8 +62,8 @@
@Singleton
open class BroadcastDispatcher @Inject constructor (
private val context: Context,
- @MainHandler private val mainHandler: Handler,
- @BgLooper private val bgLooper: Looper
+ @Main private val mainHandler: Handler,
+ @Background private val bgLooper: Looper
) : Dumpable {
// Only modify in BG thread
@@ -147,7 +147,13 @@
when (msg.what) {
MSG_ADD_RECEIVER -> {
val data = msg.obj as ReceiverData
- val userId = data.user.identifier
+ // If the receiver asked to be registered under the current user, we register
+ // under the actual current user.
+ val userId = if (data.user.identifier == UserHandle.USER_CURRENT) {
+ context.userId
+ } else {
+ data.user.identifier
+ }
if (userId < UserHandle.USER_ALL) {
if (DEBUG) Log.w(TAG, "Register receiver for invalid user: $userId")
return
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index a6a3ce0..dc24996 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -16,22 +16,15 @@
package com.android.systemui.bubbles;
import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
-import android.content.pm.LauncherApps;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.PathParser;
import android.widget.ImageView;
-import com.android.internal.graphics.ColorUtils;
-import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.DotRenderer;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -46,9 +39,9 @@
public class BadgedImageView extends ImageView {
/** Same value as Launcher3 dot code */
- private static final float WHITE_SCRIM_ALPHA = 0.54f;
+ public static final float WHITE_SCRIM_ALPHA = 0.54f;
/** Same as value in Launcher3 IconShape */
- private static final int DEFAULT_PATH_SIZE = 100;
+ public static final int DEFAULT_PATH_SIZE = 100;
static final int DOT_STATE_DEFAULT = 0;
static final int DOT_STATE_SUPPRESSED_FOR_FLYOUT = 1;
@@ -58,7 +51,6 @@
private int mCurrentDotState = DOT_STATE_SUPPRESSED_FOR_FLYOUT;
private Bubble mBubble;
- private BubbleIconFactory mBubbleIconFactory;
private int mIconBitmapSize;
private DotRenderer mDotRenderer;
@@ -94,6 +86,18 @@
mDotRenderer = new DotRenderer(mIconBitmapSize, iconPath, DEFAULT_PATH_SIZE);
}
+ /**
+ * Updates the view with provided info.
+ */
+ public void update(Bubble bubble, Bitmap bubbleImage, int dotColor, Path dotPath) {
+ mBubble = bubble;
+ setImageBitmap(bubbleImage);
+ setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT);
+ mDotColor = dotColor;
+ drawDot(dotPath);
+ animateDot();
+ }
+
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -140,14 +144,6 @@
}
/**
- * The colour to use for the dot.
- */
- void setDotColor(int color) {
- mDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
- invalidate();
- }
-
- /**
* @param iconPath The new icon path to use when calculating dot position.
*/
void drawDot(Path iconPath) {
@@ -187,25 +183,6 @@
}
/**
- * Populates this view with a bubble.
- * <p>
- * This should only be called when a new bubble is being set on the view, updates to the
- * current bubble should use {@link #update(Bubble)}.
- *
- * @param bubble the bubble to display in this view.
- */
- public void setBubble(Bubble bubble) {
- mBubble = bubble;
- }
-
- /**
- * @param factory Factory for creating normalized bubble icons.
- */
- public void setBubbleIconFactory(BubbleIconFactory factory) {
- mBubbleIconFactory = factory;
- }
-
- /**
* The key for the {@link Bubble} associated with this view, if one exists.
*/
@Nullable
@@ -213,15 +190,6 @@
return (mBubble != null) ? mBubble.getKey() : null;
}
- /**
- * Updates the UI based on the bubble, updates badge and animates messages as needed.
- */
- public void update(Bubble bubble) {
- mBubble = bubble;
- setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT);
- updateViews();
- }
-
int getDotColor() {
return mDotColor;
}
@@ -277,47 +245,4 @@
}
}).start();
}
-
- void updateViews() {
- if (mBubble == null || mBubbleIconFactory == null) {
- return;
- }
-
- Drawable bubbleDrawable = getBubbleDrawable(mContext);
- BitmapInfo badgeBitmapInfo = mBubbleIconFactory.getBadgedBitmap(mBubble);
- BitmapInfo bubbleBitmapInfo = mBubbleIconFactory.getBubbleBitmap(bubbleDrawable,
- badgeBitmapInfo);
- setImageBitmap(bubbleBitmapInfo.icon);
-
- // Update badge.
- mDotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, Color.WHITE, WHITE_SCRIM_ALPHA);
- setDotColor(mDotColor);
-
- // Update dot.
- Path iconPath = PathParser.createPathFromPathData(
- getResources().getString(com.android.internal.R.string.config_icon_mask));
- Matrix matrix = new Matrix();
- float scale = mBubbleIconFactory.getNormalizer().getScale(bubbleDrawable,
- null /* outBounds */, null /* path */, null /* outMaskShape */);
- float radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f;
- matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
- radius /* pivot y */);
- iconPath.transform(matrix);
- drawDot(iconPath);
-
- animateDot();
- }
-
- Drawable getBubbleDrawable(Context context) {
- if (mBubble.getShortcutInfo() != null && mBubble.usingShortcutInfo()) {
- LauncherApps launcherApps =
- (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
- int density = getContext().getResources().getConfiguration().densityDpi;
- return launcherApps.getShortcutIconDrawable(mBubble.getShortcutInfo(), density);
- } else {
- Notification.BubbleMetadata metadata = mBubble.getEntry().getBubbleMetadata();
- Icon ic = metadata.getIcon();
- return ic.loadDrawable(context);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index f3a7ca9..77c8e0b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles;
+import static android.os.AsyncTask.Status.FINISHED;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -26,20 +27,17 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
-import android.view.LayoutInflater;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -59,19 +57,19 @@
private NotificationEntry mEntry;
private final String mKey;
private final String mGroupId;
- private String mAppName;
- private Drawable mUserBadgedAppIcon;
- private ShortcutInfo mShortcutInfo;
-
- private boolean mInflated;
- private BadgedImageView mIconView;
- private BubbleExpandedView mExpandedView;
- private BubbleIconFactory mBubbleIconFactory;
private long mLastUpdated;
private long mLastAccessed;
- private boolean mIsUserCreated;
+ // Items that are typically loaded later
+ private String mAppName;
+ private ShortcutInfo mShortcutInfo;
+ private BadgedImageView mIconView;
+ private BubbleExpandedView mExpandedView;
+
+ private boolean mInflated;
+ private BubbleViewInfoTask mInflationTask;
+ private boolean mInflateSynchronously;
/**
* Whether this notification should be shown in the shade when it is also displayed as a bubble.
@@ -94,37 +92,11 @@
/** Used in tests when no UI is required. */
@VisibleForTesting(visibility = PRIVATE)
- Bubble(Context context, NotificationEntry e) {
+ Bubble(NotificationEntry e) {
mEntry = e;
mKey = e.getKey();
mLastUpdated = e.getSbn().getPostTime();
mGroupId = groupId(e);
-
- String shortcutId = e.getSbn().getNotification().getShortcutId();
- if (BubbleExperimentConfig.useShortcutInfoToBubble(context)
- && shortcutId != null) {
- mShortcutInfo = BubbleExperimentConfig.getShortcutInfo(context,
- e.getSbn().getPackageName(),
- e.getSbn().getUser(), shortcutId);
- }
-
- PackageManager pm = context.getPackageManager();
- ApplicationInfo info;
- try {
- info = pm.getApplicationInfo(
- mEntry.getSbn().getPackageName(),
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
- mAppName = String.valueOf(pm.getApplicationLabel(info));
- }
- Drawable appIcon = pm.getApplicationIcon(mEntry.getSbn().getPackageName());
- mUserBadgedAppIcon = pm.getUserBadgedIcon(appIcon, mEntry.getSbn().getUser());
- } catch (PackageManager.NameNotFoundException unused) {
- mAppName = mEntry.getSbn().getPackageName();
- }
}
public String getKey() {
@@ -143,41 +115,22 @@
return mEntry.getSbn().getPackageName();
}
+ @Nullable
public String getAppName() {
return mAppName;
}
- Drawable getUserBadgedAppIcon() {
- return mUserBadgedAppIcon;
- }
-
@Nullable
public ShortcutInfo getShortcutInfo() {
return mShortcutInfo;
}
- /**
- * Whether shortcut information should be used to populate the bubble.
- * <p>
- * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}.
- * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
- */
- public boolean usingShortcutInfo() {
- return BubbleExperimentConfig.isShortcutIntent(getBubbleIntent());
- }
-
- void setBubbleIconFactory(BubbleIconFactory factory) {
- mBubbleIconFactory = factory;
- }
-
- boolean isInflated() {
- return mInflated;
- }
-
+ @Nullable
BadgedImageView getIconView() {
return mIconView;
}
+ @Nullable
BubbleExpandedView getExpandedView() {
return mExpandedView;
}
@@ -188,20 +141,62 @@
}
}
- void inflate(LayoutInflater inflater, BubbleStackView stackView) {
- if (mInflated) {
- return;
+ /**
+ * Sets whether to perform inflation on the same thread as the caller. This method should only
+ * be used in tests, not in production.
+ */
+ @VisibleForTesting
+ void setInflateSynchronously(boolean inflateSynchronously) {
+ mInflateSynchronously = inflateSynchronously;
+ }
+
+ /**
+ * Starts a task to inflate & load any necessary information to display a bubble.
+ *
+ * @param callback the callback to notify one the bubble is ready to be displayed.
+ * @param context the context for the bubble.
+ * @param stackView the stackView the bubble is eventually added to.
+ * @param iconFactory the iconfactory use to create badged images for the bubble.
+ */
+ void inflate(BubbleViewInfoTask.Callback callback,
+ Context context,
+ BubbleStackView stackView,
+ BubbleIconFactory iconFactory) {
+ if (isBubbleLoading()) {
+ mInflationTask.cancel(true /* mayInterruptIfRunning */);
}
- mIconView = (BadgedImageView) inflater.inflate(
- R.layout.bubble_view, stackView, false /* attachToRoot */);
- mIconView.setBubbleIconFactory(mBubbleIconFactory);
- mIconView.setBubble(this);
+ mInflationTask = new BubbleViewInfoTask(this,
+ context,
+ stackView,
+ iconFactory,
+ callback);
+ if (mInflateSynchronously) {
+ mInflationTask.onPostExecute(mInflationTask.doInBackground());
+ } else {
+ mInflationTask.execute();
+ }
+ }
- mExpandedView = (BubbleExpandedView) inflater.inflate(
- R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- mExpandedView.setBubble(this, stackView);
+ private boolean isBubbleLoading() {
+ return mInflationTask != null && mInflationTask.getStatus() != FINISHED;
+ }
- mInflated = true;
+ boolean isInflated() {
+ return mInflated;
+ }
+
+ void setViewInfo(BubbleViewInfoTask.BubbleViewInfo info) {
+ if (!isInflated()) {
+ mIconView = info.imageView;
+ mExpandedView = info.expandedView;
+ mInflated = true;
+ }
+
+ mShortcutInfo = info.shortcutInfo;
+ mAppName = info.appName;
+
+ mExpandedView.update(this);
+ mIconView.update(this, info.badgedBubbleImage, info.dotColor, info.dotPath);
}
/**
@@ -218,13 +213,12 @@
}
}
- void updateEntry(NotificationEntry entry) {
+ /**
+ * Sets the entry associated with this bubble.
+ */
+ void setEntry(NotificationEntry entry) {
mEntry = entry;
mLastUpdated = entry.getSbn().getPostTime();
- if (mInflated) {
- mIconView.update(this);
- mExpandedView.update(this);
- }
}
/**
@@ -242,13 +236,6 @@
}
/**
- * @return the timestamp in milliseconds when this bubble was last displayed in expanded state
- */
- long getLastAccessTime() {
- return mLastAccessed;
- }
-
- /**
* @return the display id of the virtual display on which bubble contents is drawn.
*/
int getDisplayId() {
@@ -297,7 +284,9 @@
* Whether the bubble for this notification should show a dot indicating updated content.
*/
boolean showDot() {
- return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot();
+ return mShowBubbleUpdateDot
+ && !mEntry.shouldSuppressNotificationDot()
+ && !shouldSuppressNotification();
}
/**
@@ -305,6 +294,7 @@
*/
boolean showFlyout() {
return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
+ && !shouldSuppressNotification()
&& !mEntry.shouldSuppressNotificationList();
}
@@ -349,6 +339,16 @@
}
}
+ /**
+ * Whether shortcut information should be used to populate the bubble.
+ * <p>
+ * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}.
+ * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
+ */
+ boolean usingShortcutInfo() {
+ return BubbleExperimentConfig.isShortcutIntent(getBubbleIntent());
+ }
+
@Nullable
PendingIntent getBubbleIntent() {
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 1938194..7cd29ea 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -103,14 +103,11 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Singleton;
-import dagger.Lazy;
-
/**
* Bubbles are a special type of content that can "float" on top of other apps or System UI.
* Bubbles can be expanded to show more content.
@@ -147,12 +144,13 @@
private BubbleExpandListener mExpandListener;
@Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final NotificationGroupManager mNotificationGroupManager;
- private final Lazy<ShadeController> mShadeController;
+ private final ShadeController mShadeController;
private final RemoteInputUriController mRemoteInputUriController;
private Handler mHandler = new Handler() {};
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
+ private BubbleIconFactory mBubbleIconFactory;
// Tracks the id of the current (foreground) user.
private int mCurrentUserId;
@@ -162,6 +160,10 @@
// Saves notification keys of user created "fake" bubbles so that we can allow notifications
// like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
private final HashSet<String> mUserCreatedBubbles;
+ // If we're auto-bubbling bubbles via a whitelist, we need to track which notifs from that app
+ // have been "demoted" back to a notification so that we don't auto-bubbles those again.
+ // Doesn't persist across reboots, not a long-term solution.
+ private final HashSet<String> mUserBlockedBubbles;
// Bubbles get added to the status bar view
private final StatusBarWindowController mStatusBarWindowController;
@@ -182,6 +184,8 @@
/** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
+ private boolean mInflateSynchronously;
+
/**
* Listener to be notified when some states of the bubbles change.
*/
@@ -243,7 +247,7 @@
public BubbleController(Context context,
StatusBarWindowController statusBarWindowController,
StatusBarStateController statusBarStateController,
- Lazy<ShadeController> shadeController,
+ ShadeController shadeController,
BubbleData data,
ConfigurationController configurationController,
NotificationInterruptionStateProvider interruptionStateProvider,
@@ -261,7 +265,7 @@
public BubbleController(Context context,
StatusBarWindowController statusBarWindowController,
StatusBarStateController statusBarStateController,
- Lazy<ShadeController> shadeController,
+ ShadeController shadeController,
BubbleData data,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
ConfigurationController configurationController,
@@ -272,6 +276,7 @@
NotificationEntryManager entryManager,
RemoteInputUriController remoteInputUriController) {
mContext = context;
+ mShadeController = shadeController;
mNotificationInterruptionStateProvider = interruptionStateProvider;
mNotifUserManager = notifUserManager;
mZenModeController = zenModeController;
@@ -319,7 +324,6 @@
}
});
- mShadeController = shadeController;
mStatusBarWindowController = statusBarWindowController;
mStatusBarStateListener = new StatusBarStateListener();
statusBarStateController.addCallback(mStatusBarStateListener);
@@ -348,8 +352,19 @@
});
mUserCreatedBubbles = new HashSet<>();
+ mUserBlockedBubbles = new HashSet<>();
mScreenshotHelper = new ScreenshotHelper(context);
+ mBubbleIconFactory = new BubbleIconFactory(context);
+ }
+
+ /**
+ * Sets whether to perform inflation on the same thread as the caller. This method should only
+ * be used in tests, not in production.
+ */
+ @VisibleForTesting
+ void setInflateSynchronously(boolean inflateSynchronously) {
+ mInflateSynchronously = inflateSynchronously;
}
/**
@@ -413,16 +428,23 @@
@Override
public void onUiModeChanged() {
- if (mStackView != null) {
- mStackView.onThemeChanged();
- }
+ updateForThemeChanges();
}
@Override
public void onOverlayChanged() {
+ updateForThemeChanges();
+ }
+
+ private void updateForThemeChanges() {
if (mStackView != null) {
mStackView.onThemeChanged();
}
+ mBubbleIconFactory = new BubbleIconFactory(mContext);
+ for (Bubble b: mBubbleData.getBubbles()) {
+ // Reload each bubble
+ b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
+ }
}
@Override
@@ -506,14 +528,10 @@
return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
}
- void selectBubble(Bubble bubble) {
- mBubbleData.setSelectedBubble(bubble);
- }
-
@VisibleForTesting
void selectBubble(String key) {
Bubble bubble = mBubbleData.getBubbleWithKey(key);
- selectBubble(bubble);
+ mBubbleData.setSelectedBubble(bubble);
}
/**
@@ -560,11 +578,19 @@
}
void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
+ if (mStackView == null) {
+ // Lazy init stack view when a bubble is created
+ ensureStackViewCreated();
+ }
// If this is an interruptive notif, mark that it's interrupted
if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
notif.setInterruption();
}
- mBubbleData.notificationEntryUpdated(notif, suppressFlyout, showInShade);
+ Bubble bubble = mBubbleData.getOrCreateBubble(notif);
+ bubble.setInflateSynchronously(mInflateSynchronously);
+ bubble.inflate(
+ b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
+ mContext, mStackView, mBubbleIconFactory);
}
/**
@@ -579,10 +605,11 @@
if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
Log.d(TAG, "onUserCreatedBubble: " + entry.getKey());
}
- mShadeController.get().collapsePanel(true);
+ mShadeController.collapsePanel(true);
entry.setFlagBubble(true);
updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
mUserCreatedBubbles.add(entry.getKey());
+ mUserBlockedBubbles.remove(entry.getKey());
}
/**
@@ -598,6 +625,12 @@
entry.setFlagBubble(false);
removeBubble(entry.getKey(), DISMISS_BLOCKED);
mUserCreatedBubbles.remove(entry.getKey());
+ if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble(
+ mContext, entry.getSbn().getPackageName())) {
+ // This package is whitelist but user demoted the bubble, let's save it so we don't
+ // auto-bubble for the whitelist again.
+ mUserBlockedBubbles.add(entry.getKey());
+ }
}
/**
@@ -725,10 +758,11 @@
@SuppressWarnings("FieldCanBeLocal")
private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
@Override
- public void onPendingEntryAdded(NotificationEntry entry) {
+ public void onNotificationAdded(NotificationEntry entry) {
boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
+ boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
- mContext, entry, previouslyUserCreated);
+ mContext, entry, previouslyUserCreated, userBlocked);
if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
&& (canLaunchInActivityView(mContext, entry) || wasAdjusted)) {
@@ -743,8 +777,9 @@
@Override
public void onPreEntryUpdated(NotificationEntry entry) {
boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
+ boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
- mContext, entry, previouslyUserCreated);
+ mContext, entry, previouslyUserCreated, userBlocked);
boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
&& (canLaunchInActivityView(mContext, entry) || wasAdjusted);
@@ -772,16 +807,6 @@
@Override
public void applyUpdate(BubbleData.Update update) {
- if (mStackView == null && update.addedBubble != null) {
- // Lazy init stack view when the first bubble is added.
- ensureStackViewCreated();
- }
-
- // If not yet initialized, ignore all other changes.
- if (mStackView == null) {
- return;
- }
-
if (update.addedBubble != null) {
mStackView.addBubble(update.addedBubble);
}
@@ -878,12 +903,12 @@
if (DEBUG_BUBBLE_CONTROLLER) {
Log.d(TAG, "[BubbleData]");
- Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
+ Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getBubbles(),
mBubbleData.getSelectedBubble()));
if (mStackView != null) {
Log.d(TAG, "[BubbleStackView]");
- Log.d(TAG, formatBubblesString(mStackView.getBubblesOnScreen(),
+ Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(),
mStackView.getExpandedBubble()));
}
}
@@ -972,23 +997,6 @@
pw.println();
}
- static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
- StringBuilder sb = new StringBuilder();
- for (Bubble bubble : bubbles) {
- if (bubble == null) {
- sb.append(" <null> !!!!!\n");
- } else {
- boolean isSelected = (bubble == selected);
- sb.append(String.format("%s Bubble{act=%12d, ongoing=%d, key=%s}\n",
- ((isSelected) ? "->" : " "),
- bubble.getLastActivity(),
- (bubble.isOngoing() ? 1 : 0),
- bubble.getKey()));
- }
- }
- return sb.toString();
- }
-
/**
* This task stack listener is responsible for responding to tasks moved to the front
* which are on the default (main) display. When this happens, expanded bubbles must be
@@ -1124,36 +1132,33 @@
}
private void sendScreenshotToBubble(Bubble bubble) {
- // delay allows the bubble menu to disappear before the screenshot
- // done here because we already have a Handler to delay with.
- // TODO: Hide bubble + menu UI from screenshots entirely instead of just delaying.
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mScreenshotHelper.takeScreenshot(
- android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- true /* hasStatus */,
- true /* hasNav */,
- mHandler,
- new Consumer<Uri>() {
- @Override
- public void accept(Uri uri) {
- if (uri != null) {
- NotificationEntry entry = bubble.getEntry();
- Pair<RemoteInput, Notification.Action> pair = entry.getSbn()
- .getNotification().findRemoteInputActionPair(false);
- RemoteInput remoteInput = pair.first;
- Notification.Action action = pair.second;
- Intent dataIntent = prepareRemoteInputFromData("image/png", uri,
- remoteInput, entry);
- sendRemoteInput(dataIntent, entry, action.actionIntent);
- mBubbleData.setSelectedBubble(bubble);
- mBubbleData.setExpanded(true);
- }
+ mScreenshotHelper.takeScreenshot(
+ android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ true /* hasStatus */,
+ true /* hasNav */,
+ mHandler,
+ new Consumer<Uri>() {
+ @Override
+ public void accept(Uri uri) {
+ if (uri != null) {
+ NotificationEntry entry = bubble.getEntry();
+ Pair<RemoteInput, Notification.Action> pair = entry.getSbn()
+ .getNotification().findRemoteInputActionPair(false);
+ if (pair != null) {
+ RemoteInput remoteInput = pair.first;
+ Notification.Action action = pair.second;
+ Intent dataIntent = prepareRemoteInputFromData("image/png", uri,
+ remoteInput, entry);
+ sendRemoteInput(dataIntent, entry, action.actionIntent);
+ mBubbleData.setSelectedBubble(bubble);
+ mBubbleData.setExpanded(true);
+ } else {
+ Log.w(TAG, "No RemoteInput found for notification: "
+ + entry.getSbn().getKey());
}
- });
- }
- }, 200);
+ }
+ }
+ });
}
private final BubbleScreenshotListener mBubbleScreenshotListener =
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 034bff3..97224f1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -33,6 +33,7 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController.DismissReason;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -57,8 +58,6 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
- private static final int MAX_BUBBLES = 5;
-
private static final Comparator<Bubble> BUBBLES_BY_SORT_KEY_DESCENDING =
Comparator.comparing(BubbleData::sortKey).reversed();
@@ -115,6 +114,7 @@
private final List<Bubble> mBubbles;
private Bubble mSelectedBubble;
private boolean mExpanded;
+ private final int mMaxBubbles;
// State tracked during an operation -- keeps track of what listener events to dispatch.
private Update mStateChange;
@@ -144,6 +144,7 @@
mContext = context;
mBubbles = new ArrayList<>();
mStateChange = new Update(mBubbles);
+ mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
}
public boolean hasBubbles() {
@@ -179,28 +180,44 @@
dispatchPendingChanges();
}
- void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout,
- boolean showInShade) {
+ /**
+ * Constructs a new bubble or returns an existing one. Does not add new bubbles to
+ * bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)}
+ * for that.
+ */
+ Bubble getOrCreateBubble(NotificationEntry entry) {
+ Bubble bubble = getBubbleWithKey(entry.getKey());
+ if (bubble == null) {
+ bubble = new Bubble(entry);
+ } else {
+ bubble.setEntry(entry);
+ }
+ return bubble;
+ }
+
+ /**
+ * When this method is called it is expected that all info in the bubble has completed loading.
+ * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,
+ * BubbleStackView, BubbleIconFactory).
+ */
+ void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryUpdated: " + entry);
+ Log.d(TAG, "notificationEntryUpdated: " + bubble);
}
- Bubble bubble = getBubbleWithKey(entry.getKey());
- suppressFlyout |= !shouldShowFlyout(entry);
+ Bubble prevBubble = getBubbleWithKey(bubble.getKey());
+ suppressFlyout |= !shouldShowFlyout(bubble.getEntry());
- if (bubble == null) {
+ if (prevBubble == null) {
// Create a new bubble
- bubble = new Bubble(mContext, entry);
bubble.setSuppressFlyout(suppressFlyout);
doAdd(bubble);
trim();
} else {
// Updates an existing bubble
- bubble.updateEntry(entry);
bubble.setSuppressFlyout(suppressFlyout);
doUpdate(bubble);
}
-
if (bubble.shouldAutoExpand()) {
setSelectedBubbleInternal(bubble);
if (!mExpanded) {
@@ -213,6 +230,7 @@
bubble.setShowInShade(!isBubbleExpandedAndSelected && showInShade);
bubble.setShowDot(!isBubbleExpandedAndSelected /* show */, true /* animate */);
dispatchPendingChanges();
+
}
public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
@@ -329,7 +347,7 @@
}
private void trim() {
- if (mBubbles.size() > MAX_BUBBLES) {
+ if (mBubbles.size() > mMaxBubbles) {
mBubbles.stream()
// sort oldest first (ascending lastActivity)
.sorted(Comparator.comparingLong(Bubble::getLastActivity))
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
index a912ecc..3190662 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -16,6 +16,8 @@
package com.android.systemui.bubbles;
+import java.util.List;
+
/**
* Common class for the various debug {@link android.util.Log} output configuration in the Bubbles
* package.
@@ -38,5 +40,23 @@
static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
static final boolean DEBUG_EXPERIMENTS = true;
+ static final boolean DEBUG_OVERFLOW = false;
+ static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
+ StringBuilder sb = new StringBuilder();
+ for (Bubble bubble : bubbles) {
+ if (bubble == null) {
+ sb.append(" <null> !!!!!\n");
+ } else {
+ boolean isSelected = (selected != null && bubble == selected);
+ String arrow = isSelected ? "=>" : " ";
+ sb.append(String.format("%s Bubble{act=%12d, ongoing=%d, key=%s}\n",
+ arrow,
+ bubble.getLastActivity(),
+ (bubble.isOngoing() ? 1 : 0),
+ bubble.getKey()));
+ }
+ }
+ return sb.toString();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index efc955d..c1705db 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -99,7 +99,6 @@
private int mExpandedViewTouchSlop;
private Bubble mBubble;
- private String mAppName;
private BubbleController mBubbleController = Dependency.get(BubbleController.class);
private WindowManager mWindowManager;
@@ -275,17 +274,15 @@
}
void applyThemeAttrs() {
- TypedArray ta = getContext().obtainStyledAttributes(R.styleable.BubbleExpandedView);
- int bgColor = ta.getColor(
- R.styleable.BubbleExpandedView_android_colorBackgroundFloating, Color.WHITE);
- float cornerRadius = ta.getDimension(
- R.styleable.BubbleExpandedView_android_dialogCornerRadius, 0);
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {
+ android.R.attr.colorBackgroundFloating,
+ android.R.attr.dialogCornerRadius});
+ int bgColor = ta.getColor(0, Color.WHITE);
+ float cornerRadius = ta.getDimensionPixelSize(1, 0);
ta.recycle();
- // Update triangle color.
mPointerDrawable.setTint(bgColor);
-
- // Update ActivityView cornerRadius
if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(mContext.getResources())) {
mActivityView.setCornerRadius(cornerRadius);
}
@@ -341,26 +338,41 @@
}
}
- /**
- * Sets the bubble used to populate this view.
- */
- public void setBubble(Bubble bubble, BubbleStackView stackView) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "setBubble: bubble=" + (bubble != null ? bubble.getKey() : "null"));
- }
+ void setStackView(BubbleStackView stackView) {
mStackView = stackView;
- mBubble = bubble;
- mAppName = bubble.getAppName();
-
- applyThemeAttrs();
- showSettingsIcon();
- updateExpandedView();
}
/**
- * Lets activity view know it should be shown / populated.
+ * Sets the bubble used to populate this view.
*/
- public void populateExpandedView() {
+ void update(Bubble bubble) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+ }
+ boolean isNew = mBubble == null;
+ if (isNew || bubble.getKey().equals(mBubble.getKey())) {
+ mBubble = bubble;
+ mSettingsIcon.setContentDescription(getResources().getString(
+ R.string.bubbles_settings_button_description, bubble.getAppName()));
+
+ if (isNew) {
+ mBubbleIntent = mBubble.getBubbleIntent();
+ if (mBubbleIntent != null) {
+ setContentVisibility(false);
+ mActivityView.setVisibility(VISIBLE);
+ }
+ }
+ applyThemeAttrs();
+ } else {
+ Log.w(TAG, "Trying to update entry with different key, new bubble: "
+ + bubble.getKey() + " old bubble: " + bubble.getKey());
+ }
+ }
+
+ /**
+ * Lets activity view know it should be shown / populated with activity content.
+ */
+ void populateExpandedView() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "populateExpandedView: "
+ "bubble=" + getBubbleKey());
@@ -373,38 +385,6 @@
}
}
- /**
- * Updates the bubble backing this view. This will not re-populate ActivityView, it will
- * only update the deep-links in the title, and the height of the view.
- */
- public void update(Bubble bubble) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
- }
- if (bubble.getKey().equals(mBubble.getKey())) {
- mBubble = bubble;
- updateSettingsContentDescription();
- updateHeight();
- } else {
- Log.w(TAG, "Trying to update entry with different key, new bubble: "
- + bubble.getKey() + " old bubble: " + bubble.getKey());
- }
- }
-
- private void updateExpandedView() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateExpandedView: bubble="
- + getBubbleKey());
- }
-
- mBubbleIntent = mBubble.getBubbleIntent();
- if (mBubbleIntent != null) {
- setContentVisibility(false);
- mActivityView.setVisibility(VISIBLE);
- }
- updateView();
- }
-
boolean performBackPressIfNeeded() {
if (!usingActivityView()) {
return false;
@@ -492,16 +472,6 @@
}
}
- private void updateSettingsContentDescription() {
- mSettingsIcon.setContentDescription(getResources().getString(
- R.string.bubbles_settings_button_description, mAppName));
- }
-
- void showSettingsIcon() {
- updateSettingsContentDescription();
- mSettingsIcon.setVisibility(VISIBLE);
- }
-
/**
* Update appearance of the expanded view being displayed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 8299f22..4c1cf49 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -32,6 +32,9 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcelable;
@@ -39,8 +42,11 @@
import android.provider.Settings;
import android.util.Log;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleHubNotificationListenerKt;
import java.util.ArrayList;
import java.util.Arrays;
@@ -143,7 +149,7 @@
* @return whether an adjustment was made.
*/
static boolean adjustForExperiments(Context context, NotificationEntry entry,
- boolean previouslyUserCreated) {
+ boolean previouslyUserCreated, boolean userBlocked) {
Notification.BubbleMetadata metadata = null;
boolean addedMetadata = false;
boolean whiteListedToAutoBubble =
@@ -205,7 +211,9 @@
}
}
- boolean bubbleForWhitelist = whiteListedToAutoBubble && (addedMetadata || hasMetadata);
+ boolean bubbleForWhitelist = !userBlocked
+ && whiteListedToAutoBubble
+ && (addedMetadata || hasMetadata);
if ((previouslyUserCreated && addedMetadata) || bubbleForWhitelist) {
// Update to a previous bubble (or new autobubble), set its flag now.
if (DEBUG_EXPERIMENTS) {
@@ -224,12 +232,30 @@
// Use the icon of the person if available
List<Person> personList = getPeopleFromNotification(entry);
if (personList.size() > 0) {
- icon = personList.get(0).getIcon();
+ final Person person = personList.get(0);
+ if (person != null) {
+ icon = person.getIcon();
+ if (icon == null) {
+ // Lets try and grab the icon constructed by the layout
+ Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
+ if (d instanceof BitmapDrawable) {
+ icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+ }
+ }
+ }
}
if (icon == null) {
- icon = notification.getLargeIcon() != null
- ? notification.getLargeIcon()
- : notification.getSmallIcon();
+ boolean shouldTint = notification.getLargeIcon() == null;
+ icon = shouldTint
+ ? notification.getSmallIcon()
+ : notification.getLargeIcon();
+ if (shouldTint) {
+ int notifColor = entry.getSbn().getNotification().color;
+ notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
+ notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
+ true /* findFg */, 3f);
+ icon.setTint(notifColor);
+ }
}
if (intent != null) {
return new Notification.BubbleMetadata.Builder()
@@ -279,7 +305,7 @@
}
static boolean isShortcutIntent(PendingIntent intent) {
- return intent.equals(sDummyShortcutIntent);
+ return intent != null && intent.equals(sDummyShortcutIntent);
}
static List<Person> getPeopleFromNotification(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
index 9ff033c..b32dbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -15,11 +15,14 @@
*/
package com.android.systemui.bubbles;
+import android.app.Notification;
import android.content.Context;
+import android.content.pm.LauncherApps;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
@@ -42,19 +45,40 @@
return mContext.getResources().getDimensionPixelSize(
com.android.launcher3.icons.R.dimen.profile_badge_size);
}
+ /**
+ * Returns the drawable that the developer has provided to display in the bubble.
+ */
+ Drawable getBubbleDrawable(Bubble b, Context context) {
+ if (b.getShortcutInfo() != null && b.usingShortcutInfo()) {
+ LauncherApps launcherApps =
+ (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+ int density = context.getResources().getConfiguration().densityDpi;
+ return launcherApps.getShortcutIconDrawable(b.getShortcutInfo(), density);
+ } else {
+ Notification.BubbleMetadata metadata = b.getEntry().getBubbleMetadata();
+ Icon ic = metadata.getIcon();
+ return ic.loadDrawable(context);
+ }
+ }
- BitmapInfo getBadgedBitmap(Bubble b) {
+ /**
+ * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This
+ * will include the workprofile indicator on the badge if appropriate.
+ */
+ BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon) {
Bitmap userBadgedBitmap = createIconBitmap(
- b.getUserBadgedAppIcon(), 1f, getBadgeSize());
+ userBadgedAppIcon, 1f, getBadgeSize());
Canvas c = new Canvas();
ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize());
c.setBitmap(userBadgedBitmap);
shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
- BitmapInfo bitmapInfo = createIconBitmap(userBadgedBitmap);
- return bitmapInfo;
+ return createIconBitmap(userBadgedBitmap);
}
+ /**
+ * Returns a {@link BitmapInfo} for the entire bubble icon including the badge.
+ */
BitmapInfo getBubbleBitmap(Drawable bubble, BitmapInfo badge) {
BitmapInfo bubbleIconInfo = createBadgedIconBitmap(bubble,
null /* user */,
@@ -64,5 +88,4 @@
new BitmapDrawable(mContext.getResources(), badge.icon));
return bubbleIconInfo;
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java
index e8eb72e..bf83065 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMenuView.java
@@ -31,6 +31,9 @@
private FrameLayout mMenu;
private boolean mShowing = false;
+ /** Delay before taking a screenshot once the button is tapped to allow the menu time to hide.*/
+ public static final long SCREENSHOT_DELAY = 200;
+
public BubbleMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
new file mode 100644
index 0000000..018b631
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -0,0 +1,69 @@
+package com.android.systemui.bubbles;
+
+import android.app.Activity;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.os.Bundle;
+
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.systemui.R;
+
+/**
+ * Activity for showing aged out bubbles.
+ * Must be public to be accessible to androidx...AppComponentFactory
+ */
+public class BubbleOverflowActivity extends Activity {
+ private RecyclerView mRecyclerView;
+ private int mMaxBubbles;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.bubble_overflow_activity);
+ setBackgroundColor();
+
+ mMaxBubbles = getResources().getInteger(R.integer.bubbles_max_rendered);
+ mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
+ mRecyclerView.setLayoutManager(
+ new GridLayoutManager(getApplicationContext(), /* numberOfColumns */ mMaxBubbles));
+ }
+
+ void setBackgroundColor() {
+ final TypedArray ta = getApplicationContext().obtainStyledAttributes(
+ new int[] {android.R.attr.colorBackgroundFloating});
+ int bgColor = ta.getColor(0, Color.WHITE);
+ ta.recycle();
+ findViewById(android.R.id.content).setBackgroundColor(bgColor);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ public void onRestart() {
+ super.onRestart();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ public void onDestroy() {
+ super.onStop();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 29a4bb1..8987683 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -516,14 +516,7 @@
* Handle theme changes.
*/
public void onThemeChanged() {
- // Recreate icon factory to update default adaptive icon scale.
- mBubbleIconFactory = new BubbleIconFactory(mContext);
setUpFlyout();
- for (Bubble b: mBubbleData.getBubbles()) {
- b.getIconView().setBubbleIconFactory(mBubbleIconFactory);
- b.getIconView().updateViews();
- b.getExpandedView().applyThemeAttrs();
- }
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -749,10 +742,6 @@
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
}
- bubble.setBubbleIconFactory(mBubbleIconFactory);
- bubble.inflate(mInflater, this);
- bubble.getIconView().updateViews();
-
// Set the dot position to the opposite of the side the stack is resting on, since the stack
// resting slightly off-screen would result in the dot also being off-screen.
bubble.getIconView().setDotPosition(
@@ -1567,9 +1556,6 @@
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
if (mIsExpanded) {
- // First update the view so that it calculates a new height (ensuring the y position
- // calculation is correct)
- mExpandedBubble.getExpandedView().updateView();
final float y = getExpandedViewY();
if (!mExpandedViewYAnim.isRunning()) {
// We're not animating so set the value
@@ -1731,17 +1717,18 @@
*/
public void showBubbleMenu() {
PointF currentPos = mStackAnimationController.getStackPosition();
- float yPos = currentPos.y;
- float xPos = currentPos.x;
- if (mStackAnimationController.isStackOnLeftSide()) {
- xPos += mBubbleSize;
- } else {
- //TODO: Use the width of the menu instead of this fixed offset. Offset used for now
- // because menu width isn't correct the first time the menu is shown.
- xPos -= mBubbleMenuOffset;
- }
+ mBubbleMenuView.setVisibility(View.INVISIBLE);
+ post(() -> {
+ float yPos = currentPos.y;
+ float xPos = currentPos.x;
+ if (mStackAnimationController.isStackOnLeftSide()) {
+ xPos += mBubbleSize;
+ } else {
+ xPos -= mBubbleMenuView.getMenuView().getWidth();
+ }
- mBubbleMenuView.show(xPos, yPos);
+ mBubbleMenuView.show(xPos, yPos);
+ });
}
/**
@@ -1762,6 +1749,12 @@
* Take a screenshot and send it to the specified bubble.
*/
public void sendScreenshotToBubble(Bubble bubble) {
- mBubbleScreenshotListener.onBubbleScreenshot(bubble);
+ hideBubbleMenu();
+ // delay allows the bubble menu to disappear before the screenshot
+ // done here because we already have a Handler to delay with.
+ // TODO: Hide bubble + menu UI from screenshots entirely instead of just delaying.
+ postDelayed(() -> {
+ mBubbleScreenshotListener.onBubbleScreenshot(bubble);
+ }, BubbleMenuView.SCREENSHOT_DELAY);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index b1d205c..995b35f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -64,6 +64,7 @@
private boolean mMovedEnough;
private int mTouchSlopSquared;
private VelocityTracker mVelocityTracker;
+ private Runnable mShowBubbleMenuRunnable;
/** View that was initially touched, when we received the first ACTION_DOWN event. */
private View mTouchedView;
@@ -133,6 +134,11 @@
if (isStack) {
mViewPositionOnTouchDown.set(mStack.getStackPosition());
mStack.onDragStart();
+ if (!mStack.isShowingBubbleMenu() && !mStack.isExpanded()) {
+ mShowBubbleMenuRunnable = mStack::showBubbleMenu;
+ mStack.postDelayed(mShowBubbleMenuRunnable,
+ ViewConfiguration.getLongPressTimeout());
+ }
} else if (isFlyout) {
mStack.onFlyoutDragStart();
} else {
@@ -156,6 +162,7 @@
}
if (mMovedEnough) {
+ mStack.removeCallbacks(mShowBubbleMenuRunnable);
if (isStack) {
mStack.onDragged(viewX, viewY);
} else if (isFlyout) {
@@ -163,13 +170,6 @@
} else {
mStack.onBubbleDragged(mTouchedView, viewX, viewY);
}
- } else {
- float touchTime = event.getEventTime() - event.getDownTime();
- if (touchTime > ViewConfiguration.getLongPressTimeout() && !mStack.isExpanded()
- && BubbleExperimentConfig.allowBubbleScreenshotMenu(mContext)) {
- mStack.showBubbleMenu();
- return true;
- }
}
final boolean currentlyInDismissTarget = mStack.isInDismissTarget(event);
@@ -196,6 +196,8 @@
if (mStack.isShowingBubbleMenu()) {
resetForNextGesture();
return true;
+ } else {
+ mStack.removeCallbacks(mShowBubbleMenuRunnable);
}
trackMovement(event);
mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
new file mode 100644
index 0000000..41f5028
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -0,0 +1,182 @@
+/*
+ * 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.bubbles;
+
+import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
+import static com.android.systemui.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.util.PathParser;
+import android.view.LayoutInflater;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.systemui.R;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Simple task to inflate views & load necessary info to display a bubble.
+ */
+public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES;
+
+
+ /**
+ * Callback to find out when the bubble has been inflated & necessary data loaded.
+ */
+ public interface Callback {
+ /**
+ * Called when data has been loaded for the bubble.
+ */
+ void onBubbleViewsReady(Bubble bubble);
+ }
+
+ private Bubble mBubble;
+ private WeakReference<Context> mContext;
+ private WeakReference<BubbleStackView> mStackView;
+ private BubbleIconFactory mIconFactory;
+ private Callback mCallback;
+
+ /**
+ * Creates a task to load information for the provided {@link Bubble}. Once all info
+ * is loaded, {@link Callback} is notified.
+ */
+ BubbleViewInfoTask(Bubble b,
+ Context context,
+ BubbleStackView stackView,
+ BubbleIconFactory factory,
+ Callback c) {
+ mBubble = b;
+ mContext = new WeakReference<>(context);
+ mStackView = new WeakReference<>(stackView);
+ mIconFactory = factory;
+ mCallback = c;
+ }
+
+ @Override
+ protected BubbleViewInfo doInBackground(Void... voids) {
+ return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble);
+ }
+
+ @Override
+ protected void onPostExecute(BubbleViewInfo viewInfo) {
+ if (viewInfo != null) {
+ mBubble.setViewInfo(viewInfo);
+ if (mCallback != null && !isCancelled()) {
+ mCallback.onBubbleViewsReady(mBubble);
+ }
+ }
+ }
+
+ static class BubbleViewInfo {
+ BadgedImageView imageView;
+ BubbleExpandedView expandedView;
+ ShortcutInfo shortcutInfo;
+ String appName;
+ Bitmap badgedBubbleImage;
+ int dotColor;
+ Path dotPath;
+
+ @Nullable
+ static BubbleViewInfo populate(Context c, BubbleStackView stackView,
+ BubbleIconFactory iconFactory, Bubble b) {
+ BubbleViewInfo info = new BubbleViewInfo();
+
+ // View inflation: only should do this once per bubble
+ if (!b.isInflated()) {
+ LayoutInflater inflater = LayoutInflater.from(c);
+ info.imageView = (BadgedImageView) inflater.inflate(
+ R.layout.bubble_view, stackView, false /* attachToRoot */);
+
+ info.expandedView = (BubbleExpandedView) inflater.inflate(
+ R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
+ info.expandedView.setStackView(stackView);
+ }
+
+ StatusBarNotification sbn = b.getEntry().getSbn();
+ String packageName = sbn.getPackageName();
+
+ // Shortcut info for this bubble
+ String shortcutId = sbn.getNotification().getShortcutId();
+ if (BubbleExperimentConfig.useShortcutInfoToBubble(c)
+ && shortcutId != null) {
+ info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c,
+ packageName,
+ sbn.getUser(), shortcutId);
+ }
+
+ // App name & app icon
+ PackageManager pm = c.getPackageManager();
+ ApplicationInfo appInfo;
+ Drawable badgedIcon;
+ try {
+ appInfo = pm.getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (appInfo != null) {
+ info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
+ }
+ Drawable appIcon = pm.getApplicationIcon(packageName);
+ badgedIcon = pm.getUserBadgedIcon(appIcon, sbn.getUser());
+ } catch (PackageManager.NameNotFoundException exception) {
+ // If we can't find package... don't think we should show the bubble.
+ Log.w(TAG, "Unable to find package: " + packageName);
+ return null;
+ }
+
+ // Badged bubble image
+ Drawable bubbleDrawable = iconFactory.getBubbleDrawable(b, c);
+ BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
+ info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
+ badgeBitmapInfo).icon;
+
+ // Dot color & placement
+ Path iconPath = PathParser.createPathFromPathData(
+ c.getResources().getString(com.android.internal.R.string.config_icon_mask));
+ Matrix matrix = new Matrix();
+ float scale = iconFactory.getNormalizer().getScale(bubbleDrawable,
+ null /* outBounds */, null /* path */, null /* outMaskShape */);
+ float radius = DEFAULT_PATH_SIZE / 2f;
+ matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+ radius /* pivot y */);
+ iconPath.transform(matrix);
+ info.dotPath = iconPath;
+ info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
+ Color.WHITE, WHITE_SCRIM_ALPHA);
+ return info;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index 20742d6..eb014ed 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -37,8 +37,8 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dependency;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.analytics.DataCollector;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -46,6 +46,7 @@
import com.android.systemui.util.sensors.AsyncSensorManager;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
/**
* When the phone is locked, listens to touch, sensor and phone events and sends them to
@@ -77,7 +78,7 @@
private final DataCollector mDataCollector;
private final HumanInteractionClassifier mHumanInteractionClassifier;
private final AccessibilityManager mAccessibilityManager;
- private final UiOffloadThread mUiOffloadThread;
+ private final Executor mUiBgExecutor;
private boolean mEnforceBouncer = false;
private boolean mBouncerOn = false;
@@ -137,13 +138,13 @@
}
};
- FalsingManagerImpl(Context context) {
+ FalsingManagerImpl(Context context, @UiBackground Executor uiBgExecutor) {
mContext = context;
mSensorManager = Dependency.get(AsyncSensorManager.class);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDataCollector = DataCollector.getInstance(mContext);
mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
- mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+ mUiBgExecutor = uiBgExecutor;
mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
mMetricsLogger = new MetricsLogger();
@@ -196,7 +197,7 @@
}
// This can be expensive, and doesn't need to happen on the main thread.
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
mSensorManager.unregisterListener(mSensorEventListener);
});
}
@@ -237,7 +238,7 @@
if (s != null) {
// This can be expensive, and doesn't need to happen on the main thread.
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
mSensorManager.registerListener(
mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
});
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index db85fa0..f475948 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -30,7 +30,8 @@
import com.android.systemui.Dependency;
import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
import com.android.systemui.classifier.brightline.FalsingDataProvider;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingPlugin;
import com.android.systemui.plugins.PluginListener;
@@ -39,6 +40,7 @@
import com.android.systemui.util.sensors.ProximitySensor;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -58,13 +60,16 @@
private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener;
private final DeviceConfigProxy mDeviceConfig;
private boolean mBrightlineEnabled;
+ private Executor mUiBgExecutor;
@Inject
FalsingManagerProxy(Context context, PluginManager pluginManager,
- @MainHandler Handler handler,
+ @Main Handler handler,
ProximitySensor proximitySensor,
- DeviceConfigProxy deviceConfig) {
+ DeviceConfigProxy deviceConfig,
+ @UiBackground Executor uiBgExecutor) {
mProximitySensor = proximitySensor;
+ mUiBgExecutor = uiBgExecutor;
mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
mProximitySensor.setSensorDelay(SensorManager.SENSOR_DELAY_GAME);
mDeviceConfig = deviceConfig;
@@ -87,7 +92,7 @@
}
public void onPluginDisconnected(FalsingPlugin plugin) {
- mInternalFalsingManager = new FalsingManagerImpl(context);
+ mInternalFalsingManager = new FalsingManagerImpl(context, mUiBgExecutor);
}
};
@@ -117,7 +122,7 @@
mInternalFalsingManager.cleanup();
}
if (!brightlineEnabled) {
- mInternalFalsingManager = new FalsingManagerImpl(context);
+ mInternalFalsingManager = new FalsingManagerImpl(context, mUiBgExecutor);
} else {
mInternalFalsingManager = new BrightLineFalsingManager(
new FalsingDataProvider(context.getResources().getDisplayMetrics()),
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 4593164..df79310 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -22,8 +22,6 @@
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.settings.BrightnessDialog;
import com.android.systemui.tuner.TunerActivity;
-import com.android.systemui.usb.UsbDebuggingActivity;
-import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
import dagger.Binds;
import dagger.Module;
@@ -58,17 +56,4 @@
@IntoMap
@ClassKey(BrightnessDialog.class)
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
-
- /** Inject into UsbDebuggingActivity. */
- @Binds
- @IntoMap
- @ClassKey(UsbDebuggingActivity.class)
- public abstract Activity bindUsbDebuggingActivity(UsbDebuggingActivity activity);
-
- /** Inject into UsbDebuggingSecondaryUserActivity. */
- @Binds
- @IntoMap
- @ClassKey(UsbDebuggingSecondaryUserActivity.class)
- public abstract Activity bindUsbDebuggingSecondaryUserActivity(
- UsbDebuggingSecondaryUserActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 606605a..8d10552 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -20,12 +20,11 @@
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;
import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
import android.os.ServiceManager;
import android.util.DisplayMetrics;
import android.view.IWindowManager;
@@ -34,11 +33,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.BgHandler;
-import com.android.systemui.dagger.qualifiers.BgLooper;
-import com.android.systemui.dagger.qualifiers.MainHandler;
-import com.android.systemui.dagger.qualifiers.MainLooper;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.PluginInitializerImpl;
@@ -53,8 +49,6 @@
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.util.leak.LeakDetector;
@@ -84,35 +78,6 @@
return new Handler(thread.getLooper());
}
- @Singleton
- @Provides
- @BgLooper
- public Looper provideBgLooper() {
- HandlerThread thread = new HandlerThread("SysUiBg",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- return thread.getLooper();
- }
-
- /** Main Looper */
- @Provides
- @MainLooper
- public Looper provideMainLooper() {
- return Looper.getMainLooper();
- }
-
- @Provides
- @BgHandler
- public Handler provideBgHandler(@BgLooper Looper bgLooper) {
- return new Handler(bgLooper);
- }
-
- @Provides
- @MainHandler
- public Handler provideMainHandler(@MainLooper Looper mainLooper) {
- return new Handler(mainLooper);
- }
-
/** */
@Provides
public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
@@ -150,6 +115,13 @@
/** */
@Singleton
@Provides
+ public IPackageManager provideIPackageManager() {
+ return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ }
+
+ /** */
+ @Singleton
+ @Provides
public LayoutInflater providerLayoutInflater(Context context) {
return LayoutInflater.from(context);
}
@@ -170,7 +142,7 @@
@Singleton
@Provides
public NightDisplayListener provideNightDisplayListener(Context context,
- @BgHandler Handler bgHandler) {
+ @Background Handler bgHandler) {
return new NightDisplayListener(context, bgHandler);
}
@@ -183,7 +155,7 @@
@Singleton
@Provides
public NavigationBarController provideNavigationBarController(Context context,
- @MainHandler Handler mainHandler, CommandQueue commandQueue) {
+ @Main Handler mainHandler, CommandQueue commandQueue) {
return new NavigationBarController(context, mainHandler, commandQueue);
}
@@ -196,7 +168,7 @@
@Singleton
@Provides
public AutoHideController provideAutoHideController(Context context,
- @MainHandler Handler mainHandler,
+ @Main Handler mainHandler,
NotificationRemoteInputManager notificationRemoteInputManager,
IWindowManager iWindowManager) {
return new AutoHideController(context, mainHandler, notificationRemoteInputManager,
@@ -215,13 +187,6 @@
return DevicePolicyManagerWrapper.getInstance();
}
- @Singleton
- @Provides
- public DeviceProvisionedController provideDeviceProvisionedController(Context context,
- @MainHandler Handler mainHandler, BroadcastDispatcher broadcastDispatcher) {
- return new DeviceProvisionedControllerImpl(context, mainHandler, broadcastDispatcher);
- }
-
/** */
@Provides
public LockPatternUtils provideLockPatternUtils(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index f032a33..0b73ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -23,6 +23,7 @@
import android.app.IActivityManager;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
+import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
@@ -43,8 +44,8 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.systemui.dagger.qualifiers.BgHandler;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.PackageManagerWrapper;
import javax.inject.Singleton;
@@ -125,12 +126,18 @@
@Provides
@Nullable
static LocalBluetoothManager provideLocalBluetoothController(Context context,
- @BgHandler Handler bgHandler) {
+ @Background Handler bgHandler) {
return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
}
@Singleton
@Provides
+ static NotificationManager provideNotificationManager(Context context) {
+ return context.getSystemService(NotificationManager.class);
+ }
+
+ @Singleton
+ @Provides
static PackageManagerWrapper providePackageManagerWrapper() {
return PackageManagerWrapper.getInstance();
}
@@ -143,7 +150,7 @@
}
@Provides
- @MainResources
+ @Main
static Resources provideResources(Context context) {
return context.getResources();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 3cf14d6..99dd5e2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -21,6 +21,7 @@
import com.android.systemui.SizeCompatModeActivityController;
import com.android.systemui.SliceBroadcastRelayHandler;
import com.android.systemui.SystemUI;
+import com.android.systemui.accessibility.WindowMagnification;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -156,4 +157,10 @@
@IntoMap
@ClassKey(VolumeUI.class)
public abstract SystemUI bindVolumeUI(VolumeUI sysui);
+
+ /** Inject into WindowMagnification. */
+ @Binds
+ @IntoMap
+ @ClassKey(WindowMagnification.class)
+ public abstract SystemUI bindWindowMagnification(WindowMagnification sysui);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index f44eae7..5fc789c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -39,7 +39,9 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.ShadeControllerImpl;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.Optional;
@@ -82,7 +84,7 @@
KeyguardEnvironmentImpl keyguardEnvironment);
@Binds
- abstract ShadeController provideShadeController(StatusBar statusBar);
+ abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
@Singleton
@Provides
@@ -114,4 +116,8 @@
CommandQueue commandQueue) {
return new Recents(context, recentsImplementation, commandQueue);
}
+
+ @Binds
+ abstract DeviceProvisionedController bindDeviceProvisionedController(
+ DeviceProvisionedControllerImpl deviceProvisionedController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java
deleted file mode 100644
index bc6b83b..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java
+++ /dev/null
@@ -1,30 +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.dagger.qualifiers;
-
-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 BgHandler {
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java
deleted file mode 100644
index 2aadda1..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java
+++ /dev/null
@@ -1,30 +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.dagger.qualifiers;
-
-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 BgLooper {
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java
deleted file mode 100644
index 79661fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java
+++ /dev/null
@@ -1,30 +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.dagger.qualifiers;
-
-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 MainHandler {
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java
deleted file mode 100644
index 3daeda5..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java
+++ /dev/null
@@ -1,31 +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.dagger.qualifiers;
-
-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 MainResources {
- // TODO: use attribute to get other, non-main resources?
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/UiBackground.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java
rename to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/UiBackground.java
index 750d7d7..bf2237a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/UiBackground.java
@@ -23,8 +23,12 @@
import javax.inject.Qualifier;
+
+/**
+ * An annotation for injecting instances related to UI operations off the main-thread.
+ */
@Qualifier
@Documented
@Retention(RUNTIME)
-public @interface MainLooper {
+public @interface UiBackground {
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java b/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java
index ea1def0..d2fe394 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeEvent.java
@@ -28,10 +28,12 @@
* and triaging purposes.
*/
public class DozeEvent extends RichEvent {
- public static final int TOTAL_EVENT_TYPES = 19;
-
- public DozeEvent(int logLevel, int type, String reason) {
- super(logLevel, type, reason);
+ /**
+ * Initializes a doze event
+ */
+ public DozeEvent init(@EventType int type, String reason) {
+ super.init(DEBUG, type, reason);
+ return this;
}
/**
@@ -89,21 +91,6 @@
}
}
- /**
- * Builds a DozeEvent.
- */
- public static class DozeEventBuilder extends RichEvent.Builder<DozeEventBuilder> {
- @Override
- public DozeEventBuilder getBuilder() {
- return this;
- }
-
- @Override
- public RichEvent build() {
- return new DozeEvent(mLogLevel, mType, mReason);
- }
- }
-
@IntDef({PICKUP_WAKEUP, PULSE_START, PULSE_FINISH, NOTIFICATION_PULSE, DOZING, FLING,
EMERGENCY_CALL, KEYGUARD_BOUNCER_CHANGED, SCREEN_ON, SCREEN_OFF, MISSED_TICK,
TIME_TICK_SCHEDULED, KEYGUARD_VISIBILITY_CHANGE, DOZE_STATE_CHANGED, WAKE_DISPLAY,
@@ -132,6 +119,7 @@
public static final int PULSE_DROPPED = 16;
public static final int PULSE_DISABLED_BY_PROX = 17;
public static final int SENSOR_TRIGGERED = 18;
+ public static final int TOTAL_EVENT_TYPES = 19;
public static final int TOTAL_REASONS = 10;
@IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 2e4466d..fe50421 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -35,9 +35,11 @@
* dependency DumpController DozeLog
*/
@Singleton
-public class DozeLog extends SysuiLog {
+public class DozeLog extends SysuiLog<DozeEvent> {
private static final String TAG = "DozeLog";
+ private DozeEvent mRecycledEvent;
+
private boolean mPulsing;
private long mSince;
private SummaryStats mPickupPulseNearVibrationStats;
@@ -73,8 +75,8 @@
* Appends pickup wakeup event to the logs
*/
public void tracePickupWakeUp(boolean withinVibrationThreshold) {
- if (log(DozeEvent.PICKUP_WAKEUP,
- "withinVibrationThreshold=" + withinVibrationThreshold)) {
+ log(DozeEvent.PICKUP_WAKEUP, "withinVibrationThreshold=" + withinVibrationThreshold);
+ if (mEnabled) {
(withinVibrationThreshold ? mPickupPulseNearVibrationStats
: mPickupPulseNotNearVibrationStats).append();
}
@@ -85,27 +87,24 @@
* @param reason why the pulse started
*/
public void tracePulseStart(@DozeEvent.Reason int reason) {
- if (log(DozeEvent.PULSE_START, DozeEvent.reasonToString(reason))) {
- mPulsing = true;
- }
+ log(DozeEvent.PULSE_START, DozeEvent.reasonToString(reason));
+ if (mEnabled) mPulsing = true;
}
/**
* Appends pulse finished event to the logs
*/
public void tracePulseFinish() {
- if (log(DozeEvent.PULSE_FINISH)) {
- mPulsing = false;
- }
+ log(DozeEvent.PULSE_FINISH);
+ if (mEnabled) mPulsing = false;
}
/**
* Appends pulse event to the logs
*/
public void traceNotificationPulse() {
- if (log(DozeEvent.NOTIFICATION_PULSE)) {
- mNotificationPulseStats.append();
- }
+ log(DozeEvent.NOTIFICATION_PULSE);
+ if (mEnabled) mNotificationPulseStats.append();
}
/**
@@ -113,9 +112,8 @@
* @param dozing true if dozing, else false
*/
public void traceDozing(boolean dozing) {
- if (log(DozeEvent.DOZING, "dozing=" + dozing)) {
- mPulsing = false;
- }
+ log(DozeEvent.DOZING, "dozing=" + dozing);
+ if (mEnabled) mPulsing = false;
}
/**
@@ -133,9 +131,8 @@
* Appends emergency call event to the logs
*/
public void traceEmergencyCall() {
- if (log(DozeEvent.EMERGENCY_CALL)) {
- mEmergencyCallStats.append();
- }
+ log(DozeEvent.EMERGENCY_CALL);
+ if (mEnabled) mEmergencyCallStats.append();
}
/**
@@ -150,7 +147,8 @@
* Appends screen-on event to the logs
*/
public void traceScreenOn() {
- if (log(DozeEvent.SCREEN_ON, "pulsing=" + mPulsing)) {
+ log(DozeEvent.SCREEN_ON, "pulsing=" + mPulsing);
+ if (mEnabled) {
(mPulsing ? mScreenOnPulsingStats : mScreenOnNotPulsingStats).append();
mPulsing = false;
}
@@ -188,10 +186,8 @@
* @param showing whether the keyguard is now showing
*/
public void traceKeyguard(boolean showing) {
- if (log(DozeEvent.KEYGUARD_VISIBILITY_CHANGE, "showing=" + showing)
- && !showing) {
- mPulsing = false;
- }
+ log(DozeEvent.KEYGUARD_VISIBILITY_CHANGE, "showing=" + showing);
+ if (mEnabled && !showing) mPulsing = false;
}
/**
@@ -217,12 +213,11 @@
* @param reason why proximity result was triggered
*/
public void traceProximityResult(boolean near, long millis, @DozeEvent.Reason int reason) {
- if (log(DozeEvent.PROXIMITY_RESULT,
+ log(DozeEvent.PROXIMITY_RESULT,
" reason=" + DozeEvent.reasonToString(reason)
- + " near=" + near
- + " millis=" + millis)) {
- mProxStats[reason][near ? 0 : 1].append();
- }
+ + " near=" + near
+ + " millis=" + millis);
+ if (mEnabled) mProxStats[reason][near ? 0 : 1].append();
}
/**
@@ -250,15 +245,16 @@
}
}
- private boolean log(@DozeEvent.EventType int eventType) {
- return log(eventType, "");
+ private void log(@DozeEvent.EventType int eventType) {
+ log(eventType, "");
}
- private boolean log(@DozeEvent.EventType int eventType, String msg) {
- return super.log(new DozeEvent.DozeEventBuilder()
- .setType(eventType)
- .setReason(msg)
- .build());
+ private void log(@DozeEvent.EventType int eventType, String msg) {
+ if (mRecycledEvent != null) {
+ mRecycledEvent = log(mRecycledEvent.init(eventType, msg));
+ } else {
+ mRecycledEvent = log(new DozeEvent().init(eventType, msg));
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 05a234f..d008e66 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -80,8 +80,7 @@
public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
- Callback callback, Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy,
- DozeLog dozeLog) {
+ Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) {
mContext = context;
mAlarmManager = alarmManager;
mSensorManager = sensorManager;
@@ -154,7 +153,7 @@
};
mProximitySensor = new ProximitySensor(context.getResources(), sensorManager);
-
+ setProxListening(false); // Don't immediately start listening when we register.
mProximitySensor.register(
proximityEvent -> {
if (proximityEvent != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 2d6b946..722dc03 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -103,8 +103,7 @@
mWakeLock = wakeLock;
mAllowPulseTriggers = allowPulseTriggers;
mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
- config, wakeLock, this::onSensor, this::onProximityFar,
- dozeParameters.getPolicy(), dozeLog);
+ config, wakeLock, this::onSensor, this::onProximityFar, dozeLog);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 7f1b356..7aeb785 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -16,6 +16,7 @@
package com.android.systemui.doze;
+import android.annotation.Nullable;
import android.app.IWallpaperManager;
import android.os.RemoteException;
import android.util.Log;
@@ -34,6 +35,7 @@
private static final String TAG = "DozeWallpaperState";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ @Nullable
private final IWallpaperManager mWallpaperManagerService;
private final DozeParameters mDozeParameters;
private final BiometricUnlockController mBiometricUnlockController;
@@ -79,16 +81,18 @@
if (isAmbientMode != mIsAmbientMode) {
mIsAmbientMode = isAmbientMode;
- try {
- long duration = animated ? StackStateAnimator.ANIMATION_DURATION_WAKEUP : 0L;
- if (DEBUG) {
- Log.i(TAG, "AOD wallpaper state changed to: " + mIsAmbientMode
+ if (mWallpaperManagerService != null) {
+ try {
+ long duration = animated ? StackStateAnimator.ANIMATION_DURATION_WAKEUP : 0L;
+ if (DEBUG) {
+ Log.i(TAG, "AOD wallpaper state changed to: " + mIsAmbientMode
+ ", animationDuration: " + duration);
+ }
+ mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, duration);
+ } catch (RemoteException e) {
+ // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
+ Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
}
- mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, duration);
- } catch (RemoteException e) {
- // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
- Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
}
}
}
@@ -97,5 +101,6 @@
public void dump(PrintWriter pw) {
pw.println("DozeWallpaperState:");
pw.println(" isAmbientMode: " + mIsAmbientMode);
+ pw.println(" hasWallpaperService: " + (mWallpaperManagerService != null));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 63a7771..3ccad64 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -81,7 +81,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ScreenRecordHelper;
import com.android.internal.util.ScreenshotHelper;
@@ -190,7 +189,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
ConnectivityManager cm = (ConnectivityManager)
@@ -314,7 +313,7 @@
mIsWaitingForEcmExit = true;
// Launch ECM exit dialog
Intent ecmDialogIntent =
- new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+ new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(ecmDialogIntent);
} else {
@@ -1420,7 +1419,7 @@
if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason));
}
- } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
+ } 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)) &&
@@ -1562,6 +1561,7 @@
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ window.setFitWindowInsetsTypes(0 /* types */);
setTitle(R.string.global_actions);
mPanelController = plugin;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 53053fc..d385123 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -120,6 +120,7 @@
window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ window.setFitWindowInsetsTypes(0 /* types */);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
index c6812a7..4b28540 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
@@ -51,40 +51,65 @@
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLUtils;
+import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
/**
* A helper class to handle EGL management.
*/
public class EglHelper {
private static final String TAG = EglHelper.class.getSimpleName();
+ private static final int OPENGLES_VERSION = 2;
// Below two constants make drawing at low priority, so other things can preempt our drawing.
private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100;
private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
private static final boolean DEBUG = true;
+
+ private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
+ private static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
+
private static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority";
+ /**
+ * https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
+ */
+ private static final String KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
+
+ /**
+ * https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3_passthrough.txt
+ */
+ private static final String EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH =
+ "EGL_EXT_gl_colorspace_display_p3_passthrough";
+
private EGLDisplay mEglDisplay;
private EGLConfig mEglConfig;
private EGLContext mEglContext;
private EGLSurface mEglSurface;
private final int[] mEglVersion = new int[2];
private boolean mEglReady;
- private boolean mContextPrioritySupported;
+ private final Set<String> mExts;
+
+ public EglHelper() {
+ mExts = new HashSet<>();
+ connectDisplay();
+ }
/**
- * Initialize EGL and prepare EglSurface.
+ * Initialize render context.
* @param surfaceHolder surface holder.
- * @return true if EglSurface is ready.
+ * @param wideColorGamut claim if a wcg surface is necessary.
+ * @return true if the render context is ready.
*/
- public boolean init(SurfaceHolder surfaceHolder) {
- mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (mEglDisplay == EGL_NO_DISPLAY) {
- Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
+ public boolean init(SurfaceHolder surfaceHolder, boolean wideColorGamut) {
+ if (!hasEglDisplay() && !connectDisplay()) {
+ Log.w(TAG, "Can not connect display, abort!");
return false;
}
@@ -105,25 +130,38 @@
return false;
}
- if (!createEglSurface(surfaceHolder)) {
+ if (!createEglSurface(surfaceHolder, wideColorGamut)) {
Log.w(TAG, "Can't create EGLSurface!");
return false;
}
- mContextPrioritySupported = isContextPrioritySuppported();
-
mEglReady = true;
return true;
}
- private boolean isContextPrioritySuppported() {
- String[] extensions = eglQueryString(mEglDisplay, EGL_EXTENSIONS).split(" ");
- for (String extension : extensions) {
- if (extension.equals(EGL_IMG_CONTEXT_PRIORITY)) {
- return true;
- }
+ private boolean connectDisplay() {
+ mExts.clear();
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (!hasEglDisplay()) {
+ Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
+ return false;
}
- return false;
+ String queryString = eglQueryString(mEglDisplay, EGL_EXTENSIONS);
+ if (!TextUtils.isEmpty(queryString)) {
+ Collections.addAll(mExts, queryString.split(" "));
+ }
+ return true;
+ }
+
+ private boolean checkExtensionCapability(String extName) {
+ return mExts.contains(extName);
+ }
+
+ private int getWcgCapability() {
+ if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) {
+ return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+ }
+ return 0;
}
private EGLConfig chooseEglConfig() {
@@ -148,7 +186,7 @@
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
+ EGL_ALPHA_SIZE, 0,
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
@@ -160,21 +198,27 @@
/**
* Prepare an EglSurface.
* @param surfaceHolder surface holder.
+ * @param wcg if need to support wcg.
* @return true if EglSurface is ready.
*/
- public boolean createEglSurface(SurfaceHolder surfaceHolder) {
+ public boolean createEglSurface(SurfaceHolder surfaceHolder, boolean wcg) {
if (DEBUG) {
Log.d(TAG, "createEglSurface start");
}
if (hasEglDisplay()) {
- mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0);
+ int[] attrs = null;
+ int wcgCapability = getWcgCapability();
+ if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) {
+ attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE};
+ }
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, attrs, 0);
} else {
Log.w(TAG, "mEglDisplay is null");
return false;
}
- if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
+ if (!hasEglSurface()) {
Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
@@ -221,12 +265,12 @@
int[] attrib_list = new int[5];
int idx = 0;
attrib_list[idx++] = EGL_CONTEXT_CLIENT_VERSION;
- attrib_list[idx++] = 2;
- if (mContextPrioritySupported) {
+ attrib_list[idx++] = OPENGLES_VERSION;
+ if (checkExtensionCapability(EGL_IMG_CONTEXT_PRIORITY)) {
attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LOW_IMG;
}
- attrib_list[idx++] = EGL_NONE;
+ attrib_list[idx] = EGL_NONE;
if (hasEglDisplay()) {
mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
} else {
@@ -234,7 +278,7 @@
return false;
}
- if (mEglContext == EGL_NO_CONTEXT) {
+ if (!hasEglContext()) {
Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
@@ -260,7 +304,7 @@
* @return true if EglContext is ready.
*/
public boolean hasEglContext() {
- return mEglContext != null;
+ return mEglContext != null && mEglContext != EGL_NO_CONTEXT;
}
/**
@@ -268,7 +312,7 @@
* @return true if EglDisplay is ready.
*/
public boolean hasEglDisplay() {
- return mEglDisplay != null;
+ return mEglDisplay != null && mEglDisplay != EGL_NO_DISPLAY;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
index 60ea1cdf..88ab9ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
@@ -27,6 +27,11 @@
public interface GLWallpaperRenderer {
/**
+ * Check if the content to render is a WCG content.
+ */
+ boolean isWcgContent();
+
+ /**
* Called when the surface is created or recreated.
*/
void onSurfaceCreated();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
index 24a4b9e..54eca0e 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
@@ -116,7 +116,8 @@
int width = bitmap.getWidth();
int height = bitmap.getHeight();
- Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig());
+ Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig(),
+ false /* hasAlpha */, bitmap.getColorSpace());
Canvas canvas = new Canvas(grayscale);
ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX);
Paint paint = new Paint();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index a8371e3..fa8269d 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -62,6 +62,7 @@
private boolean mScissorMode;
private float mXOffset;
private float mYOffset;
+ private boolean mWcgContent;
public ImageWallpaperRenderer(Context context, SurfaceProxy proxy) {
mWallpaperManager = context.getSystemService(WallpaperManager.class);
@@ -94,6 +95,11 @@
}
@Override
+ public boolean isWcgContent() {
+ return mWcgContent;
+ }
+
+ @Override
public void onSurfaceCreated() {
glClearColor(0f, 0f, 0f, 1.0f);
mProgram.useGLProgram(
@@ -112,7 +118,8 @@
Log.d(TAG, "loadBitmap: mBitmap=" + mBitmap);
}
if (mWallpaperManager != null && mBitmap == null) {
- mBitmap = mWallpaperManager.getBitmap();
+ mBitmap = mWallpaperManager.getBitmap(false /* hardware */);
+ mWcgContent = mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM);
mWallpaperManager.forgetLoadedWallpaper();
if (mBitmap != null) {
float scale = (float) mScissor.height() / mBitmap.getHeight();
@@ -231,6 +238,7 @@
out.print(prefix); out.print("mYOffset="); out.print(mYOffset);
out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold());
out.print(prefix); out.print("mReveal="); out.print(mImageRevealHelper.getReveal());
+ out.print(prefix); out.print("mWcgContent="); out.print(mWcgContent);
mWallpaper.dump(prefix, fd, out, args);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
index 25ac8f8..f6f3b99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
@@ -11,16 +11,16 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.systemui.keyguard;
import com.android.internal.policy.IKeyguardDismissCallback;
-import com.android.systemui.Dependency;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -32,10 +32,12 @@
public class DismissCallbackRegistry {
private final ArrayList<DismissCallbackWrapper> mDismissCallbacks = new ArrayList<>();
- private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+ private final Executor mUiBgExecutor;
@Inject
- public DismissCallbackRegistry() {}
+ public DismissCallbackRegistry(@UiBackground Executor uiBgExecutor) {
+ mUiBgExecutor = uiBgExecutor;
+ }
public void addCallback(IKeyguardDismissCallback callback) {
mDismissCallbacks.add(new DismissCallbackWrapper(callback));
@@ -44,7 +46,7 @@
public void notifyDismissCancelled() {
for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
DismissCallbackWrapper callback = mDismissCallbacks.get(i);
- mUiOffloadThread.submit(callback::notifyDismissCancelled);
+ mUiBgExecutor.execute(callback::notifyDismissCancelled);
}
mDismissCallbacks.clear();
}
@@ -52,7 +54,7 @@
public void notifyDismissSucceeded() {
for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
DismissCallbackWrapper callback = mDismissCallbacks.get(i);
- mUiOffloadThread.submit(callback::notifyDismissSucceeded);
+ mUiBgExecutor.execute(callback::notifyDismissSucceeded);
}
mDismissCallbacks.clear();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8d08b28..9fcf022 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -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 com.android.systemui.keyguard;
@@ -81,8 +81,8 @@
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -95,6 +95,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -210,9 +211,8 @@
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
- private final StatusBarWindowController mStatusBarWindowController =
- Dependency.get(StatusBarWindowController.class);
- private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+ private final StatusBarWindowController mStatusBarWindowController;
+ private final Executor mUiBgExecutor;
private boolean mSystemReady;
private boolean mBootCompleted;
@@ -688,14 +688,18 @@
FalsingManager falsingManager,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
+ StatusBarWindowController statusBarWindowController,
Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManagerLazy,
- DismissCallbackRegistry dismissCallbackRegistry) {
+ DismissCallbackRegistry dismissCallbackRegistry,
+ @UiBackground Executor uiBgExecutor) {
super(context);
mFalsingManager = falsingManager;
mLockPatternUtils = lockPatternUtils;
mBroadcastDispatcher = broadcastDispatcher;
+ mStatusBarWindowController = statusBarWindowController;
mStatusBarKeyguardViewManagerLazy = statusBarKeyguardViewManagerLazy;
mDismissCallbackRegistry = dismissCallbackRegistry;
+ mUiBgExecutor = uiBgExecutor;
}
public void userActivity() {
@@ -1661,7 +1665,7 @@
private void handleKeyguardDone() {
Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
if (mLockPatternUtils.isSecure(currentUser)) {
mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
}
@@ -1704,7 +1708,7 @@
final UserHandle currentUser = new UserHandle(currentUserId);
final UserManager um = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) {
mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId));
}
@@ -1755,7 +1759,7 @@
mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
}
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
// If the stream is muted, don't play the sound
if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
@@ -1774,7 +1778,7 @@
}
private void updateActivityLockScreenState(boolean showing, boolean aodShowing) {
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
if (DEBUG) {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
}
@@ -1853,7 +1857,7 @@
// Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
// order.
final int keyguardFlag = flags;
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
try {
ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
} catch (RemoteException e) {
@@ -2216,7 +2220,7 @@
}
});
updateInputRestrictedLocked();
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
mTrustManager.reportKeyguardShowingChanged();
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index ca04633..24ad75d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -52,12 +52,6 @@
private static final String TAG = "WorkLockActivity";
/**
- * Add additional extra {@link com.android.settings.password.ConfirmDeviceCredentialActivity} to
- * enable device policy management enforcement from systemui.
- */
- public static final String EXTRA_FROM_WORK_LOCK_ACTIVITY = "from_work_lock_activity";
-
- /**
* Contains a {@link TaskDescription} for the activity being covered.
*/
static final String EXTRA_TASK_DESCRIPTION =
@@ -156,7 +150,8 @@
}
final Intent credential = getKeyguardManager()
- .createConfirmDeviceCredentialIntent(null, null, getTargetUserId());
+ .createConfirmDeviceCredentialIntent(null, null, getTargetUserId(),
+ true /* disallowBiometricsIfPolicyExists */);
if (credential == null) {
return;
}
@@ -172,7 +167,6 @@
if (target != null) {
credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
- credential.putExtra(EXTRA_FROM_WORK_LOCK_ACTIVITY, true);
}
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
diff --git a/packages/SystemUI/src/com/android/systemui/log/Event.java b/packages/SystemUI/src/com/android/systemui/log/Event.java
index 92862a2..7bc1abf 100644
--- a/packages/SystemUI/src/com/android/systemui/log/Event.java
+++ b/packages/SystemUI/src/com/android/systemui/log/Event.java
@@ -37,20 +37,28 @@
public static final int INFO = 4;
public static final int WARN = 5;
public static final int ERROR = 6;
+ public static final @Level int DEFAULT_LOG_LEVEL = DEBUG;
private long mTimestamp;
- private @Level int mLogLevel = DEBUG;
- protected String mMessage;
+ private @Level int mLogLevel = DEFAULT_LOG_LEVEL;
+ private String mMessage = "";
- public Event(String message) {
- mTimestamp = System.currentTimeMillis();
- mMessage = message;
+ /**
+ * initialize an event with a message
+ */
+ public Event init(String message) {
+ init(DEFAULT_LOG_LEVEL, message);
+ return this;
}
- public Event(@Level int logLevel, String message) {
+ /**
+ * initialize an event with a logLevel and message
+ */
+ public Event init(@Level int logLevel, String message) {
mTimestamp = System.currentTimeMillis();
mLogLevel = logLevel;
mMessage = message;
+ return this;
}
public String getMessage() {
@@ -64,4 +72,13 @@
public @Level int getLogLevel() {
return mLogLevel;
}
+
+ /**
+ * Recycle this event
+ */
+ void recycle() {
+ mTimestamp = -1;
+ mLogLevel = DEFAULT_LOG_LEVEL;
+ mMessage = "";
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
index acf761ed..470f2b0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
@@ -23,23 +23,21 @@
* Events are stored in {@link SysuiLog} and can be printed in a dumpsys.
*/
public abstract class RichEvent extends Event {
- private final int mType;
- private final String mReason;
+ private int mType;
/**
- * Create a rich event that includes an event type that matches with an index in the array
+ * Initializes a rich event that includes an event type that matches with an index in the array
* getEventLabels().
*/
- public RichEvent(@Event.Level int logLevel, int type, String reason) {
- super(logLevel, null);
+ public RichEvent init(@Event.Level int logLevel, int type, String reason) {
final int numEvents = getEventLabels().length;
if (type < 0 || type >= numEvents) {
throw new IllegalArgumentException("Unsupported event type. Events only supported"
+ " from 0 to " + (numEvents - 1) + ", but given type=" + type);
}
mType = type;
- mReason = reason;
- mMessage = getEventLabels()[mType] + " " + mReason;
+ super.init(logLevel, getEventLabels()[mType] + " " + reason);
+ return this;
}
/**
@@ -49,25 +47,43 @@
*/
public abstract String[] getEventLabels();
- public int getType() {
- return mType;
+ @Override
+ public void recycle() {
+ super.recycle();
+ mType = -1;
}
- public String getReason() {
- return mReason;
+ public int getType() {
+ return mType;
}
/**
* Builder to build a RichEvent.
* @param <B> Log specific builder that is extending this builder
+ * @param <E> Type of event we'll be building
*/
- public abstract static class Builder<B extends Builder<B>> {
+ public abstract static class Builder<B extends Builder<B, E>, E extends RichEvent> {
public static final int UNINITIALIZED = -1;
+ public final SysuiLog mLog;
private B mBuilder = getBuilder();
- protected int mType = UNINITIALIZED;
+ protected int mType;
protected String mReason;
- protected @Level int mLogLevel = VERBOSE;
+ protected @Level int mLogLevel;
+
+ public Builder(SysuiLog sysuiLog) {
+ mLog = sysuiLog;
+ reset();
+ }
+
+ /**
+ * Reset this builder's parameters so it can be reused to build another RichEvent.
+ */
+ public void reset() {
+ mType = UNINITIALIZED;
+ mReason = null;
+ mLogLevel = VERBOSE;
+ }
/**
* Get the log-specific builder.
@@ -75,9 +91,9 @@
public abstract B getBuilder();
/**
- * Build the log-specific event.
+ * Build the log-specific event given an event to populate.
*/
- public abstract RichEvent build();
+ public abstract E build(E e);
/**
* Optional - set the log level. Defaults to DEBUG.
diff --git a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
index f094cb9..4e15668 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
@@ -20,6 +20,7 @@
import android.os.SystemProperties;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
@@ -39,22 +40,26 @@
* To manually view the logs via adb:
* adb shell dumpsys activity service com.android.systemui/.SystemUIService \
* dependency DumpController <SysuiLogId>
+ *
+ * Logs can be disabled by setting the following SystemProperty and then restarting the device:
+ * adb shell setprop persist.sysui.log.enabled.<id> true/false && adb reboot
+ *
+ * @param <E> Type of event we'll be logging
*/
-public class SysuiLog implements Dumpable {
+public class SysuiLog<E extends Event> implements Dumpable {
public static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
- private final Object mDataLock = new Object();
+ protected final Object mDataLock = new Object();
private final String mId;
private final int mMaxLogs;
- private boolean mEnabled;
+ protected boolean mEnabled;
+ protected boolean mLogToLogcatEnabled;
- @VisibleForTesting protected ArrayDeque<Event> mTimeline;
+ @VisibleForTesting protected ArrayDeque<E> mTimeline;
/**
* Creates a SysuiLog
- * To enable or disable logs, set the system property and then restart the device:
- * adb shell setprop sysui.log.enabled.<id> true/false && adb reboot
* @param dumpController where to register this logger's dumpsys
* @param id user-readable tag for this logger
* @param maxDebugLogs maximum number of logs to retain when {@link sDebuggable} is true
@@ -62,41 +67,42 @@
*/
public SysuiLog(DumpController dumpController, String id, int maxDebugLogs, int maxLogs) {
this(dumpController, id, sDebuggable ? maxDebugLogs : maxLogs,
- SystemProperties.getBoolean(SYSPROP_ENABLED_PREFIX + id, DEFAULT_ENABLED));
+ SystemProperties.getBoolean(SYSPROP_ENABLED_PREFIX + id, DEFAULT_ENABLED),
+ SystemProperties.getBoolean(SYSPROP_LOGCAT_ENABLED_PREFIX + id,
+ DEFAULT_LOGCAT_ENABLED));
}
@VisibleForTesting
- protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled) {
+ protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled,
+ boolean logcatEnabled) {
mId = id;
mMaxLogs = maxLogs;
mEnabled = enabled;
+ mLogToLogcatEnabled = logcatEnabled;
mTimeline = mEnabled ? new ArrayDeque<>(mMaxLogs) : null;
dumpController.registerDumpable(mId, this);
}
- public SysuiLog(DumpController dumpController, String id) {
- this(dumpController, id, DEFAULT_MAX_DEBUG_LOGS, DEFAULT_MAX_LOGS);
- }
-
/**
* Logs an event to the timeline which can be printed by the dumpsys.
* May also log to logcat if enabled.
- * @return true if event was logged, else false
+ * @return the last event that was discarded from the Timeline (can be recycled)
*/
- public boolean log(Event event) {
+ public E log(E event) {
if (!mEnabled) {
- return false;
+ return null;
}
+ E recycledEvent = null;
synchronized (mDataLock) {
if (mTimeline.size() >= mMaxLogs) {
- mTimeline.removeFirst();
+ recycledEvent = mTimeline.removeFirst();
}
mTimeline.add(event);
}
- if (LOG_TO_LOGCAT_ENABLED) {
+ if (mLogToLogcatEnabled) {
final String strEvent = eventToString(event);
switch (event.getLogLevel()) {
case Event.VERBOSE:
@@ -116,13 +122,18 @@
break;
}
}
- return true;
+
+ if (recycledEvent != null) {
+ recycledEvent.recycle();
+ }
+
+ return recycledEvent;
}
/**
* @return user-readable string of the given event with timestamp
*/
- public String eventToTimestampedString(Event event) {
+ private String eventToTimestampedString(Event event) {
StringBuilder sb = new StringBuilder();
sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp()));
sb.append(" ");
@@ -137,9 +148,7 @@
return event.getMessage();
}
- /**
- * only call on this method if you have the mDataLock
- */
+ @GuardedBy("mDataLock")
private void dumpTimelineLocked(PrintWriter pw) {
pw.println("\tTimeline:");
@@ -162,9 +171,10 @@
}
private static boolean sDebuggable = Build.IS_DEBUGGABLE;
- private static final String SYSPROP_ENABLED_PREFIX = "sysui.log.enabled.";
- private static final boolean LOG_TO_LOGCAT_ENABLED = sDebuggable;
+ private static final String SYSPROP_ENABLED_PREFIX = "persist.sysui.log.enabled.";
+ private static final String SYSPROP_LOGCAT_ENABLED_PREFIX = "persist.sysui.log.enabled.logcat.";
private static final boolean DEFAULT_ENABLED = sDebuggable;
+ private static final boolean DEFAULT_LOGCAT_ENABLED = false;
private static final int DEFAULT_MAX_DEBUG_LOGS = 100;
private static final int DEFAULT_MAX_LOGS = 50;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index f784293..66c51d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -98,8 +98,10 @@
paint.setTextSize(42);
CharSequence dialogText = null;
+ CharSequence dialogTitle = null;
if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
dialogText = getString(R.string.media_projection_dialog_service_text);
+ dialogTitle = getString(R.string.media_projection_dialog_service_title);
} else {
String label = aInfo.loadLabel(packageManager).toString();
@@ -138,10 +140,9 @@
appNameIndex, appNameIndex + appName.length(), 0);
}
dialogText = message;
+ dialogTitle = getString(R.string.media_projection_dialog_title, appName);
}
- String dialogTitle = getString(R.string.media_projection_dialog_title);
-
View dialogTitleView = View.inflate(this, R.layout.media_projection_dialog_title, null);
TextView titleText = (TextView) dialogTitleView.findViewById(R.id.dialog_title);
titleText.setText(dialogTitle);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
index 75e260e..d1d9b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
@@ -20,11 +20,13 @@
import android.content.res.Configuration;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.wm.DisplayWindowController;
import java.io.PrintWriter;
public interface BasePipManager {
- void initialize(Context context, BroadcastDispatcher broadcastDispatcher);
+ void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
+ DisplayWindowController displayWindowController);
void showPictureInPictureMenu();
default void expandPip() {}
default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index f10274a..8e34a90 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -16,8 +16,14 @@
package com.android.systemui.pip;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
@@ -32,10 +38,9 @@
import android.view.Gravity;
import android.view.IPinnedStackController;
import android.view.IWindowManager;
+import android.view.WindowContainerTransaction;
import android.view.WindowManagerGlobal;
-import com.android.internal.policy.PipSnapAlgorithm;
-
import java.io.PrintWriter;
/**
@@ -154,6 +159,7 @@
*/
public void onMinimizedStateChanged(boolean minimized) {
mIsMinimized = minimized;
+ mSnapAlgorithm.setMinimized(minimized);
}
/**
@@ -199,6 +205,7 @@
mReentrySnapFraction = INVALID_SNAP_FRACTION;
mReentrySize = null;
mLastPipComponentName = null;
+ mLastDestinationBounds.setEmpty();
}
public Rect getLastDestinationBounds() {
@@ -235,8 +242,9 @@
*/
public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
final Rect destinationBounds;
+ final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
if (bounds == null) {
- destinationBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
+ destinationBounds = new Rect(defaultBounds);
} else {
destinationBounds = new Rect(bounds);
}
@@ -253,12 +261,85 @@
mPinnedStackController.startAnimation(destinationBounds, sourceRectHint,
-1 /* animationDuration */);
mLastDestinationBounds.set(destinationBounds);
+ mPinnedStackController.reportBounds(defaultBounds,
+ getMovementBounds(defaultBounds));
} catch (RemoteException e) {
Log.e(TAG, "Failed to start PiP animation from SysUI", e);
}
}
/**
+ * Updates the display info, calculating and returning the new stack and movement bounds in the
+ * new orientation of the device if necessary.
+ *
+ * @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise.
+ */
+ public boolean onDisplayRotationChanged(Rect outBounds, int displayId, int fromRotation,
+ int toRotation, WindowContainerTransaction t) {
+ // Bail early if the event is not sent to current {@link #mDisplayInfo}
+ if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) {
+ return false;
+ }
+
+ // Bail early if the pinned stack is staled.
+ final ActivityManager.StackInfo pinnedStackInfo;
+ try {
+ pinnedStackInfo = ActivityTaskManager.getService()
+ .getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+ if (pinnedStackInfo == null) return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get StackInfo for pinned stack", e);
+ return false;
+ }
+
+ // Calculate the snap fraction of the current stack along the old movement bounds
+ final Rect postChangeStackBounds = new Rect(mLastDestinationBounds);
+ final float snapFraction = getSnapFraction(postChangeStackBounds);
+
+ // Populate the new {@link #mDisplayInfo}.
+ // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
+ // therefore, the width/height may require a swap first.
+ // Moving forward, we should get the new dimensions after rotation from DisplayLayout.
+ mDisplayInfo.rotation = toRotation;
+ updateDisplayInfoIfNeeded();
+
+ // Calculate the stack bounds in the new orientation based on same fraction along the
+ // rotated movement bounds.
+ final Rect postChangeMovementBounds = getMovementBounds(postChangeStackBounds,
+ false /* adjustForIme */);
+ mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
+ snapFraction);
+ if (mIsMinimized) {
+ applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds);
+ }
+
+ try {
+ outBounds.set(postChangeStackBounds);
+ mLastDestinationBounds.set(outBounds);
+ mPinnedStackController.resetBoundsAnimation(outBounds);
+ mPinnedStackController.reportBounds(outBounds, getMovementBounds(outBounds));
+ t.setBounds(pinnedStackInfo.stackToken, outBounds);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to resize PiP on display rotation", e);
+ }
+ return true;
+ }
+
+ private void updateDisplayInfoIfNeeded() {
+ final boolean updateNeeded;
+ if ((mDisplayInfo.rotation == ROTATION_0) || (mDisplayInfo.rotation == ROTATION_180)) {
+ updateNeeded = (mDisplayInfo.logicalWidth > mDisplayInfo.logicalHeight);
+ } else {
+ updateNeeded = (mDisplayInfo.logicalWidth < mDisplayInfo.logicalHeight);
+ }
+ if (updateNeeded) {
+ final int newLogicalHeight = mDisplayInfo.logicalWidth;
+ mDisplayInfo.logicalWidth = mDisplayInfo.logicalHeight;
+ mDisplayInfo.logicalHeight = newLogicalHeight;
+ }
+ }
+
+ /**
* @return whether the given {@param aspectRatio} is valid.
*/
private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
similarity index 99%
rename from core/java/com/android/internal/policy/PipSnapAlgorithm.java
rename to packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
index e3623c5..f3e707c 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.policy;
+package com.android.systemui.pip;
import android.content.Context;
import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index 583ce67..29de90b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -28,6 +28,7 @@
import com.android.systemui.SystemUI;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.wm.DisplayWindowController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -44,15 +45,17 @@
private final CommandQueue mCommandQueue;
private BasePipManager mPipManager;
private final BroadcastDispatcher mBroadcastDispatcher;
-
+ private final DisplayWindowController mDisplayWindowController;
private boolean mSupportsPip;
@Inject
public PipUI(Context context, CommandQueue commandQueue,
- BroadcastDispatcher broadcastDispatcher) {
+ BroadcastDispatcher broadcastDispatcher,
+ DisplayWindowController displayWindowController) {
super(context);
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
+ mDisplayWindowController = displayWindowController;
}
@Override
@@ -72,7 +75,7 @@
mPipManager = pm.hasSystemFeature(FEATURE_LEANBACK_ONLY)
? com.android.systemui.pip.tv.PipManager.getInstance()
: com.android.systemui.pip.phone.PipManager.getInstance();
- mPipManager.initialize(mContext, mBroadcastDispatcher);
+ mPipManager.initialize(mContext, mBroadcastDispatcher, mDisplayWindowController);
mCommandQueue.addCallback(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index 3f15966..750cc60 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -109,6 +109,7 @@
lp.setTitle("pip-dismiss-overlay");
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.setFitWindowInsetsTypes(0 /* types */);
mWindowManager.addView(mDismissView, lp);
}
mDismissView.animate().cancel();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index a4707cf..f39d1ec 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -34,6 +34,7 @@
import android.util.Pair;
import android.view.DisplayInfo;
import android.view.IPinnedStackController;
+import android.view.WindowContainerTransaction;
import com.android.systemui.Dependency;
import com.android.systemui.UiOffloadThread;
@@ -45,6 +46,7 @@
import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.wm.DisplayWindowController;
import java.io.PrintWriter;
@@ -75,9 +77,22 @@
private PipAppOpsListener mAppOpsListener;
/**
+ * Handler for display rotation changes.
+ */
+ private final DisplayWindowController.OnDisplayWindowRotationController mRotationController = (
+ int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
+ final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds,
+ displayId, fromRotation, toRotation, t);
+ if (changed) {
+ updateMovementBounds(mTmpNormalBounds, false /* fromImeAdjustment */,
+ false /* fromShelfAdjustment */);
+ }
+ };
+
+ /**
* Handler for system task stack changes.
*/
- TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
mTouchHandler.onActivityPinned();
@@ -85,7 +100,7 @@
mMenuController.onActivityPinned();
mAppOpsListener.onActivityPinned(packageName);
- Dependency.get(UiOffloadThread.class).submit(() -> {
+ Dependency.get(UiOffloadThread.class).execute(() -> {
WindowManagerWrapper.getInstance().setPipVisibility(true);
});
}
@@ -99,7 +114,7 @@
mTouchHandler.onActivityUnpinned(topActivity);
mAppOpsListener.onActivityUnpinned();
- Dependency.get(UiOffloadThread.class).submit(() -> {
+ Dependency.get(UiOffloadThread.class).execute(() -> {
WindowManagerWrapper.getInstance().setPipVisibility(topActivity != null);
});
}
@@ -214,7 +229,8 @@
/**
* Initializes {@link PipManager}.
*/
- public void initialize(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
+ DisplayWindowController displayWindowController) {
mContext = context;
mActivityManager = ActivityManager.getService();
mActivityTaskManager = ActivityTaskManager.getService();
@@ -235,6 +251,7 @@
mMenuController, mInputConsumerController, mPipBoundsHandler);
mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
mTouchHandler.getMotionHelper());
+ displayWindowController.addRotationController(mRotationController);
// If SystemUI restart, and it already existed a pinned stack,
// register the pip input consumer to ensure touch can send to it.
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index fa60477..6afa0bf 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -46,7 +46,7 @@
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.os.SomeArgs;
-import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.systemui.pip.PipSnapAlgorithm;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.FlingAnimationUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 2e90a3e..95e3444 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -46,9 +46,9 @@
import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.os.logging.MetricsLoggerWrapper;
-import com.android.internal.policy.PipSnapAlgorithm;
import com.android.systemui.R;
import com.android.systemui.pip.PipBoundsHandler;
+import com.android.systemui.pip.PipSnapAlgorithm;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -194,7 +194,8 @@
mMenuController.addListener(mMenuListener);
mDismissViewController = new PipDismissViewController(context);
mSnapAlgorithm = new PipSnapAlgorithm(mContext);
- mFlingAnimationUtils = new FlingAnimationUtils(context, 2.5f);
+ mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(),
+ 2.5f);
mGestures = new PipTouchGesture[] {
mDefaultMovementGesture
};
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 195fca8..1d92375 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -55,6 +55,7 @@
import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.wm.DisplayWindowController;
import java.util.ArrayList;
import java.util.List;
@@ -228,7 +229,8 @@
/**
* Initializes {@link PipManager}.
*/
- public void initialize(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public void initialize(Context context, BroadcastDispatcher broadcastDispatcher,
+ DisplayWindowController displayWindowController) {
if (mInitialized) {
return;
}
@@ -748,7 +750,7 @@
}
private void updatePipVisibility(final boolean visible) {
- Dependency.get(UiOffloadThread.class).submit(() -> {
+ Dependency.get(UiOffloadThread.class).execute(() -> {
WindowManagerWrapper.getInstance().setPipVisibility(visible);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java
index ac94858..fb10642 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java
@@ -32,8 +32,8 @@
import androidx.annotation.VisibleForTesting;
import com.android.keyguard.CarrierTextController;
-import com.android.systemui.dagger.qualifiers.BgHandler;
-import com.android.systemui.dagger.qualifiers.MainLooper;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -111,7 +111,7 @@
}
private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
- @BgHandler Handler bgHandler, @MainLooper Looper mainLooper,
+ @Background Handler bgHandler, @Main Looper mainLooper,
NetworkController networkController,
CarrierTextController.Builder carrierTextControllerBuilder) {
mActivityStarter = activityStarter;
@@ -308,8 +308,8 @@
private final CarrierTextController.Builder mCarrierTextControllerBuilder;
@Inject
- public Builder(ActivityStarter activityStarter, @BgHandler Handler handler,
- @MainLooper Looper looper, NetworkController networkController,
+ public Builder(ActivityStarter activityStarter, @Background Handler handler,
+ @Main Looper looper, NetworkController networkController,
CarrierTextController.Builder carrierTextControllerBuilder) {
mActivityStarter = activityStarter;
mHandler = handler;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index c01bc8fe..86ed274 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -34,8 +34,8 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.BgLooper;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
@@ -96,8 +96,8 @@
public QSTileHost(Context context,
StatusBarIconController iconController,
QSFactoryImpl defaultFactory,
- @MainHandler Handler mainHandler,
- @BgLooper Looper bgLooper,
+ @Main Handler mainHandler,
+ @Background Looper bgLooper,
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 5bb882e..d40e250 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -250,6 +250,14 @@
return (state.getState() == PlaybackState.STATE_PLAYING);
}
+ /**
+ * Check whether this player has an attached media session.
+ * @return whether there is a controller with a current media session.
+ */
+ public boolean hasMediaSession() {
+ return mController != null && mController.getPlaybackState() != null;
+ }
+
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);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index d377f1c..db52e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -57,6 +57,12 @@
private int mMaxTiles;
protected QSPanel mFullPanel;
private QuickQSMediaPlayer mMediaPlayer;
+ private boolean mUsingMediaPlayer;
+ private LinearLayout mHorizontalLinearLayout;
+
+ // Only used with media
+ private QSTileLayout mMediaTileLayout;
+ private QSTileLayout mRegularTileLayout;
@Inject
public QuickQSPanel(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -72,8 +78,9 @@
removeView((View) mTileLayout);
}
- if (Utils.useQsMediaPlayer(context)) {
- LinearLayout mHorizontalLinearLayout = new LinearLayout(mContext);
+ mUsingMediaPlayer = Utils.useQsMediaPlayer(context);
+ if (mUsingMediaPlayer) {
+ mHorizontalLinearLayout = new LinearLayout(mContext);
mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
mHorizontalLinearLayout.setClipChildren(false);
mHorizontalLinearLayout.setClipToPadding(false);
@@ -81,6 +88,8 @@
LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
mTileLayout = new DoubleLineTileLayout(context);
+ mMediaTileLayout = mTileLayout;
+ mRegularTileLayout = new HeaderTileLayout(context);
lp.setMarginEnd(10);
lp.setMarginStart(0);
mHorizontalLinearLayout.addView((View) mTileLayout, lp);
@@ -95,6 +104,8 @@
mTileLayout.setListening(mListening);
addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */);
+ ((View) mRegularTileLayout).setVisibility(View.GONE);
+ addView((View) mRegularTileLayout, 0);
super.setPadding(0, 0, 0, 0);
} else {
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
@@ -130,6 +141,8 @@
Dependency.get(TunerService.class).removeTunable(mNumTiles);
}
+
+
@Override
protected String getDumpableTag() {
return TAG;
@@ -152,6 +165,42 @@
super.drawTile(r, state);
}
+ boolean switchTileLayout() {
+ if (!mUsingMediaPlayer) return false;
+ if (mMediaPlayer.hasMediaSession()
+ && mHorizontalLinearLayout.getVisibility() == View.GONE) {
+ mHorizontalLinearLayout.setVisibility(View.VISIBLE);
+ ((View) mRegularTileLayout).setVisibility(View.GONE);
+ mTileLayout.setListening(false);
+ for (TileRecord record : mRecords) {
+ mTileLayout.removeTile(record);
+ record.tile.removeCallback(record.callback);
+ }
+ mTileLayout = mMediaTileLayout;
+ if (mHost != null) setTiles(mHost.getTiles());
+ mTileLayout.setListening(mListening);
+ return true;
+ } else if (!mMediaPlayer.hasMediaSession()
+ && mHorizontalLinearLayout.getVisibility() == View.VISIBLE) {
+ mHorizontalLinearLayout.setVisibility(View.GONE);
+ ((View) mRegularTileLayout).setVisibility(View.VISIBLE);
+ mTileLayout.setListening(false);
+ for (TileRecord record : mRecords) {
+ mTileLayout.removeTile(record);
+ record.tile.removeCallback(record.callback);
+ }
+ mTileLayout = mRegularTileLayout;
+ if (mHost != null) setTiles(mHost.getTiles());
+ mTileLayout.setListening(mListening);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean hasMediaPlayerSession() {
+ return mMediaPlayer.hasMediaSession();
+ }
+
@Override
public void setHost(QSTileHost host, QSCustomizer customizer) {
super.setHost(host, customizer);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index e5cec87..d4af154 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -339,7 +339,7 @@
if (mQsDisabled) {
lp.height = resources.getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_offset_height);
- } else if (useQsMediaPlayer(mContext)) {
+ } else if (useQsMediaPlayer(mContext) && mHeaderQsPanel.hasMediaPlayerSession()) {
lp.height = Math.max(getMinimumHeight(),
resources.getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_total_height_with_media));
@@ -405,6 +405,11 @@
mHeaderTextContainerView.setVisibility(INVISIBLE);
}
}
+ if (expansionFraction < 1 && expansionFraction > 0.99) {
+ if (mHeaderQsPanel.switchTileLayout()) {
+ updateResources();
+ }
+ }
}
public void disable(int state1, int state2, boolean animate) {
@@ -453,6 +458,9 @@
return;
}
mHeaderQsPanel.setListening(listening);
+ if (mHeaderQsPanel.switchTileLayout()) {
+ updateResources();
+ }
mListening = listening;
if (listening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index e0f26cd..1d37911 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -413,6 +413,7 @@
handleSetListening(false);
}
mCallbacks.clear();
+ mHandler.removeCallbacksAndMessages(null);
}
protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 4afcf01..5e297e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -26,11 +26,11 @@
import android.provider.Settings.Global;
import android.service.quicksettings.Tile;
import android.sysprop.TelephonyProperties;
+import android.telephony.TelephonyManager;
import android.widget.Switch;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.ActivityStarter;
@@ -76,7 +76,7 @@
MetricsLogger.action(mContext, getMetricsCategory(), !airplaneModeEnabled);
if (!airplaneModeEnabled && TelephonyProperties.in_ecm_mode().orElse(false)) {
mActivityStarter.postStartActivityDismissingKeyguard(
- new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0);
+ new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0);
return;
}
setEnabled(!airplaneModeEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 9fe9703..7bc2a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -92,7 +92,10 @@
boolean nightMode = (mContext.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
- if (isAuto) {
+ if (powerSave) {
+ state.secondaryLabel = mContext.getResources().getString(
+ R.string.quick_settings_dark_mode_secondary_label_battery_saver);
+ } else if (isAuto) {
state.secondaryLabel = mContext.getResources().getString(nightMode
? R.string.quick_settings_dark_mode_secondary_label_until_sunrise
: R.string.quick_settings_dark_mode_secondary_label_on_at_sunset);
@@ -100,9 +103,7 @@
state.secondaryLabel = null;
}
state.value = nightMode;
- state.label = mContext.getString(powerSave
- ? R.string.quick_settings_ui_mode_night_label_battery_saver
- : R.string.quick_settings_ui_mode_night_label);
+ state.label = mContext.getString(R.string.quick_settings_ui_mode_night_label);
state.icon = mIcon;
state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
? state.label
@@ -123,7 +124,7 @@
@Override
public Intent getLongClickIntent() {
- return new Intent(Settings.ACTION_DISPLAY_SETTINGS);
+ return new Intent(Settings.ACTION_DARK_THEME_SETTINGS);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index d819b8e..1d649ee 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -138,6 +138,7 @@
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("ScreenPinningConfirmation");
lp.gravity = Gravity.FILL;
+ lp.setFitWindowInsetsTypes(0 /* types */);
return lp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 0383dee..d3ccbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -371,7 +371,8 @@
Bitmap thumbnailBitmap = null;
try {
ContentResolver resolver = getContentResolver();
- Size size = Point.convert(MediaStore.ThumbnailConstants.MINI_SIZE);
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ Size size = new Size(metrics.widthPixels, metrics.heightPixels / 2);
thumbnailBitmap = resolver.loadThumbnail(uri, size, null);
} catch (IOException e) {
Log.e(TAG, "Error creating thumbnail: " + e.getMessage());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 5041354..02c4beb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -16,8 +16,6 @@
package com.android.systemui.screenshot;
-import static android.content.Context.NOTIFICATION_SERVICE;
-import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -31,14 +29,10 @@
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -55,7 +49,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.DisplayMetrics;
@@ -74,20 +67,13 @@
import android.widget.TextView;
import android.widget.Toast;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.util.NotificationChannels;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -108,29 +94,20 @@
* POD used in the AsyncTask which saves an image in the background.
*/
static class SaveImageInBackgroundData {
- public Context context;
public Bitmap image;
public Uri imageUri;
public Consumer<Uri> finisher;
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
- public int iconSize;
- public int previewWidth;
- public int previewheight;
public int errorMsgResId;
void clearImage() {
image = null;
imageUri = null;
- iconSize = 0;
- }
-
- void clearContext() {
- context = null;
}
}
abstract static class ActionsReadyListener {
- abstract void onActionsReady(PendingIntent shareAction, PendingIntent editAction);
+ abstract void onActionsReady(Uri imageUri, List<Notification.Action> actions);
}
// These strings are used for communicating the action invoked to
@@ -165,13 +142,12 @@
private static final float SCREENSHOT_CORNER_MIN_SCALE_OFFSET = .1f;
private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000;
private static final int MESSAGE_CORNER_TIMEOUT = 2;
- private final int mPreviewWidth;
- private final int mPreviewHeight;
+
+ private final ScreenshotNotificationsController mNotificationsController;
private Context mContext;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowLayoutParams;
- private NotificationManager mNotificationManager;
private Display mDisplay;
private DisplayMetrics mDisplayMetrics;
@@ -182,13 +158,9 @@
private ImageView mScreenshotView;
private ImageView mScreenshotFlash;
private LinearLayout mActionsView;
- private TextView mShareAction;
- private TextView mEditAction;
- private TextView mScrollAction;
private AnimatorSet mScreenshotAnimation;
- private int mNotificationIconSize;
private float mBgPadding;
private float mBgPaddingScale;
@@ -213,9 +185,11 @@
* @param context everything needs a context :(
*/
@Inject
- public GlobalScreenshot(Context context, @MainResources Resources resources,
- LayoutInflater layoutInflater) {
+ public GlobalScreenshot(
+ Context context, @Main Resources resources, LayoutInflater layoutInflater,
+ ScreenshotNotificationsController screenshotNotificationsController) {
mContext = context;
+ mNotificationsController = screenshotNotificationsController;
// Inflate the screenshot layout
mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
@@ -223,21 +197,6 @@
mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot);
mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
- mShareAction = (TextView) layoutInflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- mEditAction = (TextView) layoutInflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- mScrollAction = (TextView) layoutInflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
-
- mShareAction.setText(com.android.internal.R.string.share);
- mEditAction.setText(com.android.internal.R.string.screenshot_edit);
- mScrollAction.setText("Scroll"); // TODO (mkephart): Add to resources and translate
-
- mActionsView.addView(mShareAction);
- mActionsView.addView(mEditAction);
- mActionsView.addView(mScrollAction);
-
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
mScreenshotLayout.setFocusable(true);
@@ -258,33 +217,16 @@
PixelFormat.TRANSLUCENT);
mWindowLayoutParams.setTitle("ScreenshotAnimation");
mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mNotificationManager =
- (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
mDisplayMetrics = new DisplayMetrics();
mDisplay.getRealMetrics(mDisplayMetrics);
- // Get the various target sizes
- mNotificationIconSize =
- resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
-
// Scale has to account for both sides of the bg
mBgPadding = (float) resources.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
- // determine the optimal preview size
- int panelWidth = 0;
- try {
- panelWidth = resources.getDimensionPixelSize(R.dimen.notification_panel_width);
- } catch (Resources.NotFoundException e) {
- }
- if (panelWidth <= 0) {
- // includes notification_panel_width==match_parent (-1)
- panelWidth = mDisplayMetrics.widthPixels;
- }
- mPreviewWidth = panelWidth;
- mPreviewHeight = resources.getDimensionPixelSize(R.dimen.notification_max_height);
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
@@ -297,22 +239,20 @@
private void saveScreenshotInWorkerThread(
Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
- data.context = mContext;
data.image = mScreenBitmap;
- data.iconSize = mNotificationIconSize;
data.finisher = finisher;
data.mActionsReadyListener = actionsReadyListener;
- data.previewWidth = mPreviewWidth;
- data.previewheight = mPreviewHeight;
if (mSaveInBgTask != null) {
mSaveInBgTask.cancel(false);
}
- mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
- .execute();
- }
- private void saveScreenshotInWorkerThread(Consumer<Uri> finisher) {
- saveScreenshotInWorkerThread(finisher, null);
+ if (!DeviceConfig.getBoolean(
+ NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
+ mNotificationsController.reset();
+ mNotificationsController.setImage(mScreenBitmap);
+ mNotificationsController.showSavingScreenshotNotification();
+ }
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
}
/**
@@ -327,7 +267,7 @@
// Take the screenshot
mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
if (mScreenBitmap == null) {
- notifyScreenshotError(mContext, mNotificationManager,
+ mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
finisher.accept(null);
return;
@@ -372,12 +312,8 @@
if (rect != null) {
if (rect.width() != 0 && rect.height() != 0) {
// Need mScreenshotLayout to handle it after the view disappears
- mScreenshotLayout.post(new Runnable() {
- public void run() {
- takeScreenshot(finisher, statusBarVisible, navBarVisible,
- rect);
- }
- });
+ mScreenshotLayout.post(() -> takeScreenshot(
+ finisher, statusBarVisible, navBarVisible, rect));
}
}
@@ -463,15 +399,30 @@
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
if (!useCornerFlow) {
- saveScreenshotInWorkerThread(finisher);
+ 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);
+ }
+ }
+ });
clearScreenshot();
} else {
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
@Override
- void onActionsReady(PendingIntent shareAction, PendingIntent editAction) {
- mScreenshotHandler.post(() ->
- createScreenshotActionsShadeAnimation(shareAction, editAction)
- .start());
+ 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(
@@ -678,8 +629,33 @@
return anim;
}
- private ValueAnimator createScreenshotActionsShadeAnimation(
- PendingIntent shareAction, PendingIntent editAction) {
+ private ValueAnimator createScreenshotActionsShadeAnimation(List<Notification.Action> actions) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mActionsView.removeAllViews();
+
+ for (Notification.Action action : actions) {
+ TextView actionChip = (TextView) inflater.inflate(
+ R.layout.global_screenshot_action_chip, mActionsView, false);
+ actionChip.setText(action.title);
+ actionChip.setOnClickListener(v -> {
+ try {
+ action.actionIntent.send();
+ clearScreenshot();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG,
+ String.format("Intent cancelled (title: %s)", action.title), e);
+ }
+ });
+ 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);
+
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
mActionsView.setY(mDisplayMetrics.heightPixels);
mActionsView.setVisibility(VISIBLE);
@@ -697,158 +673,11 @@
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mScreenshotView.requestFocus();
- mShareAction.setOnClickListener(v -> {
- try {
- shareAction.send();
- clearScreenshot();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Share intent cancelled", e);
- }
- });
- mEditAction.setOnClickListener(v -> {
- try {
- editAction.send();
- clearScreenshot();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Edit intent cancelled", e);
- }
- });
- Toast scrollNotImplemented = Toast.makeText(
- mContext, "Not implemented", Toast.LENGTH_SHORT);
- mScrollAction.setOnClickListener(v -> scrollNotImplemented.show());
}
});
return animator;
}
- static void notifyScreenshotError(Context context, NotificationManager nManager, int msgResId) {
- Resources r = context.getResources();
- String errorMsg = r.getString(msgResId);
-
- // Repurpose the existing notification to notify the user of the error
- Notification.Builder b = new Notification.Builder(context, NotificationChannels.ALERTS)
- .setTicker(r.getString(R.string.screenshot_failed_title))
- .setContentTitle(r.getString(R.string.screenshot_failed_title))
- .setContentText(errorMsg)
- .setSmallIcon(R.drawable.stat_notify_image_error)
- .setWhen(System.currentTimeMillis())
- .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
- .setCategory(Notification.CATEGORY_ERROR)
- .setAutoCancel(true)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- final Intent intent = dpm.createAdminSupportIntent(
- DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
- if (intent != null) {
- final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- context, 0, intent, 0, null, UserHandle.CURRENT);
- b.setContentIntent(pendingIntent);
- }
-
- SystemUI.overrideNotificationAppName(context, b, true);
-
- Notification n = new Notification.BigTextStyle(b)
- .bigText(errorMsg)
- .build();
- nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
- }
-
- @VisibleForTesting
- static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
- Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
- boolean smartActionsEnabled, boolean isManagedProfile) {
- if (!smartActionsEnabled) {
- Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
- return CompletableFuture.completedFuture(Collections.emptyList());
- }
- if (image.getConfig() != Bitmap.Config.HARDWARE) {
- Slog.w(TAG, String.format(
- "Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
- image.getConfig()));
- return CompletableFuture.completedFuture(Collections.emptyList());
- }
-
- Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
- CompletableFuture<List<Notification.Action>> smartActionsFuture;
- long startTimeMs = SystemClock.uptimeMillis();
- try {
- ActivityManager.RunningTaskInfo runningTask =
- ActivityManagerWrapper.getInstance().getRunningTask();
- ComponentName componentName =
- (runningTask != null && runningTask.topActivity != null)
- ? runningTask.topActivity
- : new ComponentName("", "");
- smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
- componentName,
- isManagedProfile);
- } catch (Throwable e) {
- long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
- smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
- Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
- notifyScreenshotOp(screenshotId, smartActionsProvider,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
- waitTimeMs);
- }
- return smartActionsFuture;
- }
-
- @VisibleForTesting
- static List<Notification.Action> getSmartActions(String screenshotId,
- CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
- ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
- long startTimeMs = SystemClock.uptimeMillis();
- try {
- List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
- TimeUnit.MILLISECONDS);
- long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
- Slog.d(TAG, String.format("Got %d smart actions. Wait time: %d ms",
- actions.size(), waitTimeMs));
- notifyScreenshotOp(screenshotId, smartActionsProvider,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
- waitTimeMs);
- return actions;
- } catch (Throwable e) {
- long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
- Slog.e(TAG, String.format("Error getting smart actions. Wait time: %d ms", waitTimeMs),
- e);
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
- (e instanceof TimeoutException)
- ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
- : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
- notifyScreenshotOp(screenshotId, smartActionsProvider,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
- status, waitTimeMs);
- return Collections.emptyList();
- }
- }
-
- static void notifyScreenshotOp(String screenshotId,
- ScreenshotNotificationSmartActionsProvider smartActionsProvider,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
- try {
- smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
- } catch (Throwable e) {
- Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
- }
- }
-
- static void notifyScreenshotAction(Context context, String screenshotId, String action,
- boolean isSmartAction) {
- try {
- ScreenshotNotificationSmartActionsProvider provider =
- SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
- context, THREAD_POOL_EXECUTOR, new Handler());
- provider.notifyAction(screenshotId, action, isSmartAction);
- } catch (Throwable e) {
- Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
- }
- }
-
/**
* Receiver to proxy the share or edit intent, used to clean up the notification and send
* appropriate signals to the system (ie. to dismiss the keyguard if necessary).
@@ -877,7 +706,7 @@
Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
- cancelScreenshotNotification(context);
+ ScreenshotNotificationsController.cancelScreenshotNotification(context);
}
ActivityOptions opts = ActivityOptions.makeBasic();
opts.setDisallowEnterPictureInPictureWhileLaunching(
@@ -896,8 +725,8 @@
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT
: ACTION_TYPE_SHARE;
- notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
- actionType, false);
+ ScreenshotSmartActions.notifyScreenshotAction(
+ context, intent.getStringExtra(EXTRA_ID), actionType, false);
}
}
}
@@ -909,7 +738,7 @@
@Override
public void onReceive(Context context, Intent intent) {
// Clear the notification only after the user has chosen a share action
- cancelScreenshotNotification(context);
+ ScreenshotNotificationsController.cancelScreenshotNotification(context);
}
}
@@ -924,15 +753,14 @@
}
// Clear the notification when the image is deleted
- cancelScreenshotNotification(context);
+ ScreenshotNotificationsController.cancelScreenshotNotification(context);
// And delete the image from the media store
final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
new DeleteImageInBackgroundTask(context).execute(uri);
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
- notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
- ACTION_TYPE_DELETE,
- false);
+ ScreenshotSmartActions.notifyScreenshotAction(
+ context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false);
}
}
}
@@ -951,15 +779,8 @@
context.startActivityAsUser(actionIntent, opts.toBundle(),
UserHandle.CURRENT);
- notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
- actionType,
- true);
+ ScreenshotSmartActions.notifyScreenshotAction(
+ context, intent.getStringExtra(EXTRA_ID), actionType, true);
}
}
-
- private static void cancelScreenshotNotification(Context context) {
- final NotificationManager nm =
- (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
- nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 76925b4..6bad15c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -18,22 +18,18 @@
import android.app.ActivityTaskManager;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Picture;
+import android.graphics.drawable.Icon;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.AsyncTask;
@@ -48,18 +44,15 @@
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.R;
-import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
-import com.android.systemui.util.NotificationChannels;
-
-import libcore.io.IoUtils;
import java.io.File;
import java.io.IOException;
@@ -72,6 +65,7 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@@ -89,22 +83,17 @@
private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
+ private final Context mContext;
private final GlobalScreenshot.SaveImageInBackgroundData mParams;
- private final NotificationManager mNotificationManager;
- private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
private final String mImageFileName;
private final long mImageTime;
- private final Notification.BigPictureStyle mNotificationStyle;
- private final int mImageWidth;
- private final int mImageHeight;
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
private final String mScreenshotId;
private final boolean mSmartActionsEnabled;
private final Random mRandom = new Random();
- SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data,
- NotificationManager nManager) {
- Resources r = context.getResources();
+ SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
+ mContext = context;
// Prepare all the output metadata
mParams = data;
@@ -125,80 +114,228 @@
// If smart actions is not enabled use empty implementation.
mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
}
+ }
- // Create the large notification icon
- mImageWidth = data.image.getWidth();
- mImageHeight = data.image.getHeight();
- int iconSize = data.iconSize;
- int previewWidth = data.previewWidth;
- int previewHeight = data.previewheight;
+ @Override
+ protected Void doInBackground(Void... paramsUnused) {
+ if (isCancelled()) {
+ return null;
+ }
- Paint paint = new Paint();
- ColorMatrix desat = new ColorMatrix();
- desat.setSaturation(0.25f);
- paint.setColorFilter(new ColorMatrixColorFilter(desat));
- Matrix matrix = new Matrix();
- int overlayColor = 0x40FFFFFF;
+ // By default, AsyncTask sets the worker thread to have background thread priority,
+ // so bump it back up so that we save a little quicker.
+ Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
- matrix.setTranslate((previewWidth - mImageWidth) / 2,
- (previewHeight - mImageHeight) / 2);
- Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight,
- matrix, paint, overlayColor);
+ ContentResolver resolver = mContext.getContentResolver();
+ Bitmap image = mParams.image;
+ Resources r = mContext.getResources();
- // Note, we can't use the preview for the small icon, since it is non-square
- float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
- matrix.setScale(scale, scale);
- matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
- (iconSize - (scale * mImageHeight)) / 2);
- Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint,
- overlayColor);
+ try {
+ CompletableFuture<List<Notification.Action>> smartActionsFuture =
+ ScreenshotSmartActions.getSmartActionsFuture(
+ mScreenshotId, image, mSmartActionsProvider,
+ mSmartActionsEnabled, isManagedProfile(mContext));
- mNotificationManager = nManager;
- final long now = System.currentTimeMillis();
+ // Save the screenshot to the MediaStore
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
+ + File.separator + Environment.DIRECTORY_SCREENSHOTS);
+ values.put(MediaColumns.DISPLAY_NAME, mImageFileName);
+ values.put(MediaColumns.MIME_TYPE, "image/png");
+ values.put(MediaColumns.DATE_ADDED, mImageTime / 1000);
+ values.put(MediaColumns.DATE_MODIFIED, mImageTime / 1000);
+ values.put(MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
+ values.put(MediaColumns.IS_PENDING, 1);
- // Setup the notification
- mNotificationStyle = new Notification.BigPictureStyle()
- .bigPicture(picture.createAshmemBitmap());
+ final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ try {
+ // First, write the actual data for our screenshot
+ try (OutputStream out = resolver.openOutputStream(uri)) {
+ if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
+ throw new IOException("Failed to compress");
+ }
+ }
- // The public notification will show similar info but with the actual screenshot omitted
- mPublicNotificationBuilder =
- new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
- .setContentTitle(r.getString(R.string.screenshot_saving_title))
- .setSmallIcon(R.drawable.stat_notify_image)
- .setCategory(Notification.CATEGORY_PROGRESS)
- .setWhen(now)
- .setShowWhen(true)
- .setColor(r.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder, true);
+ // Next, write metadata to help index the screenshot
+ try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) {
+ final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
- mNotificationBuilder = new Notification.Builder(context,
- NotificationChannels.SCREENSHOTS_HEADSUP)
- .setContentTitle(r.getString(R.string.screenshot_saving_title))
- .setSmallIcon(R.drawable.stat_notify_image)
- .setWhen(now)
- .setShowWhen(true)
- .setColor(r.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setStyle(mNotificationStyle)
- .setPublicVersion(mPublicNotificationBuilder.build());
- mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
- SystemUI.overrideNotificationAppName(context, mNotificationBuilder, true);
+ exif.setAttribute(ExifInterface.TAG_SOFTWARE,
+ "Android " + Build.DISPLAY);
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
- mNotificationBuilder.build());
+ exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH,
+ Integer.toString(image.getWidth()));
+ exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH,
+ Integer.toString(image.getHeight()));
- /**
- * NOTE: The following code prepares the notification builder for updating the
- * notification after the screenshot has been written to disk.
- */
+ final ZonedDateTime time = ZonedDateTime.ofInstant(
+ Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault());
+ exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL,
+ DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time));
+ exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
+ DateTimeFormatter.ofPattern("SSS").format(time));
- // On the tablet, the large icon makes the notification appear as if it is clickable
- // (and on small devices, the large icon is not shown) so defer showing the large icon
- // until we compose the final post-save notification below.
- mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
- // But we still don't set it for the expanded view, allowing the smallIcon to show here.
- mNotificationStyle.bigLargeIcon((Bitmap) null);
+ if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) {
+ exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00");
+ } else {
+ exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
+ DateTimeFormatter.ofPattern("XXX").format(time));
+ }
+
+ exif.saveAttributes();
+ }
+
+ // Everything went well above, publish it!
+ values.clear();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ resolver.update(uri, values, null, null);
+ } catch (Exception e) {
+ resolver.delete(uri, null);
+ throw e;
+ }
+
+ List<Notification.Action> actions =
+ populateNotificationActions(mContext, r, uri, smartActionsFuture);
+ mParams.mActionsReadyListener.onActionsReady(uri, actions);
+ mParams.imageUri = uri;
+ mParams.image = null;
+ mParams.errorMsgResId = 0;
+ } catch (Exception e) {
+ // IOException/UnsupportedOperationException may be thrown if external storage is
+ // not mounted
+ Slog.e(TAG, "unable to save screenshot", e);
+ mParams.clearImage();
+ mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
+ mParams.mActionsReadyListener.onActionsReady(null, null);
+ }
+
+ // Recycle the bitmap data
+ if (image != null) {
+ image.recycle();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void params) {
+ mParams.finisher.accept(mParams.imageUri);
+ }
+
+ @Override
+ protected void onCancelled(Void params) {
+ // If we are cancelled while the task is running in the background, we may get null
+ // params. The finisher is expected to always be called back, so just use the baked-in
+ // params from the ctor in any case.
+ mParams.mActionsReadyListener.onActionsReady(null, null);
+ mParams.finisher.accept(null);
+ mParams.clearImage();
+ }
+
+ @VisibleForTesting
+ List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri,
+ CompletableFuture<List<Notification.Action>> smartActionsFuture) {
+ // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
+ // order to do some common work like dismissing the keyguard and sending
+ // closeSystemWindows
+
+ // Create a share intent, this will always go through the chooser activity first
+ // which should not trigger auto-enter PiP
+ String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+ String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
+ Intent sharingIntent = new Intent(Intent.ACTION_SEND);
+ sharingIntent.setType("image/png");
+ sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ ClipData clipdata = new ClipData(new ClipDescription("content",
+ new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
+ new ClipData.Item(uri));
+ sharingIntent.setClipData(clipdata);
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // Make sure pending intents for the system user are still unique across users
+ // by setting the (otherwise unused) request code to the current user id.
+ int requestCode = context.getUserId();
+
+ PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
+ new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ Intent sharingChooserIntent =
+ Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender())
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // Create a share action for the notification
+ PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
+ new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
+ .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, sharingChooserIntent)
+ .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true)
+ .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ mSmartActionsEnabled)
+ .setAction(Intent.ACTION_SEND),
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+
+ Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
+ Icon.createWithResource(r, R.drawable.ic_screenshot_share),
+ r.getString(com.android.internal.R.string.share), shareAction);
+
+ // Create an edit intent, if a specific package is provided as the editor, then
+ // launch that directly
+ String editorPackage = context.getString(R.string.config_screenshotEditor);
+ Intent editIntent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ }
+ editIntent.setType("image/png");
+ editIntent.setData(uri);
+ editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ // Create a edit action
+ PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
+ new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
+ .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, editIntent)
+ .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION,
+ editIntent.getComponent() != null)
+ .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ mSmartActionsEnabled)
+ .setAction(Intent.ACTION_EDIT),
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
+ Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
+ r.getString(com.android.internal.R.string.screenshot_edit), editAction);
+
+ // Create a delete action for the notification
+ PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
+ new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+ .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
+ .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ mSmartActionsEnabled),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
+ Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
+ r.getString(com.android.internal.R.string.delete), deleteAction);
+
+ ArrayList<Notification.Action> actions = new ArrayList<>(
+ Arrays.asList(shareActionBuilder.build(), editActionBuilder.build(),
+ deleteActionBuilder.build()));
+ if (mSmartActionsEnabled) {
+ int timeoutMs = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
+ 1000);
+ actions.addAll(buildSmartActions(
+ ScreenshotSmartActions.getSmartActions(
+ mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider),
+ context));
+ }
+ return actions;
}
private int getUserHandleOfForegroundApplication(Context context) {
@@ -250,285 +387,5 @@
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
}
- /**
- * Generates a new hardware bitmap with specified values, copying the content from the
- * passed in bitmap.
- */
- private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
- Paint paint, int color) {
- Picture picture = new Picture();
- Canvas canvas = picture.beginRecording(width, height);
- canvas.drawColor(color);
- canvas.drawBitmap(bitmap, matrix, paint);
- picture.endRecording();
- return Bitmap.createBitmap(picture);
- }
- @Override
- protected Void doInBackground(Void... paramsUnused) {
- if (isCancelled()) {
- return null;
- }
-
- // By default, AsyncTask sets the worker thread to have background thread priority,
- // so bump it back up so that we save a little quicker.
- Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
-
- Context context = mParams.context;
- Bitmap image = mParams.image;
- Resources r = context.getResources();
-
- try {
- CompletableFuture<List<Notification.Action>> smartActionsFuture =
- GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image,
- mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
-
- // Save the screenshot to the MediaStore
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
- params.setRelativePath(Environment.DIRECTORY_PICTURES + File.separator
- + Environment.DIRECTORY_SCREENSHOTS);
-
- final Uri uri = MediaStore.createPending(context, params);
- final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
- try {
- // First, write the actual data for our screenshot
- try (OutputStream out = session.openOutputStream()) {
- if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
- throw new IOException("Failed to compress");
- }
- }
-
- // Next, write metadata to help index the screenshot
- try (ParcelFileDescriptor pfd = session.open()) {
- final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
-
- exif.setAttribute(ExifInterface.TAG_SOFTWARE,
- "Android " + Build.DISPLAY);
-
- exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH,
- Integer.toString(image.getWidth()));
- exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH,
- Integer.toString(image.getHeight()));
-
- final ZonedDateTime time = ZonedDateTime.ofInstant(
- Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault());
- exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL,
- DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time));
- exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
- DateTimeFormatter.ofPattern("SSS").format(time));
-
- if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) {
- exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00");
- } else {
- exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
- DateTimeFormatter.ofPattern("XXX").format(time));
- }
-
- exif.saveAttributes();
- }
- session.publish();
- } catch (Exception e) {
- session.abandon();
- throw e;
- } finally {
- IoUtils.closeQuietly(session);
- }
-
- populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
-
- mParams.imageUri = uri;
- mParams.image = null;
- mParams.errorMsgResId = 0;
- } catch (Exception e) {
- // IOException/UnsupportedOperationException may be thrown if external storage is
- // not mounted
- Slog.e(TAG, "unable to save screenshot", e);
- mParams.clearImage();
- mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
- }
-
- // Recycle the bitmap data
- if (image != null) {
- image.recycle();
- }
-
- return null;
- }
-
- @VisibleForTesting
- void populateNotificationActions(Context context, Resources r, Uri uri,
- CompletableFuture<List<Notification.Action>> smartActionsFuture,
- Notification.Builder notificationBuilder) {
- // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
- // order to do some common work like dismissing the keyguard and sending
- // closeSystemWindows
-
- // Create a share intent, this will always go through the chooser activity first
- // which should not trigger auto-enter PiP
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
- String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
- Intent sharingIntent = new Intent(Intent.ACTION_SEND);
- sharingIntent.setType("image/png");
- sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
- new ClipData.Item(uri));
- sharingIntent.setClipData(clipdata);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
- sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Make sure pending intents for the system user are still unique across users
- // by setting the (otherwise unused) request code to the current user id.
- int requestCode = context.getUserId();
-
- PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
- new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
- chooserAction.getIntentSender())
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Create a share action for the notification
- PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
- new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
- .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, sharingChooserIntent)
- .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true)
- .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
- .setAction(Intent.ACTION_SEND),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
- Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_share,
- r.getString(com.android.internal.R.string.share), shareAction);
- notificationBuilder.addAction(shareActionBuilder.build());
-
- // Create an edit intent, if a specific package is provided as the editor, then
- // launch that directly
- String editorPackage = context.getString(R.string.config_screenshotEditor);
- Intent editIntent = new Intent(Intent.ACTION_EDIT);
- if (!TextUtils.isEmpty(editorPackage)) {
- editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
- }
- editIntent.setType("image/png");
- editIntent.setData(uri);
- editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
- // Create a edit action
- PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
- new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
- .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, editIntent)
- .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION,
- editIntent.getComponent() != null)
- .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
- .setAction(Intent.ACTION_EDIT),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
- Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_edit,
- r.getString(com.android.internal.R.string.screenshot_edit), editAction);
- notificationBuilder.addAction(editActionBuilder.build());
- if (mParams.mActionsReadyListener != null) {
- mParams.mActionsReadyListener.onActionsReady(shareAction, editAction);
- }
-
- // Create a delete action for the notification
- PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
- new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
- .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
- .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_delete,
- r.getString(com.android.internal.R.string.delete), deleteAction);
- notificationBuilder.addAction(deleteActionBuilder.build());
-
- if (mSmartActionsEnabled) {
- int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags
- .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
- 1000);
- List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions(mScreenshotId,
- smartActionsFuture, timeoutMs, mSmartActionsProvider);
- smartActions = buildSmartActions(smartActions, context);
- for (Notification.Action action : smartActions) {
- notificationBuilder.addAction(action);
- }
- }
- }
-
- @Override
- protected void onPostExecute(Void params) {
- if (mParams.errorMsgResId != 0) {
- // Show a message that we've failed to save the image to disk
- GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,
- mParams.errorMsgResId);
- } else {
- if (mParams.mActionsReadyListener != null) {
- // Cancel the "saving screenshot" notification
- mNotificationManager.cancel(
- SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
- } else {
- // Show the final notification to indicate screenshot saved
- Context context = mParams.context;
- Resources r = context.getResources();
-
- // Create the intent to show the screenshot in gallery
- Intent launchIntent = new Intent(Intent.ACTION_VIEW);
- launchIntent.setDataAndType(mParams.imageUri, "image/png");
- launchIntent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- final long now = System.currentTimeMillis();
-
- // Update the text and the icon for the existing notification
- mPublicNotificationBuilder
- .setContentTitle(r.getString(R.string.screenshot_saved_title))
- .setContentText(r.getString(R.string.screenshot_saved_text))
- .setContentIntent(
- PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
- .setWhen(now)
- .setAutoCancel(true)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationBuilder
- .setContentTitle(r.getString(R.string.screenshot_saved_title))
- .setContentText(r.getString(R.string.screenshot_saved_text))
- .setContentIntent(PendingIntent.getActivity(mParams.context, 0,
- launchIntent, 0))
- .setWhen(now)
- .setAutoCancel(true)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setPublicVersion(mPublicNotificationBuilder.build())
- .setFlag(Notification.FLAG_NO_CLEAR, false);
-
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
- mNotificationBuilder.build());
- }
- }
- mParams.finisher.accept(mParams.imageUri);
- mParams.clearContext();
- }
-
- @Override
- protected void onCancelled(Void params) {
- // If we are cancelled while the task is running in the background, we may get null
- // params. The finisher is expected to always be called back, so just use the baked-in
- // params from the ctor in any case.
- mParams.finisher.accept(null);
- mParams.clearImage();
- mParams.clearContext();
-
- // Cancel the posted notification
- mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
new file mode 100644
index 0000000..42fca94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -0,0 +1,293 @@
+/*
+ * 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.screenshot;
+
+import static android.content.Context.NOTIFICATION_SERVICE;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+import com.android.systemui.util.NotificationChannels;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Convenience class to handle showing and hiding notifications while taking a screenshot.
+ */
+public class ScreenshotNotificationsController {
+ private static final String TAG = "ScreenshotNotificationManager";
+
+ private final Context mContext;
+ private final Resources mResources;
+ private final NotificationManager mNotificationManager;
+ private final Notification.BigPictureStyle mNotificationStyle;
+
+ private int mIconSize;
+ private int mPreviewWidth, mPreviewHeight;
+ private Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
+
+ @Inject
+ ScreenshotNotificationsController(Context context, WindowManager windowManager) {
+ mContext = context;
+ mResources = context.getResources();
+
+ mNotificationManager =
+ (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
+
+ mIconSize = mResources.getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_height);
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
+
+
+ // determine the optimal preview size
+ int panelWidth = 0;
+ try {
+ panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
+ } catch (Resources.NotFoundException e) {
+ }
+ if (panelWidth <= 0) {
+ // includes notification_panel_width==match_parent (-1)
+ panelWidth = displayMetrics.widthPixels;
+ }
+ mPreviewWidth = panelWidth;
+ mPreviewHeight = mResources.getDimensionPixelSize(R.dimen.notification_max_height);
+
+ // Setup the notification
+ mNotificationStyle = new Notification.BigPictureStyle();
+ }
+
+ /**
+ * Resets the notification builders.
+ */
+ public void reset() {
+ // The public notification will show similar info but with the actual screenshot omitted
+ mPublicNotificationBuilder =
+ new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
+ mNotificationBuilder =
+ new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
+ }
+
+ /**
+ * Sets the current screenshot bitmap.
+ *
+ * @param image the bitmap of the current screenshot (used for preview)
+ */
+ public void setImage(Bitmap image) {
+ // Create the large notification icon
+ int imageWidth = image.getWidth();
+ int imageHeight = image.getHeight();
+
+ Paint paint = new Paint();
+ ColorMatrix desat = new ColorMatrix();
+ desat.setSaturation(0.25f);
+ paint.setColorFilter(new ColorMatrixColorFilter(desat));
+ Matrix matrix = new Matrix();
+ int overlayColor = 0x40FFFFFF;
+
+ matrix.setTranslate((mPreviewWidth - imageWidth) / 2f, (mPreviewHeight - imageHeight) / 2f);
+
+ Bitmap picture = generateAdjustedHwBitmap(
+ image, mPreviewWidth, mPreviewHeight, matrix, paint, overlayColor);
+
+ mNotificationStyle.bigPicture(picture.createAshmemBitmap());
+
+ // Note, we can't use the preview for the small icon, since it is non-square
+ float scale = (float) mIconSize / Math.min(imageWidth, imageHeight);
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(
+ (mIconSize - (scale * imageWidth)) / 2,
+ (mIconSize - (scale * imageHeight)) / 2);
+ Bitmap icon =
+ generateAdjustedHwBitmap(image, mIconSize, mIconSize, matrix, paint, overlayColor);
+
+ /**
+ * NOTE: The following code prepares the notification builder for updating the
+ * notification after the screenshot has been written to disk.
+ */
+
+ // On the tablet, the large icon makes the notification appear as if it is clickable
+ // (and on small devices, the large icon is not shown) so defer showing the large icon
+ // until we compose the final post-save notification below.
+ mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
+ // But we still don't set it for the expanded view, allowing the smallIcon to show here.
+ mNotificationStyle.bigLargeIcon((Bitmap) null);
+ }
+
+ /**
+ * Shows a notification to inform the user that a screenshot is currently being saved.
+ */
+ public void showSavingScreenshotNotification() {
+ final long now = System.currentTimeMillis();
+
+ mPublicNotificationBuilder
+ .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
+ .setSmallIcon(R.drawable.stat_notify_image)
+ .setCategory(Notification.CATEGORY_PROGRESS)
+ .setWhen(now)
+ .setShowWhen(true)
+ .setColor(mResources.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true);
+
+ mNotificationBuilder
+ .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
+ .setSmallIcon(R.drawable.stat_notify_image)
+ .setWhen(now)
+ .setShowWhen(true)
+ .setColor(mResources.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setStyle(mNotificationStyle)
+ .setPublicVersion(mPublicNotificationBuilder.build());
+ mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
+ SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true);
+
+ mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
+ mNotificationBuilder.build());
+ }
+
+ /**
+ * Shows a notification with the saved screenshot and actions that can be taken with it.
+ *
+ * @param imageUri URI for the saved image
+ * @param actions a list of notification actions which can be taken
+ */
+ public void showScreenshotActionsNotification(
+ Uri imageUri,
+ List<Notification.Action> actions) {
+ for (Notification.Action action : actions) {
+ mNotificationBuilder.addAction(action);
+ }
+
+ // Create the intent to show the screenshot in gallery
+ Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+ launchIntent.setDataAndType(imageUri, "image/png");
+ launchIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ final long now = System.currentTimeMillis();
+
+ // Update the text and the icon for the existing notification
+ mPublicNotificationBuilder
+ .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
+ .setContentText(mResources.getString(R.string.screenshot_saved_text))
+ .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+ .setWhen(now)
+ .setAutoCancel(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ mNotificationBuilder
+ .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
+ .setContentText(mResources.getString(R.string.screenshot_saved_text))
+ .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+ .setWhen(now)
+ .setAutoCancel(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setPublicVersion(mPublicNotificationBuilder.build())
+ .setFlag(Notification.FLAG_NO_CLEAR, false);
+
+ mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
+ mNotificationBuilder.build());
+ }
+
+ /**
+ * Sends a notification that the screenshot capture has failed.
+ */
+ public void notifyScreenshotError(int msgResId) {
+ Resources res = mContext.getResources();
+ String errorMsg = res.getString(msgResId);
+
+ // Repurpose the existing notification to notify the user of the error
+ Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
+ .setTicker(res.getString(R.string.screenshot_failed_title))
+ .setContentTitle(res.getString(R.string.screenshot_failed_title))
+ .setContentText(errorMsg)
+ .setSmallIcon(R.drawable.stat_notify_image_error)
+ .setWhen(System.currentTimeMillis())
+ .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
+ .setCategory(Notification.CATEGORY_ERROR)
+ .setAutoCancel(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ final Intent intent =
+ dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ if (intent != null) {
+ final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ mContext, 0, intent, 0, null, UserHandle.CURRENT);
+ b.setContentIntent(pendingIntent);
+ }
+
+ SystemUI.overrideNotificationAppName(mContext, b, true);
+
+ Notification n = new Notification.BigTextStyle(b)
+ .bigText(errorMsg)
+ .build();
+ mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
+ }
+
+ /**
+ * Cancels the current screenshot notification.
+ */
+ public void cancelNotification() {
+ mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
+ }
+
+ /**
+ * Generates a new hardware bitmap with specified values, copying the content from the
+ * passed in bitmap.
+ */
+ private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
+ Paint paint, int color) {
+ Picture picture = new Picture();
+ Canvas canvas = picture.beginRecording(width, height);
+ canvas.drawColor(color);
+ canvas.drawBitmap(bitmap, matrix, paint);
+ picture.endRecording();
+ return Bitmap.createBitmap(picture);
+ }
+
+ static void cancelScreenshotNotification(Context context) {
+ final NotificationManager nm =
+ (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
index fc2a1e4..522f729 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
@@ -16,10 +16,10 @@
package com.android.systemui.screenshot;
-import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.view.WindowManager;
import com.android.systemui.R;
@@ -32,9 +32,9 @@
@Override
public void onReceive(final Context context, Intent intent) {
// Show a message that we've failed to save the image to disk
- NotificationManager nm = (NotificationManager)
- context.getSystemService(Context.NOTIFICATION_SERVICE);
- GlobalScreenshot.notifyScreenshotError(context, nm,
- R.string.screenshot_failed_to_save_unknown_text);
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ ScreenshotNotificationsController controller =
+ new ScreenshotNotificationsController(context, wm);
+ controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
new file mode 100644
index 0000000..e76e37e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -0,0 +1,139 @@
+/*
+ * 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.screenshot;
+
+import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Collects the static functions for retrieving and acting on smart actions.
+ */
+public class ScreenshotSmartActions {
+ private static final String TAG = "ScreenshotSmartActions";
+
+ @VisibleForTesting
+ static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
+ Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+ boolean smartActionsEnabled, boolean isManagedProfile) {
+ if (!smartActionsEnabled) {
+ Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+ if (image.getConfig() != Bitmap.Config.HARDWARE) {
+ Slog.w(TAG, String.format(
+ "Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
+ image.getConfig()));
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+
+ Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
+ CompletableFuture<List<Notification.Action>> smartActionsFuture;
+ long startTimeMs = SystemClock.uptimeMillis();
+ try {
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ ComponentName componentName =
+ (runningTask != null && runningTask.topActivity != null)
+ ? runningTask.topActivity
+ : new ComponentName("", "");
+ smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
+ componentName,
+ isManagedProfile);
+ } catch (Throwable e) {
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
+ Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
+ waitTimeMs);
+ }
+ return smartActionsFuture;
+ }
+
+ @VisibleForTesting
+ static List<Notification.Action> getSmartActions(String screenshotId,
+ CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
+ ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
+ long startTimeMs = SystemClock.uptimeMillis();
+ try {
+ List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
+ TimeUnit.MILLISECONDS);
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ Slog.d(TAG, String.format("Got %d smart actions. Wait time: %d ms",
+ actions.size(), waitTimeMs));
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
+ waitTimeMs);
+ return actions;
+ } catch (Throwable e) {
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ Slog.e(TAG, String.format("Error getting smart actions. Wait time: %d ms", waitTimeMs),
+ e);
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
+ (e instanceof TimeoutException)
+ ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
+ : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ status, waitTimeMs);
+ return Collections.emptyList();
+ }
+ }
+
+ static void notifyScreenshotOp(String screenshotId,
+ ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
+ try {
+ smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
+ }
+ }
+
+ static void notifyScreenshotAction(Context context, String screenshotId, String action,
+ boolean isSmartAction) {
+ try {
+ ScreenshotNotificationSmartActionsProvider provider =
+ SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
+ context, THREAD_POOL_EXECUTOR, new Handler());
+ provider.notifyAction(screenshotId, action, isSmartAction);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 6243b4b..29b96a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -42,14 +42,11 @@
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
- Consumer<Uri> finisher = new Consumer<Uri>() {
- @Override
- public void accept(Uri uri) {
- Message reply = Message.obtain(null, 1, uri);
- try {
- callback.send(reply);
- } catch (RemoteException e) {
- }
+ Consumer<Uri> finisher = uri -> {
+ Message reply = Message.obtain(null, 1, uri);
+ try {
+ callback.send(reply);
+ } catch (RemoteException e) {
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 9f79785..077d260 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -97,7 +98,7 @@
if (!mReceiverRegistered) {
mCurrentUserId = ActivityManager.getCurrentUser();
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, filter);
+ mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
mReceiverRegistered = true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index bb34a87..325af24 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -23,6 +23,7 @@
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -40,12 +41,14 @@
import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsets;
@@ -292,7 +295,7 @@
R.integer.long_press_dock_anim_duration);
mGrowRecents = getResources().getBoolean(R.bool.recents_grow_in_multiwindow);
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.3f);
+ mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.3f);
updateDisplayInfo();
boolean landscape = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
@@ -321,6 +324,16 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if (isAttachedToWindow()
+ && ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) {
+ // Our window doesn't cover entire display, so we use the display frame to re-calculate
+ // the insets.
+ final InsetsState state = getWindowInsetsController().getState();
+ insets = state.calculateInsets(state.getDisplayFrame(), insets.isRound(),
+ insets.shouldAlwaysConsumeSystemBars(), insets.getDisplayCutout(),
+ null /* legacyContentInsets */, null /* legacyStableInsets */,
+ SOFT_INPUT_ADJUST_NOTHING, null /* typeSideMap */);
+ }
if (mStableInsets.left != insets.getStableInsetLeft()
|| mStableInsets.top != insets.getStableInsetTop()
|| mStableInsets.right != insets.getStableInsetRight()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index d427260..525b5b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar;
import android.animation.Animator;
-import android.content.Context;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
@@ -25,7 +25,8 @@
import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.phone.StatusBar;
+
+import javax.inject.Inject;
/**
* Utility class to calculate general fling animation when the finger is released.
@@ -56,8 +57,8 @@
private float mCachedStartGradient = -1;
private float mCachedVelocityFactor = -1;
- public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
- this(ctx, maxLengthSeconds, 0.0f);
+ public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds) {
+ this(displayMetrics, maxLengthSeconds, 0.0f);
}
/**
@@ -66,8 +67,9 @@
* the end of the animation. 0 means it's at the beginning and no
* acceleration will take place.
*/
- public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor) {
- this(ctx, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f);
+ public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds,
+ float speedUpFactor) {
+ this(displayMetrics, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f);
}
/**
@@ -79,8 +81,8 @@
* is provided, the value is automatically calculated.
* @param y2 the y value to take for the second point of the bezier spline
*/
- public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2,
- float y2) {
+ public FlingAnimationUtils(DisplayMetrics displayMetrics, float maxLengthSeconds,
+ float speedUpFactor, float x2, float y2) {
mMaxLengthSeconds = maxLengthSeconds;
mSpeedUpFactor = speedUpFactor;
if (x2 < 0) {
@@ -92,10 +94,8 @@
}
mY2 = y2;
- mMinVelocityPxPerSecond
- = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
- mHighVelocityPxPerSecond
- = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
+ mMinVelocityPxPerSecond = MIN_VELOCITY_DP_PER_SECOND * displayMetrics.density;
+ mHighVelocityPxPerSecond = HIGH_VELOCITY_DP_PER_SECOND * displayMetrics.density;
}
/**
@@ -365,4 +365,41 @@
long duration;
}
+ public static class Builder {
+ private final DisplayMetrics mDisplayMetrics;
+ float mMaxLengthSeconds;
+ float mSpeedUpFactor = 0.0f;
+ float mX2 = -1.0f;
+ float mY2 = 1.0f;
+
+ @Inject
+ public Builder(DisplayMetrics displayMetrics) {
+ mDisplayMetrics = displayMetrics;
+ }
+
+ public Builder setMaxLengthSeconds(float maxLengthSeconds) {
+ mMaxLengthSeconds = maxLengthSeconds;
+ return this;
+ }
+
+ public Builder setSpeedUpFactor(float speedUpFactor) {
+ mSpeedUpFactor = speedUpFactor;
+ return this;
+ }
+
+ public Builder setX2(float x2) {
+ mX2 = x2;
+ return this;
+ }
+
+ public Builder setY2(float y2) {
+ mY2 = y2;
+ return this;
+ }
+
+ public FlingAnimationUtils build() {
+ return new FlingAnimationUtils(mDisplayMetrics, mMaxLengthSeconds, mSpeedUpFactor,
+ mX2, mY2);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 6adaa0d..2c29635 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -138,7 +138,8 @@
mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_affordance_min_background_radius);
mColorInterpolator = new ArgbEvaluator();
- mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.3f);
+ mFlingAnimationUtils = new FlingAnimationUtils(mContext.getResources().getDisplayMetrics(),
+ 0.3f);
a.recycle();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 61043fb..a8188b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -35,7 +35,7 @@
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.systemui.Dependency;
import com.android.systemui.assist.AssistHandleViewController;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
@@ -65,7 +65,7 @@
SparseArray<NavigationBarFragment> mNavigationBars = new SparseArray<>();
@Inject
- public NavigationBarController(Context context, @MainHandler Handler handler,
+ public NavigationBarController(Context context, @Main Handler handler,
CommandQueue commandQueue) {
mContext = context;
mHandler = handler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 9dcfb6a..8dd801b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -19,9 +19,7 @@
import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
-import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -32,12 +30,11 @@
import android.service.notification.StatusBarNotification;
import android.util.Log;
-import com.android.systemui.dagger.qualifiers.MainHandler;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import java.util.ArrayList;
+import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -51,33 +48,35 @@
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
- // Dependencies:
- private final NotificationEntryManager mEntryManager;
- private final NotificationGroupManager mGroupManager;
-
private final Context mContext;
+ private final NotificationManager mNotificationManager;
private final Handler mMainHandler;
+ private final List<NotifServiceListener> mNotificationListeners = new ArrayList<>();
private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
- @Nullable private NotifServiceListener mDownstreamListener;
@Inject
- public NotificationListener(Context context, @MainHandler Handler mainHandler,
- NotificationEntryManager notificationEntryManager,
- NotificationGroupManager notificationGroupManager) {
+ public NotificationListener(
+ Context context,
+ NotificationManager notificationManager,
+ @Main Handler mainHandler) {
mContext = context;
+ mNotificationManager = notificationManager;
mMainHandler = mainHandler;
- mEntryManager = notificationEntryManager;
- mGroupManager = notificationGroupManager;
}
+ /** Registers a listener that's notified when notifications are added/removed/etc. */
+ public void addNotificationListener(NotifServiceListener listener) {
+ if (mNotificationListeners.contains(listener)) {
+ throw new IllegalArgumentException("Listener is already added");
+ }
+ mNotificationListeners.add(listener);
+ }
+
+ /** Registers a listener that's notified when any notification-related settings change. */
public void addNotificationSettingsListener(NotificationSettingsListener listener) {
mSettingsListeners.add(listener);
}
- public void setDownstreamListener(NotifServiceListener downstreamListener) {
- mDownstreamListener = downstreamListener;
- }
-
@Override
public void onListenerConnected() {
if (DEBUG) Log.d(TAG, "onListenerConnected");
@@ -89,15 +88,25 @@
}
final RankingMap currentRanking = getCurrentRanking();
mMainHandler.post(() -> {
+ // There's currently a race condition between the calls to getActiveNotifications() and
+ // getCurrentRanking(). It's possible for the ranking that we store here to not contain
+ // entries for every notification in getActiveNotifications(). To prevent downstream
+ // crashes, we temporarily fill in these missing rankings with stubs.
+ // See b/146011844 for long-term fix
+ final List<Ranking> newRankings = new ArrayList<>();
for (StatusBarNotification sbn : notifications) {
- if (mDownstreamListener != null) {
- mDownstreamListener.onNotificationPosted(sbn, currentRanking);
+ newRankings.add(getRankingOrTemporaryStandIn(currentRanking, sbn.getKey()));
+ }
+ final RankingMap completeMap = new RankingMap(newRankings.toArray(new Ranking[0]));
+
+ for (StatusBarNotification sbn : notifications) {
+ for (NotifServiceListener listener : mNotificationListeners) {
+ listener.onNotificationPosted(sbn, completeMap);
}
- mEntryManager.addNotification(sbn, currentRanking);
}
});
- NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
- onSilentStatusBarIconsVisibilityChanged(noMan.shouldHideSilentStatusBarIcons());
+ onSilentStatusBarIconsVisibilityChanged(
+ mNotificationManager.shouldHideSilentStatusBarIcons());
}
@Override
@@ -108,34 +117,8 @@
mMainHandler.post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
- if (mDownstreamListener != null) {
- mDownstreamListener.onNotificationPosted(sbn, rankingMap);
- }
-
- String key = sbn.getKey();
- boolean isUpdate = mEntryManager.getActiveNotificationUnfiltered(key) != null;
- // In case we don't allow child notifications, we ignore children of
- // notifications that have a summary, since` we're not going to show them
- // anyway. This is true also when the summary is canceled,
- // because children are automatically canceled by NoMan in that case.
- if (!ENABLE_CHILD_NOTIFICATIONS
- && mGroupManager.isChildInGroupWithSummary(sbn)) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
- }
-
- // Remove existing notification to avoid stale data.
- if (isUpdate) {
- mEntryManager.removeNotification(key, rankingMap, UNDEFINED_DISMISS_REASON);
- } else {
- mEntryManager.updateRanking(rankingMap, "onNotificationPosted");
- }
- return;
- }
- if (isUpdate) {
- mEntryManager.updateNotification(sbn, rankingMap);
- } else {
- mEntryManager.addNotification(sbn, rankingMap);
+ for (NotifServiceListener listener : mNotificationListeners) {
+ listener.onNotificationPosted(sbn, rankingMap);
}
});
}
@@ -146,12 +129,10 @@
int reason) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
- final String key = sbn.getKey();
mMainHandler.post(() -> {
- if (mDownstreamListener != null) {
- mDownstreamListener.onNotificationRemoved(sbn, rankingMap, reason);
+ for (NotifServiceListener listener : mNotificationListeners) {
+ listener.onNotificationRemoved(sbn, rankingMap, reason);
}
- mEntryManager.removeNotification(key, rankingMap, reason);
});
}
}
@@ -167,10 +148,9 @@
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
mMainHandler.post(() -> {
- if (mDownstreamListener != null) {
- mDownstreamListener.onNotificationRankingUpdate(rankingMap);
+ for (NotifServiceListener listener : mNotificationListeners) {
+ listener.onNotificationRankingUpdate(r);
}
- mEntryManager.updateNotificationRanking(r);
});
}
}
@@ -192,6 +172,35 @@
}
}
+ private static Ranking getRankingOrTemporaryStandIn(RankingMap rankingMap, String key) {
+ Ranking ranking = new Ranking();
+ if (!rankingMap.getRanking(key, ranking)) {
+ ranking.populate(
+ key,
+ 0,
+ false,
+ 0,
+ 0,
+ 0,
+ null,
+ null,
+ null,
+ new ArrayList<>(),
+ new ArrayList<>(),
+ false,
+ 0,
+ false,
+ 0,
+ false,
+ new ArrayList<>(),
+ new ArrayList<>(),
+ false,
+ false
+ );
+ }
+ return ranking;
+ }
+
public interface NotificationSettingsListener {
default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 0f3f6b7..2e369b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -48,7 +48,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
@@ -190,7 +190,7 @@
IStatusBarService iStatusBarService,
KeyguardManager keyguardManager,
StatusBarStateController statusBarStateController,
- @MainHandler Handler mainHandler,
+ @Main Handler mainHandler,
DeviceProvisionedController deviceProvisionedController,
KeyguardStateController keyguardStateController) {
mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index f6f3ac1..43d0399 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -50,7 +50,7 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -263,7 +263,7 @@
NotificationEntryManager notificationEntryManager,
Lazy<StatusBar> statusBarLazy,
StatusBarStateController statusBarStateController,
- @MainHandler Handler mainHandler,
+ @Main Handler mainHandler,
RemoteInputUriController remoteInputUriController) {
mContext = context;
mLockscreenUserManager = lockscreenUserManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
deleted file mode 100644
index 1ac8198..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUpdateHandler.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-
-/**
- * Interface for accepting notification updates from {@link NotificationListener}.
- */
-public interface NotificationUpdateHandler {
- /**
- * Add a new notification and update the current notification ranking map.
- *
- * @param notification Notification to add
- * @param ranking RankingMap to update with
- */
- void addNotification(StatusBarNotification notification,
- NotificationListenerService.RankingMap ranking);
-
- /**
- * Remove a notification and update the current notification ranking map.
- *
- * @param key Key identifying the notification to remove
- * @param ranking RankingMap to update with
- * @param reason why the notification is being removed, e.g.
- * {@link NotificationListenerService#REASON_CANCEL}.
- */
- void removeNotification(String key, NotificationListenerService.RankingMap ranking, int reason);
-
- /**
- * Update a given notification and the current notification ranking map.
- *
- * @param notification Updated notification
- * @param ranking RankingMap to update with
- */
- void updateNotification(StatusBarNotification notification,
- NotificationListenerService.RankingMap ranking);
-
- /**
- * Update with a new notification ranking map.
- *
- * @param ranking RankingMap to update with
- */
- void updateNotificationRanking(NotificationListenerService.RankingMap ranking);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 4204f68..6b0b5df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -27,7 +27,7 @@
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -37,7 +37,6 @@
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.util.Assert;
import com.android.systemui.util.Utils;
@@ -49,8 +48,6 @@
import javax.inject.Inject;
import javax.inject.Singleton;
-import dagger.Lazy;
-
/**
* NotificationViewHierarchyManager manages updating the view hierarchy of notification views based
* on their group structure. For example, if a notification becomes bundled with another,
@@ -75,9 +72,6 @@
private final SysuiStatusBarStateController mStatusBarStateController;
private final NotificationEntryManager mEntryManager;
- // Lazy
- private final Lazy<ShadeController> mShadeController;
-
/**
* {@code true} if notifications not part of a group should by default be rendered in their
* expanded state. If {@code false}, then only the first notification will be expanded if
@@ -99,13 +93,12 @@
private boolean mIsHandleDynamicPrivacyChangeScheduled;
@Inject
- public NotificationViewHierarchyManager(Context context, @MainHandler Handler mainHandler,
+ public NotificationViewHierarchyManager(Context context, @Main Handler mainHandler,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationGroupManager groupManager,
VisualStabilityManager visualStabilityManager,
StatusBarStateController statusBarStateController,
NotificationEntryManager notificationEntryManager,
- Lazy<ShadeController> shadeController,
KeyguardBypassController bypassController,
BubbleController bubbleController,
DynamicPrivacyController privacyController) {
@@ -117,7 +110,6 @@
mVisualStabilityManager = visualStabilityManager;
mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
mEntryManager = notificationEntryManager;
- mShadeController = shadeController;
Resources res = context.getResources();
mAlwaysExpandNonGroupedNotification =
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 49bed15..93f5805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -54,12 +54,13 @@
import com.android.systemui.DockedStackExistsListener;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.NotificationChannels;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -74,16 +75,18 @@
public static final int NUM_TASKS_FOR_INSTANT_APP_INFO = 5;
private final Handler mHandler = new Handler();
- private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+ private final Executor mUiBgExecutor;
private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
private final CommandQueue mCommandQueue;
private boolean mDockedStackExists;
private KeyguardStateController mKeyguardStateController;
@Inject
- public InstantAppNotifier(Context context, CommandQueue commandQueue) {
+ public InstantAppNotifier(Context context, CommandQueue commandQueue,
+ @UiBackground Executor uiBgExecutor) {
super(context);
mCommandQueue = commandQueue;
+ mUiBgExecutor = uiBgExecutor;
}
@Override
@@ -151,7 +154,7 @@
private void updateForegroundInstantApps() {
NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
IPackageManager pm = AppGlobals.getPackageManager();
- mUiOffloadThread.submit(
+ mUiBgExecutor.execute(
() -> {
ArraySet<Pair<String, Integer>> notifs = new ArraySet<>(mCurrentNotifs);
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index 8ecf2b8..c8b34f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -26,6 +26,8 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.util.Optional;
+
/**
* Click handler for generic clicks on notifications. Clicks on specific areas (expansion caret,
* app ops icon, etc) are handled elsewhere.
@@ -33,11 +35,11 @@
public final class NotificationClicker implements View.OnClickListener {
private static final String TAG = "NotificationClicker";
- private final StatusBar mStatusBar;
+ private final Optional<StatusBar> mStatusBar;
private final BubbleController mBubbleController;
private final NotificationActivityStarter mNotificationActivityStarter;
- public NotificationClicker(StatusBar statusBar,
+ public NotificationClicker(Optional<StatusBar> statusBar,
BubbleController bubbleController,
NotificationActivityStarter notificationActivityStarter) {
mStatusBar = statusBar;
@@ -52,7 +54,8 @@
return;
}
- mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK");
+ mStatusBar.ifPresent(statusBar -> statusBar.wakeUpIfDozing(
+ SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
final StatusBarNotification sbn = row.getEntry().getSbn();
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 7a58097..43b9fbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -33,11 +33,12 @@
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.NotificationUpdateHandler;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
@@ -94,7 +95,6 @@
public class NotificationEntryManager implements
Dumpable,
NotificationContentInflater.InflationCallback,
- NotificationUpdateHandler,
VisualStabilityManager.Callback {
private static final String TAG = "NotificationEntryMgr";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -176,6 +176,11 @@
mKeyguardEnvironment = keyguardEnvironment;
}
+ /** Once called, the NEM will start processing notification events from system server. */
+ public void attach(NotificationListener notificationListener) {
+ notificationListener.addNotificationListener(mNotifListener);
+ }
+
/** Adds a {@link NotificationEntryListener}. */
public void addNotificationEntryListener(NotificationEntryListener listener) {
mNotificationEntryListeners.add(listener);
@@ -267,14 +272,13 @@
NotificationEntry entry = mPendingNotifications.get(key);
entry.abortTask();
mPendingNotifications.remove(key);
- mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry.getSbn(), null,
- "PendingNotification aborted. " + reason);
+ mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry, "PendingNotification aborted"
+ + " reason=" + reason);
}
NotificationEntry addedEntry = getActiveNotificationUnfiltered(key);
if (addedEntry != null) {
addedEntry.abortTask();
- mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getSbn(),
- null, reason);
+ mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getKey() + " " + reason);
}
}
@@ -321,6 +325,36 @@
}
}
+ private final NotifServiceListener mNotifListener = new NotifServiceListener() {
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ final boolean isUpdate = mActiveNotifications.containsKey(sbn.getKey());
+ if (isUpdate) {
+ updateNotification(sbn, rankingMap);
+ } else {
+ addNotification(sbn, rankingMap);
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ removeNotification(sbn.getKey(), rankingMap, UNDEFINED_DISMISS_REASON);
+ }
+
+ @Override
+ public void onNotificationRemoved(
+ StatusBarNotification sbn,
+ RankingMap rankingMap,
+ int reason) {
+ removeNotification(sbn.getKey(), rankingMap, reason);
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(RankingMap rankingMap) {
+ updateNotificationRanking(rankingMap);
+ }
+ };
+
/**
* Equivalent to the old NotificationData#add
* @param entry - an entry which is prepared for display
@@ -347,7 +381,6 @@
}
- @Override
public void removeNotification(String key, RankingMap ranking,
int reason) {
removeNotificationInternal(key, ranking, obtainVisibility(key), false /* forceRemove */,
@@ -501,13 +534,12 @@
abortExistingInflation(key, "addNotification");
mPendingNotifications.put(key, entry);
- mNotifLog.log(NotifEvent.NOTIF_ADDED, entry.getSbn());
+ mNotifLog.log(NotifEvent.NOTIF_ADDED, entry);
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPendingEntryAdded(entry);
}
}
- @Override
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
@@ -536,7 +568,7 @@
entry.setSbn(notification);
mGroupManager.onEntryUpdated(entry, oldSbn);
- mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry.getSbn(), entry.getRanking());
+ mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry);
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPreEntryUpdated(entry);
}
@@ -557,7 +589,6 @@
}
}
- @Override
public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
try {
updateNotificationInternal(notification, ranking);
@@ -577,7 +608,6 @@
}
}
- @Override
public void updateNotificationRanking(RankingMap rankingMap) {
List<NotificationEntry> entries = new ArrayList<>();
entries.addAll(getVisibleNotifications());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 6c61923..3afd623 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -32,7 +32,6 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -120,11 +119,6 @@
return true;
}
- if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
- && mGroupManager.isChildInGroupWithSummary(sbn)) {
- return true;
- }
-
if (getFsc().isDisclosureNotification(sbn)
&& !getFsc().isDisclosureNeededForUser(sbn.getUserId())) {
// this is a foreground-service disclosure for a user that does not need to show one
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 1b57308..99718abb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -23,7 +23,7 @@
import androidx.collection.ArraySet;
import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -62,7 +62,7 @@
@Inject
public VisualStabilityManager(
- NotificationEntryManager notificationEntryManager, @MainHandler Handler handler) {
+ NotificationEntryManager notificationEntryManager, @Main Handler handler) {
mHandler = handler;
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 9ae3882..ec1efa5 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,6 +24,7 @@
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
@@ -57,15 +58,22 @@
@VisibleForTesting
public void setSummary(@Nullable NotificationEntry summary) {
- mSummary = summary;
+ if (!Objects.equals(mSummary, summary)) {
+ mSummary = summary;
+ onGroupingUpdated();
+ }
}
void clearChildren() {
- mChildren.clear();
+ if (mChildren.size() != 0) {
+ mChildren.clear();
+ onGroupingUpdated();
+ }
}
void addChild(NotificationEntry child) {
mChildren.add(child);
+ onGroupingUpdated();
}
void sortChildren(Comparator<? super NotificationEntry> c) {
@@ -77,4 +85,5 @@
}
public static final GroupEntry ROOT_ENTRY = new GroupEntry("<root>");
+
}
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 6ce7fd9..601b3e0 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,7 +18,14 @@
import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
+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;
/**
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
@@ -26,14 +33,23 @@
*/
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;
+ // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
+ // replaced in GroupEntry and NotifListBuilderImpl
+ private final NotificationGroupManager mGroupManager;
+
ListEntry(String key) {
mKey = key;
+
+ // TODO: (b/145659174) remove
+ mGroupManager = Dependency.get(NotificationGroupManager.class);
}
public String getKey() {
@@ -51,9 +67,12 @@
return mParent;
}
- @VisibleForTesting
- public void setParent(@Nullable GroupEntry parent) {
- mParent = parent;
+ void setParent(@Nullable GroupEntry parent) {
+ if (!Objects.equals(mParent, parent)) {
+ invalidateParent();
+ mParent = parent;
+ onGroupingUpdated();
+ }
}
@Nullable public GroupEntry getPreviousParent() {
@@ -72,4 +91,58 @@
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 6f085c0..7f85c88 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
@@ -116,7 +116,7 @@
}
mAttached = true;
- listenerService.setDownstreamListener(mNotifServiceListener);
+ listenerService.addNotificationListener(mNotifServiceListener);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
index 21a4b4f..f0a003f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
@@ -20,16 +20,18 @@
import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_PENDING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
-import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FILTERING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_GROUP_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_PRE_RENDER_FILTERING;
+import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_RESETTING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.util.ArrayMap;
-import android.util.Log;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
@@ -40,6 +42,8 @@
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.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;
@@ -59,19 +63,20 @@
@MainThread
@Singleton
public class NotifListBuilderImpl implements NotifListBuilder {
-
private final SystemClock mSystemClock;
+ private final NotifLog mNotifLog;
- private final List<ListEntry> mNotifList = new ArrayList<>();
+ private List<ListEntry> mNotifList = new ArrayList<>();
+ private List<ListEntry> mNewNotifList = new ArrayList<>();
private final PipelineState mPipelineState = new PipelineState();
private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
- private final List<ListEntry> mNewEntries = new ArrayList<>();
private int mIterationCount = 0;
- private final List<NotifFilter> mNotifFilters = new ArrayList<>();
+ private final List<NotifFilter> mNotifPreGroupFilters = new ArrayList<>();
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();
@@ -86,9 +91,10 @@
private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
@Inject
- public NotifListBuilderImpl(SystemClock systemClock) {
+ public NotifListBuilderImpl(SystemClock systemClock, NotifLog notifLog) {
Assert.isMainThread();
mSystemClock = systemClock;
+ mNotifLog = notifLog;
}
/**
@@ -136,12 +142,21 @@
}
@Override
- public void addFilter(NotifFilter filter) {
+ public void addPreGroupFilter(NotifFilter filter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mNotifFilters.add(filter);
- filter.setInvalidationListener(this::onFilterInvalidated);
+ mNotifPreGroupFilters.add(filter);
+ filter.setInvalidationListener(this::onPreGroupFilterInvalidated);
+ }
+
+ @Override
+ public void addPreRenderFilter(NotifFilter filter) {
+ Assert.isMainThread();
+ mPipelineState.requireState(STATE_IDLE);
+
+ mNotifPreRenderFilters.add(filter);
+ filter.setInvalidationListener(this::onPreRenderFilterInvalidated);
}
@Override
@@ -193,28 +208,28 @@
Assert.isMainThread();
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
- Log.i(TAG, "Build request received from NotifCollection");
+ mNotifLog.log(NotifEvent.ON_BUILD_LIST, "Request received from "
+ + "NotifCollection");
mAllEntries = entries;
buildList();
}
};
- private void onFilterInvalidated(NotifFilter filter) {
+ private void onPreGroupFilterInvalidated(NotifFilter filter) {
Assert.isMainThread();
- // TODO: Convert these log statements (here and elsewhere) into timeline logging
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.PRE_GROUP_FILTER_INVALIDATED, String.format(
"Filter \"%s\" invalidated; pipeline state is %d",
filter.getName(),
mPipelineState.getState()));
- rebuildListIfBefore(STATE_FILTERING);
+ rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
}
private void onPromoterInvalidated(NotifPromoter filter) {
Assert.isMainThread();
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.PROMOTER_INVALIDATED, String.format(
"NotifPromoter \"%s\" invalidated; pipeline state is %d",
filter.getName(),
mPipelineState.getState()));
@@ -225,7 +240,7 @@
private void onSectionsProviderInvalidated(SectionsProvider provider) {
Assert.isMainThread();
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format(
"Sections provider \"%s\" invalidated; pipeline state is %d",
provider.getName(),
mPipelineState.getState()));
@@ -233,10 +248,21 @@
rebuildListIfBefore(STATE_SORTING);
}
+ private void onPreRenderFilterInvalidated(NotifFilter filter) {
+ Assert.isMainThread();
+
+ mNotifLog.log(NotifEvent.PRE_RENDER_FILTER_INVALIDATED, String.format(
+ "Filter \"%s\" invalidated; pipeline state is %d",
+ filter.getName(),
+ mPipelineState.getState()));
+
+ rebuildListIfBefore(STATE_PRE_RENDER_FILTERING);
+ }
+
private void onNotifComparatorInvalidated(NotifComparator comparator) {
Assert.isMainThread();
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.COMPARATOR_INVALIDATED, String.format(
"Comparator \"%s\" invalidated; pipeline state is %d",
comparator.getName(),
mPipelineState.getState()));
@@ -245,6 +271,17 @@
}
/**
+ * Points mNotifList to the list stored in mNewNotifList.
+ * Reuses the (emptied) mNotifList as mNewNotifList.
+ */
+ private void applyNewNotifList() {
+ mNotifList.clear();
+ List<ListEntry> emptyList = mNotifList;
+ mNotifList = mNewNotifList;
+ mNewNotifList = emptyList;
+ }
+
+ /**
* The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for
* details on our contracts with other code.
*
@@ -254,60 +291,67 @@
* if we detect that behavior, we should crash instantly.
*/
private void buildList() {
- Log.i(TAG, "Starting notif list build #" + mIterationCount + "...");
+ mNotifLog.log(NotifEvent.START_BUILD_LIST, "Run #" + mIterationCount + "...");
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
mPipelineState.setState(STATE_BUILD_STARTED);
- // Step 1: Filtering and initial grouping
- // Filter out any notifs that shouldn't be shown right now and cluster any that are part of
- // a group
- mPipelineState.incrementTo(STATE_FILTERING);
- mNotifList.clear();
- mNewEntries.clear();
- filterAndGroup(mAllEntries, mNotifList, mNewEntries);
- pruneIncompleteGroups(mNotifList, mNewEntries);
+ // Step 1: Reset notification states
+ mPipelineState.incrementTo(STATE_RESETTING);
+ resetNotifs();
- // Step 2: Group transforming
+ // Step 2: Filter out any notifications that shouldn't be shown right now
+ mPipelineState.incrementTo(STATE_PRE_GROUP_FILTERING);
+ filterNotifs(mAllEntries, mNotifList, mNotifPreGroupFilters);
+
+ // Step 3: Group notifications with the same group key and set summaries
+ mPipelineState.incrementTo(STATE_GROUPING);
+ groupNotifs(mNotifList, mNewNotifList);
+ applyNewNotifList();
+ pruneIncompleteGroups(mNotifList);
+
+ // Step 4: Group transforming
// Move some notifs out of their groups and up to top-level (mostly used for heads-upping)
- dispatchOnBeforeTransformGroups(mReadOnlyNotifList, mNewEntries);
+ dispatchOnBeforeTransformGroups(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_TRANSFORMING);
promoteNotifs(mNotifList);
- pruneIncompleteGroups(mNotifList, mNewEntries);
+ pruneIncompleteGroups(mNotifList);
- // Step 3: Sort
+ // Step 5: Sort
// Assign each top-level entry a section, then sort the list by section and then within
// section by our list of custom comparators
dispatchOnBeforeSort(mReadOnlyNotifList);
mPipelineState.incrementTo(STATE_SORTING);
sortList();
- // Step 4: Lock in our group structure and log anything that's changed since the last run
+ // 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
+ mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
+ filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
+ applyNewNotifList();
+ pruneIncompleteGroups(mNotifList);
+
+ // Step 7: Lock in our group structure and log anything that's changed since the last run
mPipelineState.incrementTo(STATE_FINALIZING);
logParentingChanges();
freeEmptyGroups();
- // Step 5: Dispatch the new list, first to any listeners and then to the view layer
- Log.i(TAG, "List finalized, is:\n" + dumpList(mNotifList));
- Log.i(TAG, "Dispatching final list to listeners...");
+ // 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));
dispatchOnBeforeRenderList(mReadOnlyNotifList);
if (mOnRenderListListener != null) {
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
}
- // Step 6: We're done!
- Log.i(TAG, "Notif list build #" + mIterationCount + " completed");
+ // Step 7: We're done!
+ mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE,
+ "Notif list build #" + mIterationCount + " completed");
mPipelineState.setState(STATE_IDLE);
mIterationCount++;
}
- private void filterAndGroup(
- Collection<NotificationEntry> entries,
- List<ListEntry> out,
- List<ListEntry> newlyVisibleEntries) {
-
- long now = mSystemClock.uptimeMillis();
-
+ private void resetNotifs() {
for (GroupEntry group : mGroups.values()) {
group.setPreviousParent(group.getParent());
group.setParent(null);
@@ -315,22 +359,57 @@
group.setSummary(null);
}
- for (NotificationEntry entry : entries) {
+ for (NotificationEntry entry : mAllEntries) {
entry.setPreviousParent(entry.getParent());
entry.setParent(null);
- // See if we should filter out this notification
- boolean shouldFilterOut = applyFilters(entry, now);
- if (shouldFilterOut) {
- continue;
- }
-
if (entry.mFirstAddedIteration == -1) {
entry.mFirstAddedIteration = mIterationCount;
- newlyVisibleEntries.add(entry);
}
+ }
- // Otherwise, group it
+ mNotifList.clear();
+ }
+
+ private void filterNotifs(Collection<? extends ListEntry> entries,
+ List<ListEntry> out, List<NotifFilter> filters) {
+ final long now = mSystemClock.uptimeMillis();
+ for (ListEntry entry : entries) {
+ if (entry instanceof GroupEntry) {
+ final GroupEntry groupEntry = (GroupEntry) entry;
+
+ // apply filter on its summary
+ final NotificationEntry summary = groupEntry.getRepresentativeEntry();
+ if (applyFilters(summary, now, filters)) {
+ groupEntry.setSummary(null);
+ annulAddition(summary);
+ }
+
+ // apply filter on its children
+ final List<NotificationEntry> children = groupEntry.getRawChildren();
+ for (int j = children.size() - 1; j >= 0; j--) {
+ final NotificationEntry child = children.get(j);
+ if (applyFilters(child, now, filters)) {
+ children.remove(child);
+ annulAddition(child);
+ }
+ }
+
+ out.add(groupEntry);
+ } else {
+ if (applyFilters((NotificationEntry) entry, now, filters)) {
+ annulAddition(entry);
+ } else {
+ out.add(entry);
+ }
+ }
+ }
+ }
+
+ private void groupNotifs(List<ListEntry> entries, List<ListEntry> out) {
+ for (ListEntry listEntry : entries) {
+ // since grouping hasn't happened yet, all notifs are NotificationEntries
+ NotificationEntry entry = (NotificationEntry) listEntry;
if (entry.getSbn().isGroup()) {
final String topLevelKey = entry.getSbn().getGroupKey();
@@ -338,7 +417,6 @@
if (group == null) {
group = new GroupEntry(topLevelKey);
group.mFirstAddedIteration = mIterationCount;
- newlyVisibleEntries.add(group);
mGroups.put(topLevelKey, group);
}
if (group.getParent() == null) {
@@ -354,7 +432,7 @@
if (existingSummary == null) {
group.setSummary(entry);
} else {
- Log.w(TAG, String.format(
+ mNotifLog.log(NotifEvent.WARN, String.format(
"Duplicate summary for group '%s': '%s' vs. '%s'",
group.getKey(),
existingSummary.getKey(),
@@ -364,9 +442,9 @@
if (entry.getSbn().getPostTime()
> existingSummary.getSbn().getPostTime()) {
group.setSummary(entry);
- annulAddition(existingSummary, out, newlyVisibleEntries);
+ annulAddition(existingSummary, out);
} else {
- annulAddition(entry, out, newlyVisibleEntries);
+ annulAddition(entry, out);
}
}
} else {
@@ -377,7 +455,8 @@
final String topLevelKey = entry.getKey();
if (mGroups.containsKey(topLevelKey)) {
- Log.wtf(TAG, "Duplicate non-group top-level key: " + topLevelKey);
+ mNotifLog.log(NotifEvent.WARN,
+ "Duplicate non-group top-level key: " + topLevelKey);
} else {
entry.setParent(ROOT_ENTRY);
out.add(entry);
@@ -407,10 +486,7 @@
}
}
- private void pruneIncompleteGroups(
- List<ListEntry> shadeList,
- List<ListEntry> newlyVisibleEntries) {
-
+ private void pruneIncompleteGroups(List<ListEntry> shadeList) {
for (int i = 0; i < shadeList.size(); i++) {
final ListEntry tle = shadeList.get(i);
@@ -427,7 +503,7 @@
shadeList.add(summary);
group.setSummary(null);
- annulAddition(group, shadeList, newlyVisibleEntries);
+ annulAddition(group, shadeList);
} else if (group.getSummary() == null
|| children.size() < MIN_CHILDREN_FOR_GROUP) {
@@ -440,7 +516,7 @@
if (group.getSummary() != null) {
final NotificationEntry summary = group.getSummary();
group.setSummary(null);
- annulAddition(summary, shadeList, newlyVisibleEntries);
+ annulAddition(summary, shadeList);
}
for (int j = 0; j < children.size(); j++) {
@@ -450,7 +526,7 @@
}
children.clear();
- annulAddition(group, shadeList, newlyVisibleEntries);
+ annulAddition(group, shadeList);
}
}
}
@@ -464,10 +540,7 @@
* Before calling this method, the entry must already have been removed from its parent. If
* it's a group, its summary must be null and its children must be empty.
*/
- private void annulAddition(
- ListEntry entry,
- List<ListEntry> shadeList,
- List<ListEntry> newlyVisibleEntries) {
+ private void annulAddition(ListEntry entry, List<ListEntry> shadeList) {
// This function does very little, but if any of its assumptions are violated (and it has a
// lot of them), it will put the system into an inconsistent state. So we check all of them
@@ -504,13 +577,18 @@
}
}
+ annulAddition(entry);
+
+ }
+
+ /**
+ * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
+ * This can happen if the entry is removed from a group that was broken up or if the entry was
+ * filtered out during any of the filtering steps.
+ */
+ private void annulAddition(ListEntry entry) {
entry.setParent(null);
if (entry.mFirstAddedIteration == mIterationCount) {
- if (!newlyVisibleEntries.remove(entry)) {
- throw new IllegalStateException("Cannot late-filter entry " + entry.getKey() + " "
- + entry + " from " + newlyVisibleEntries + " "
- + entry.mFirstAddedIteration);
- }
entry.mFirstAddedIteration = -1;
}
}
@@ -539,7 +617,7 @@
private void logParentingChanges() {
for (NotificationEntry entry : mAllEntries) {
if (entry.getParent() != entry.getPreviousParent()) {
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format(
"%s: parent changed from %s to %s",
entry.getKey(),
entry.getPreviousParent() == null
@@ -550,7 +628,7 @@
}
for (GroupEntry group : mGroups.values()) {
if (group.getParent() != group.getPreviousParent()) {
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format(
"%s: parent changed from %s to %s",
group.getKey(),
group.getPreviousParent() == null
@@ -602,22 +680,22 @@
return cmp;
};
- private boolean applyFilters(NotificationEntry entry, long now) {
- NotifFilter filter = findRejectingFilter(entry, now);
+ private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
+ NotifFilter filter = findRejectingFilter(entry, now, filters);
if (filter != entry.mExcludingFilter) {
if (entry.mExcludingFilter == null) {
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
"%s: filtered out by '%s'",
entry.getKey(),
filter.getName()));
} else if (filter == null) {
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
"%s: no longer filtered out (previous filter was '%s')",
entry.getKey(),
entry.mExcludingFilter.getName()));
} else {
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format(
"%s: filter changed: '%s' -> '%s'",
entry.getKey(),
entry.mExcludingFilter,
@@ -633,9 +711,12 @@
return filter != null;
}
- @Nullable private NotifFilter findRejectingFilter(NotificationEntry entry, long now) {
- for (int i = 0; i < mNotifFilters.size(); i++) {
- NotifFilter filter = mNotifFilters.get(i);
+ @Nullable private static NotifFilter findRejectingFilter(NotificationEntry entry, long now,
+ List<NotifFilter> filters) {
+ final int size = filters.size();
+
+ for (int i = 0; i < size; i++) {
+ NotifFilter filter = filters.get(i);
if (filter.shouldFilterOut(entry, now)) {
return filter;
}
@@ -648,23 +729,22 @@
if (promoter != entry.mNotifPromoter) {
if (entry.mNotifPromoter == null) {
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
"%s: Entry promoted to top level by '%s'",
entry.getKey(),
promoter.getName()));
} else if (promoter == null) {
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
"%s: Entry is no longer promoted to top level (previous promoter was '%s')",
entry.getKey(),
entry.mNotifPromoter.getName()));
} else {
- Log.i(TAG, String.format(
+ mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format(
"%s: Top-level promoter changed: '%s' -> '%s'",
entry.getKey(),
entry.mNotifPromoter,
promoter));
}
-
entry.mNotifPromoter = promoter;
}
@@ -688,12 +768,9 @@
}
}
- private void dispatchOnBeforeTransformGroups(
- List<ListEntry> entries,
- List<ListEntry> newlyVisibleEntries) {
+ private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
- mOnBeforeTransformGroupsListeners.get(i)
- .onBeforeTransformGroups(entries, newlyVisibleEntries);
+ mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
}
}
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 232fb6d..de16ef5 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
@@ -21,7 +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.Notification.EXTRA_MESSAGES;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
@@ -42,7 +41,6 @@
import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Bundle;
-import android.os.Parcelable;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
@@ -92,7 +90,6 @@
private StatusBarNotification mSbn;
private Ranking mRanking;
-
/*
* Bookkeeping members
*/
@@ -120,7 +117,6 @@
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
public CharSequence remoteInputText;
- private final List<Person> mAssociatedPeople = new ArrayList<>();
private Notification.BubbleMetadata mBubbleMetadata;
/**
@@ -157,12 +153,6 @@
*/
private boolean hasSentReply;
- /**
- * 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.
- */
- private boolean mHighPriority;
-
private boolean mSensitive = true;
private Runnable mOnSensitiveChangedListener;
private boolean mAutoHeadsUp;
@@ -212,9 +202,11 @@
+ " doesn't match existing key " + mKey);
}
- mSbn = sbn;
- mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
- updatePeopleList();
+ if (!Objects.equals(mSbn, sbn)) {
+ mSbn = sbn;
+ mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
+ onSbnUpdated();
+ }
}
/**
@@ -239,10 +231,12 @@
+ " doesn't match existing key " + mKey);
}
- mRanking = ranking;
+ if (!Objects.equals(mRanking, ranking)) {
+ mRanking = ranking;
+ onRankingUpdated();
+ }
}
-
/*
* Convenience getters for SBN and Ranking members
*/
@@ -304,49 +298,10 @@
return interruption;
}
- public boolean isHighPriority() {
- return mHighPriority;
- }
-
- public void setIsHighPriority(boolean highPriority) {
- this.mHighPriority = highPriority;
- }
-
public boolean isBubble() {
return (mSbn.getNotification().flags & FLAG_BUBBLE) != 0;
}
- private void updatePeopleList() {
- mAssociatedPeople.clear();
-
- Bundle extras = mSbn.getNotification().extras;
- if (extras == null) {
- return;
- }
-
- List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
-
- if (p != null) {
- mAssociatedPeople.addAll(p);
- }
-
- if (Notification.MessagingStyle.class.equals(
- mSbn.getNotification().getNotificationStyle())) {
- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (!ArrayUtils.isEmpty(messages)) {
- for (Notification.MessagingStyle.Message message :
- Notification.MessagingStyle.Message
- .getMessagesFromBundleArray(messages)) {
- mAssociatedPeople.add(message.getSenderPerson());
- }
- }
- }
- }
-
- boolean hasAssociatedPeople() {
- return mAssociatedPeople.size() > 0;
- }
-
/**
* Returns the data needed for a bubble for this notification, if it exists.
*/
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 48a4882..7010943 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,14 +16,11 @@
package com.android.systemui.statusbar.notification.collection
-import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
-import android.app.NotificationManager.IMPORTANCE_LOW
import android.app.NotificationManager.IMPORTANCE_MIN
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.NotificationListenerService.RankingMap
import android.service.notification.StatusBarNotification
-import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationFilter
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
@@ -105,44 +102,6 @@
return entry.key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN
}
- @VisibleForTesting
- protected fun isHighPriority(entry: NotificationEntry): Boolean {
- if (entry.importance >= IMPORTANCE_DEFAULT ||
- hasHighPriorityCharacteristics(entry)) {
- return true
- }
-
- if (groupManager.isSummaryOfGroup(entry.sbn)) {
- val logicalChildren = groupManager.getLogicalChildren(entry.sbn)
- for (child in logicalChildren) {
- if (isHighPriority(child)) {
- return true
- }
- }
- }
-
- return false
- }
-
- private fun hasHighPriorityCharacteristics(entry: NotificationEntry): Boolean {
- val c = entry.channel
- val n = entry.sbn.notification
-
- if ((n.isForegroundService && entry.ranking.importance >= IMPORTANCE_LOW) ||
- n.hasMediaSession() ||
- entry.isPeopleNotification()) {
- // Users who have long pressed and demoted to silent should not see the notification
- // in the top section
- if (c != null && c.hasUserSetImportance()) {
- return false
- }
-
- return true
- }
-
- return false
- }
-
fun updateRanking(
newRankingMap: RankingMap?,
entries: Collection<NotificationEntry>,
@@ -219,7 +178,10 @@
// TODO: notify group manager here?
groupManager.onEntryUpdated(entry, oldSbn)
}
- entry.setIsHighPriority(isHighPriority(entry))
+
+ // TODO: (b/145659174) remove after moving to new NotifPipeline
+ // (should be able to remove all groupManager code post-migration)
+ entry.invalidateDerivedMembers()
}
}
}
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 511aafc..5e7dd98 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
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import android.Manifest;
-import android.app.AppGlobals;
import android.app.Notification;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -43,20 +42,23 @@
private static final String TAG = "DeviceProvisionedCoordinator";
private final DeviceProvisionedController mDeviceProvisionedController;
+ private final IPackageManager mIPackageManager;
@Inject
- public DeviceProvisionedCoordinator(DeviceProvisionedController deviceProvisionedController) {
+ public DeviceProvisionedCoordinator(DeviceProvisionedController deviceProvisionedController,
+ IPackageManager packageManager) {
mDeviceProvisionedController = deviceProvisionedController;
+ mIPackageManager = packageManager;
}
@Override
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
- protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+ private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
return !mDeviceProvisionedController.isDeviceProvisioned()
@@ -70,17 +72,16 @@
* marking them as relevant for setup are allowed to show when device is unprovisioned
*/
private boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
- final boolean hasPermission = checkUidPermission(AppGlobals.getPackageManager(),
+ final boolean hasPermission = checkUidPermission(
Manifest.permission.NOTIFICATION_DURING_SETUP,
sbn.getUid()) == PackageManager.PERMISSION_GRANTED;
return hasPermission
&& sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
}
- private static int checkUidPermission(IPackageManager packageManager, String permission,
- int uid) {
+ private int checkUidPermission(String permission, int uid) {
try {
- return packageManager.checkUidPermission(permission, uid);
+ return mIPackageManager.checkUidPermission(permission, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
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 4803cf4..62342b1 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
@@ -24,8 +24,7 @@
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.qualifiers.BgHandler;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+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;
@@ -52,12 +51,11 @@
*/
@Singleton
public class ForegroundCoordinator implements Coordinator {
- private static final String TAG = "ForegroundNotificationCoordinator";
+ private static final String TAG = "ForegroundCoordinator";
private final ForegroundServiceController mForegroundServiceController;
private final AppOpsController mAppOpsController;
private final Handler mMainHandler;
- private final Handler mBgHandler;
private NotifCollection mNotifCollection;
@@ -65,12 +63,10 @@
public ForegroundCoordinator(
ForegroundServiceController foregroundServiceController,
AppOpsController appOpsController,
- @MainHandler Handler mainHandler,
- @BgHandler Handler bgHandler) {
+ @Main Handler mainHandler) {
mForegroundServiceController = foregroundServiceController;
mAppOpsController = appOpsController;
mMainHandler = mainHandler;
- mBgHandler = bgHandler;
}
@Override
@@ -87,13 +83,13 @@
mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
// filter out foreground service notifications that aren't necessary anymore
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
/**
* Filters out notifications that represent foreground services that are no longer running.
*/
- protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+ private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
StatusBarNotification sbn = entry.getSbn();
@@ -120,7 +116,8 @@
* Extends the lifetime of foreground notification services such that they show for at least
* five seconds
*/
- private final NotifLifetimeExtender mForegroundLifetimeExtender = new NotifLifetimeExtender() {
+ private final NotifLifetimeExtender mForegroundLifetimeExtender =
+ new NotifLifetimeExtender() {
private static final int MIN_FGS_TIME_MS = 5000;
private OnEndLifetimeExtensionCallback mEndCallback;
private Map<String, Runnable> mEndRunnables = new HashMap<>();
@@ -154,8 +151,8 @@
}
};
mEndRunnables.put(entry.getKey(), runnable);
- mBgHandler.postDelayed(runnable, MIN_FGS_TIME_MS
- - (currTime - entry.getSbn().getPostTime()));
+ mMainHandler.postDelayed(runnable,
+ MIN_FGS_TIME_MS - (currTime - entry.getSbn().getPostTime()));
}
}
@@ -166,7 +163,7 @@
public void cancelLifetimeExtension(NotificationEntry entry) {
if (mEndRunnables.containsKey(entry.getKey())) {
Runnable endRunnable = mEndRunnables.remove(entry.getKey());
- mBgHandler.removeCallbacks(endRunnable);
+ mMainHandler.removeCallbacks(endRunnable);
}
}
};
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 6daf3fc..9312c22 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
@@ -37,6 +37,8 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
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.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
@@ -51,7 +53,7 @@
*/
@Singleton
public class KeyguardCoordinator implements Coordinator {
- private static final String TAG = "KeyguardNotificationCoordinator";
+ private static final String TAG = "KeyguardCoordinator";
private final Context mContext;
private final Handler mMainHandler;
@@ -83,10 +85,10 @@
@Override
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
setupInvalidateNotifListCallbacks();
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreRenderFilter(mNotifFilter);
}
- protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+ private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
final StatusBarNotification sbn = entry.getSbn();
@@ -129,12 +131,11 @@
}
}
- // ... neither this notification nor its summary have high enough priority
+ // ... neither this notification nor its group have high enough priority
// to be shown on the lockscreen
- // TODO: grouping hasn't happened yet (b/145134683)
if (entry.getParent() != null) {
- final NotificationEntry summary = entry.getParent().getRepresentativeEntry();
- if (priorityExceedsLockscreenShowingThreshold(summary)) {
+ final GroupEntry parent = entry.getParent();
+ if (priorityExceedsLockscreenShowingThreshold(parent)) {
return false;
}
}
@@ -144,17 +145,16 @@
}
};
- private boolean priorityExceedsLockscreenShowingThreshold(NotificationEntry entry) {
+ private boolean priorityExceedsLockscreenShowingThreshold(ListEntry entry) {
if (entry == null) {
return false;
}
if (NotificationUtils.useNewInterruptionModel(mContext)
&& hideSilentNotificationsOnLockscreen()) {
- // TODO: make sure in the NewNotifPipeline that entry.isHighPriority() has been
- // correctly updated before reaching this point (b/145134683)
return entry.isHighPriority();
} else {
- return !entry.getRanking().isAmbient();
+ return entry.getRepresentativeEntry() != null
+ && !entry.getRepresentativeEntry().getRanking().isAmbient();
}
}
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 c390f96..0751aa8 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
@@ -26,7 +26,10 @@
import javax.inject.Singleton;
/**
- * Filters out NotificationEntries based on its Ranking.
+ * Filters out NotificationEntries based on its Ranking and dozing state.
+ * We check the NotificationEntry's Ranking for:
+ * - whether the notification's app is suspended or hiding its notifications
+ * - whether DND settings are hiding notifications from ambient display or the notification list
*/
@Singleton
public class RankingCoordinator implements Coordinator {
@@ -43,7 +46,7 @@
public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
- notifListBuilder.addFilter(mNotifFilter);
+ notifListBuilder.addPreGroupFilter(mNotifFilter);
}
/**
@@ -51,7 +54,7 @@
* NotifListBuilder invalidates the notification list each time the ranking is updated,
* so we don't need to explicitly invalidate this filter on ranking update.
*/
- protected final NotifFilter mNotifFilter = new NotifFilter(TAG) {
+ private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
// App suspended from Ranking
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
index 15d3b92..7580924 100644
--- 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
@@ -59,11 +59,12 @@
public interface NotifListBuilder {
/**
- * Registers a filter with the pipeline. 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).
+ * 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 addFilter(NotifFilter filter);
+ void addPreGroupFilter(NotifFilter filter);
/**
* Registers a promoter with the pipeline. Promoters are able to promote child notifications to
@@ -91,6 +92,15 @@
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.
*/
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 170ff48..d7a0815 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
@@ -33,8 +33,6 @@
* @param list The current filtered and grouped list of (top-level) entries. Note that this is
* a live view into the current notif list and will change as the list moves through
* the pipeline.
- * @param newlyVisibleEntries The list of all entries (both top-level and children) who have
- * been added to the list for the first time.
*/
- void onBeforeTransformGroups(List<ListEntry> list, List<ListEntry> newlyVisibleEntries);
+ void onBeforeTransformGroups(List<ListEntry> list);
}
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 ad4bbd9..85f828d 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
@@ -78,18 +78,24 @@
public static final int STATE_IDLE = 0;
public static final int STATE_BUILD_PENDING = 1;
public static final int STATE_BUILD_STARTED = 2;
- public static final int STATE_FILTERING = 3;
- public static final int STATE_TRANSFORMING = 4;
- public static final int STATE_SORTING = 5;
- public static final int STATE_FINALIZING = 6;
+ public static final int STATE_RESETTING = 3;
+ public static final int STATE_PRE_GROUP_FILTERING = 4;
+ public static final int STATE_GROUPING = 5;
+ public static final int STATE_TRANSFORMING = 6;
+ public static final int STATE_SORTING = 7;
+ public static final int STATE_PRE_RENDER_FILTERING = 8;
+ public static final int STATE_FINALIZING = 9;
@IntDef(prefix = { "STATE_" }, value = {
STATE_IDLE,
STATE_BUILD_PENDING,
STATE_BUILD_STARTED,
- STATE_FILTERING,
+ STATE_RESETTING,
+ STATE_PRE_GROUP_FILTERING,
+ STATE_GROUPING,
STATE_TRANSFORMING,
STATE_SORTING,
+ STATE_PRE_RENDER_FILTERING,
STATE_FINALIZING,
})
@Retention(RetentionPolicy.SOURCE)
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 685eac8..e6189ed 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
@@ -20,8 +20,8 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
/**
- * Pluggable for participating in notif filtering. See
- * {@link NotifListBuilder#addFilter(NotifFilter)}.
+ * Pluggable for participating in notif filtering.
+ * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}.
*/
public abstract class NotifFilter extends Pluggable<NotifFilter> {
protected NotifFilter(String name) {
@@ -34,7 +34,11 @@
* This doesn't necessarily mean that your filter will get called on every notification,
* however. If another filter returns true before yours, we'll skip straight to the next notif.
*
- * @param entry The entry in question
+ * @param entry The entry in question.
+ * If this filter is registered via {@link NotifListBuilder#addPreGroupFilter},
+ * this entry will not have any grouping nor sorting information.
+ * If this filter is registered via {@link NotifListBuilder#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
* during this pipeline run, giving pluggables a stable concept of "now" to compare
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
new file mode 100644
index 0000000..815e6f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
@@ -0,0 +1,62 @@
+/*
+ * 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/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
new file mode 100644
index 0000000..76e256b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
@@ -0,0 +1,148 @@
+/*
+ * 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 8ebbca2..c18af80 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
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.notification.logging;
import android.annotation.IntDef;
-import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import com.android.systemui.log.RichEvent;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -31,103 +33,72 @@
* here to mitigate memory usage.
*/
public class NotifEvent extends RichEvent {
- public static final int TOTAL_EVENT_TYPES = 11;
-
/**
- * Creates a NotifEvent with an event type that matches with an index in the array
- * getSupportedEvents() and {@link EventType}.
- *
- * The status bar notification and ranking objects are stored as shallow copies of the current
- * state of the event when this event occurred.
+ * Initializes a rich event that includes an event type that matches with an index in the array
+ * getEventLabels().
*/
- public NotifEvent(int logLevel, int type, String reason, StatusBarNotification sbn,
- Ranking ranking) {
- super(logLevel, type, reason);
- mMessage += getExtraInfo(sbn, ranking);
- }
-
- private String getExtraInfo(StatusBarNotification sbn, Ranking ranking) {
- StringBuilder extraInfo = new StringBuilder();
-
+ public NotifEvent init(@EventType int type, StatusBarNotification sbn,
+ NotificationListenerService.Ranking ranking, String reason) {
+ StringBuilder extraInfo = new StringBuilder(reason);
if (sbn != null) {
- extraInfo.append(" Sbn=");
- extraInfo.append(sbn);
+ extraInfo.append(" " + sbn.getKey());
}
if (ranking != null) {
extraInfo.append(" Ranking=");
- extraInfo.append(ranking);
+ extraInfo.append(ranking.getRank());
}
-
- return extraInfo.toString();
+ super.init(INFO, type, extraInfo.toString());
+ return this;
}
/**
- * Event labels for NotifEvents
- * Index corresponds to the {@link EventType}
+ * Event labels for ListBuilderEvents
+ * Index corresponds to an # in {@link EventType}
*/
@Override
public String[] getEventLabels() {
- final String[] events = new String[]{
- "NotifAdded",
- "NotifRemoved",
- "NotifUpdated",
- "Filter",
- "Sort",
- "FilterAndSort",
- "NotifVisibilityChanged",
- "LifetimeExtended",
- "RemoveIntercepted",
- "InflationAborted",
- "Inflated"
- };
-
- if (events.length != TOTAL_EVENT_TYPES) {
- throw new IllegalStateException("NotifEvents events.length should match "
- + TOTAL_EVENT_TYPES
- + " events.length=" + events.length
- + " TOTAL_EVENT_LENGTH=" + TOTAL_EVENT_TYPES);
- }
- return events;
+ assert (TOTAL_EVENT_LABELS == (TOTAL_NEM_EVENT_TYPES + TOTAL_LIST_BUILDER_EVENT_TYPES));
+ return EVENT_LABELS;
}
/**
- * Builds a NotifEvent.
+ * @return if this event occurred in {@link NotifListBuilder}
*/
- public static class NotifEventBuilder extends RichEvent.Builder<NotifEventBuilder> {
- private StatusBarNotification mSbn;
- private Ranking mRanking;
-
- @Override
- public NotifEventBuilder getBuilder() {
- return this;
- }
-
- /**
- * Stores the status bar notification object. A shallow copy is stored in the NotifEvent's
- * constructor.
- */
- public NotifEventBuilder setSbn(StatusBarNotification sbn) {
- mSbn = sbn;
- return this;
- }
-
- /**
- * Stores the ranking object. A shallow copy is stored in the NotifEvent's
- * constructor.
- */
- public NotifEventBuilder setRanking(Ranking ranking) {
- mRanking = ranking;
- return this;
- }
-
- @Override
- public RichEvent build() {
- return new NotifEvent(mLogLevel, mType, mReason, mSbn, mRanking);
- }
+ static boolean isListBuilderEvent(@EventType int type) {
+ return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES);
}
- @IntDef({NOTIF_ADDED,
+ /**
+ * @return if this event occurred in {@link NotificationEntryManager}
+ */
+ static boolean isNemEvent(@EventType int type) {
+ return isBetweenInclusive(type, TOTAL_LIST_BUILDER_EVENT_TYPES,
+ TOTAL_LIST_BUILDER_EVENT_TYPES + TOTAL_NEM_EVENT_TYPES);
+ }
+
+ private static boolean isBetweenInclusive(int x, int a, int b) {
+ return x >= a && x <= b;
+ }
+
+ @IntDef({
+ // NotifListBuilder events:
+ WARN,
+ ON_BUILD_LIST,
+ START_BUILD_LIST,
+ DISPATCH_FINAL_LIST,
+ LIST_BUILD_COMPLETE,
+ PRE_GROUP_FILTER_INVALIDATED,
+ PROMOTER_INVALIDATED,
+ SECTIONS_PROVIDER_INVALIDATED,
+ COMPARATOR_INVALIDATED,
+ PARENT_CHANGED,
+ FILTER_CHANGED,
+ PROMOTER_CHANGED,
+ PRE_RENDER_FILTER_INVALIDATED,
+
+ // NotificationEntryManager events:
+ NOTIF_ADDED,
NOTIF_REMOVED,
NOTIF_UPDATED,
FILTER,
@@ -139,22 +110,74 @@
INFLATION_ABORTED,
INFLATED
})
-
- /**
- * Types of NotifEvents
- */
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
- public static final int NOTIF_ADDED = 0;
- public static final int NOTIF_REMOVED = 1;
- public static final int NOTIF_UPDATED = 2;
- public static final int FILTER = 3;
- public static final int SORT = 4;
- public static final int FILTER_AND_SORT = 5;
- public static final int NOTIF_VISIBILITY_CHANGED = 6;
- public static final int LIFETIME_EXTENDED = 7;
+
+ private static final String[] EVENT_LABELS =
+ new String[]{
+ // NotifListBuilder labels:
+ "Warning",
+ "OnBuildList",
+ "StartBuildList",
+ "DispatchFinalList",
+ "ListBuildComplete",
+ "FilterInvalidated",
+ "PromoterInvalidated",
+ "SectionsProviderInvalidated",
+ "ComparatorInvalidated",
+ "ParentChanged",
+ "FilterChanged",
+ "PromoterChanged",
+ "FinalFilterInvalidated",
+
+ // NEM event labels:
+ "NotifAdded",
+ "NotifRemoved",
+ "NotifUpdated",
+ "Filter",
+ "Sort",
+ "FilterAndSort",
+ "NotifVisibilityChanged",
+ "LifetimeExtended",
+ "RemoveIntercepted",
+ "InflationAborted",
+ "Inflated"
+ };
+
+ private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
+
+ /**
+ * Events related to {@link NotifListBuilder}
+ */
+ public static final int WARN = 0;
+ public static final int ON_BUILD_LIST = 1;
+ public static final int START_BUILD_LIST = 2;
+ public static final int DISPATCH_FINAL_LIST = 3;
+ 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 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;
+
+ /**
+ * Events related to {@link NotificationEntryManager}
+ */
+ public static final int NOTIF_ADDED = TOTAL_LIST_BUILDER_EVENT_TYPES + 0;
+ 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;
// unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor}
- public static final int REMOVE_INTERCEPTED = 8;
- public static final int INFLATION_ABORTED = 9;
- public static final int INFLATED = 10;
+ 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;
+ private static final int TOTAL_NEM_EVENT_TYPES = 11;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
index 1292831..299d628 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.logging;
+import android.os.SystemProperties;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.StatusBarNotification;
@@ -33,93 +34,82 @@
* dependency DumpController NotifLog
*/
@Singleton
-public class NotifLog extends SysuiLog {
+public class NotifLog extends SysuiLog<NotifEvent> {
private static final String TAG = "NotifLog";
+ private static final boolean SHOW_NEM_LOGS =
+ SystemProperties.getBoolean("persist.sysui.log.notif.nem", true);
+ private static final boolean SHOW_LIST_BUILDER_LOGS =
+ SystemProperties.getBoolean("persist.sysui.log.notif.listbuilder", true);
+
private static final int MAX_DOZE_DEBUG_LOGS = 400;
private static final int MAX_DOZE_LOGS = 50;
+ private NotifEvent mRecycledEvent;
+
@Inject
public NotifLog(DumpController dumpController) {
super(dumpController, TAG, MAX_DOZE_DEBUG_LOGS, MAX_DOZE_LOGS);
}
/**
- * Logs a {@link NotifEvent} with a notification, ranking and message
+ * Logs a {@link NotifEvent} with a notification, ranking and message.
+ * Uses the last recycled event if available.
* @return true if successfully logged, else false
*/
- public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn,
- Ranking ranking, String msg) {
- return log(new NotifEvent.NotifEventBuilder()
- .setType(eventType)
- .setSbn(sbn)
- .setRanking(ranking)
- .setReason(msg)
- .build());
+ public void log(@NotifEvent.EventType int eventType,
+ StatusBarNotification sbn, Ranking ranking, String msg) {
+ if (!mEnabled
+ || (NotifEvent.isListBuilderEvent(eventType) && !SHOW_LIST_BUILDER_LOGS)
+ || (NotifEvent.isNemEvent(eventType) && !SHOW_NEM_LOGS)) {
+ return;
+ }
+
+ if (mRecycledEvent != null) {
+ mRecycledEvent = log(mRecycledEvent.init(eventType, sbn, ranking, msg));
+ } else {
+ mRecycledEvent = log(new NotifEvent().init(eventType, sbn, ranking, msg));
+ }
}
/**
- * Logs a {@link NotifEvent}
- * @return true if successfully logged, else false
+ * Logs a {@link NotifEvent} with no extra information aside from the event type
*/
- public boolean log(@NotifEvent.EventType int eventType) {
- return log(eventType, null, null, null);
+ public void log(@NotifEvent.EventType int eventType) {
+ log(eventType, null, null, "");
}
/**
* Logs a {@link NotifEvent} with a message
- * @return true if successfully logged, else false
*/
- public boolean log(@NotifEvent.EventType int eventType, String msg) {
- return log(eventType, null, null, msg);
+ public void log(@NotifEvent.EventType int eventType, String msg) {
+ log(eventType, null, null, msg);
}
/**
- * Logs a {@link NotifEvent} with a notification
- * @return true if successfully logged, else false
+ * Logs a {@link NotifEvent} with a entry
*/
- public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn) {
- return log(eventType, sbn, null, "");
+ public void log(@NotifEvent.EventType int eventType, NotificationEntry entry) {
+ log(eventType, entry.getSbn(), entry.getRanking(), "");
}
/**
- * Logs a {@link NotifEvent} with a notification
- * @return true if successfully logged, else false
+ * Logs a {@link NotifEvent} with a NotificationEntry and message
*/
- public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) {
- return log(eventType, sbn, null, msg);
+ public void log(@NotifEvent.EventType int eventType, NotificationEntry entry, String msg) {
+ log(eventType, entry.getSbn(), entry.getRanking(), msg);
}
/**
- * Logs a {@link NotifEvent} with a ranking
- * @return true if successfully logged, else false
+ * Logs a {@link NotifEvent} with a notification and message
*/
- public boolean log(@NotifEvent.EventType int eventType, Ranking ranking) {
- return log(eventType, null, ranking, "");
+ public void log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) {
+ log(eventType, sbn, null, msg);
}
/**
- * Logs a {@link NotifEvent} with a notification and ranking
- * @return true if successfully logged, else false
+ * Logs a {@link NotifEvent} with a ranking and message
*/
- public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn,
- Ranking ranking) {
- return log(eventType, sbn, ranking, "");
- }
-
- /**
- * Logs a {@link NotifEvent} with a notification entry
- * @return true if successfully logged, else false
- */
- public boolean log(@NotifEvent.EventType int eventType, NotificationEntry entry) {
- return log(eventType, entry.getSbn(), entry.getRanking(), "");
- }
-
- /**
- * Logs a {@link NotifEvent} with a notification entry
- * @return true if successfully logged, else false
- */
- public boolean log(@NotifEvent.EventType int eventType, NotificationEntry entry,
- String msg) {
- return log(eventType, entry.getSbn(), entry.getRanking(), msg);
+ public void log(@NotifEvent.EventType int eventType, Ranking ranking, String msg) {
+ log(eventType, null, ranking, msg);
}
}
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 77ccf19..3e1b5bd 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
@@ -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 com.android.systemui.statusbar.notification.logging;
@@ -32,7 +32,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.NotificationListener;
@@ -47,6 +47,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -68,7 +69,7 @@
// Dependencies:
private final NotificationListenerService mNotificationListener;
- private final UiOffloadThread mUiOffloadThread;
+ private final Executor mUiBgExecutor;
private final NotificationEntryManager mEntryManager;
private HeadsUpManager mHeadsUpManager;
private final ExpansionStateLogger mExpansionStateLogger;
@@ -193,12 +194,12 @@
@Inject
public NotificationLogger(NotificationListener notificationListener,
- UiOffloadThread uiOffloadThread,
+ @UiBackground Executor uiBgExecutor,
NotificationEntryManager entryManager,
StatusBarStateController statusBarStateController,
ExpansionStateLogger expansionStateLogger) {
mNotificationListener = notificationListener;
- mUiOffloadThread = uiOffloadThread;
+ mUiBgExecutor = uiBgExecutor;
mEntryManager = entryManager;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -319,7 +320,7 @@
final NotificationVisibility[] newlyVisibleAr = cloneVisibilitiesAsArr(newlyVisible);
final NotificationVisibility[] noLongerVisibleAr = cloneVisibilitiesAsArr(noLongerVisible);
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
try {
mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
} catch (RemoteException e) {
@@ -429,13 +430,13 @@
* Notification key -> last logged expansion state, should be accessed in UI thread only.
*/
private final Map<String, Boolean> mLoggedExpansionState = new ArrayMap<>();
- private final UiOffloadThread mUiOffloadThread;
+ private final Executor mUiBgExecutor;
@VisibleForTesting
IStatusBarService mBarService;
@Inject
- public ExpansionStateLogger(UiOffloadThread uiOffloadThread) {
- mUiOffloadThread = uiOffloadThread;
+ public ExpansionStateLogger(@UiBackground Executor uiBgExecutor) {
+ mUiBgExecutor = uiBgExecutor;
mBarService =
IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -513,7 +514,7 @@
}
mLoggedExpansionState.put(key, state.mIsExpanded);
final State stateToBeLogged = new State(state);
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
try {
mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
index 4f03003..efcef71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
@@ -31,6 +31,11 @@
abstract fun peopleHubDataSource(impl: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
@Binds
+ abstract fun peopleHubSettingChangeDataSource(
+ impl: PeopleHubSettingChangeDataSourceImpl
+ ): DataSource<Boolean>
+
+ @Binds
abstract fun peopleHubViewModelFactoryDataSource(
impl: PeopleHubViewModelFactoryDataSourceImpl
): DataSource<PeopleHubViewModelFactory>
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 987b52db..784673e 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
@@ -249,7 +249,7 @@
}
}
-private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
+fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
entry.row
?.childrenWithId(R.id.expanded)
?.mapNotNull { it as? ViewGroup }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index 5c35408..ec1d6de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -16,7 +16,14 @@
package com.android.systemui.statusbar.notification.people
+import android.content.Context
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
import android.view.View
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager
import javax.inject.Inject
@@ -90,29 +97,58 @@
@Singleton
class PeopleHubViewModelFactoryDataSourceImpl @Inject constructor(
private val activityStarter: ActivityStarter,
- private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
+ private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>,
+ private val settingChangeSource: DataSource<@JvmSuppressWildcards Boolean>
) : DataSource<PeopleHubViewModelFactory> {
- override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>) =
- dataSource.registerListener(PeopleHubModelListenerImpl(activityStarter, listener))
+ override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>): Subscription {
+ var stripEnabled = false
+ var model: PeopleHubModel? = null
+
+ fun updateListener() {
+ // don't invoke listener until we've received our first model
+ model?.let { model ->
+ val factory =
+ if (stripEnabled) PeopleHubViewModelFactoryImpl(model, activityStarter)
+ else EmptyViewModelFactory
+ listener.onDataChanged(factory)
+ }
+ }
+
+ val settingSub = settingChangeSource.registerListener(object : DataListener<Boolean> {
+ override fun onDataChanged(data: Boolean) {
+ stripEnabled = data
+ updateListener()
+ }
+ })
+ val dataSub = dataSource.registerListener(object : DataListener<PeopleHubModel> {
+ override fun onDataChanged(data: PeopleHubModel) {
+ model = data
+ updateListener()
+ }
+ })
+ return object : Subscription {
+ override fun unsubscribe() {
+ settingSub.unsubscribe()
+ dataSub.unsubscribe()
+ }
+ }
+ }
}
-private class PeopleHubModelListenerImpl(
- private val activityStarter: ActivityStarter,
- private val dataListener: DataListener<PeopleHubViewModelFactory>
-) : DataListener<PeopleHubModel> {
-
- override fun onDataChanged(data: PeopleHubModel) =
- dataListener.onDataChanged(PeopleHubViewModelFactoryImpl(data, activityStarter))
+private object EmptyViewModelFactory : PeopleHubViewModelFactory {
+ override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
+ return PeopleHubViewModel(emptySequence(), false)
+ }
}
private class PeopleHubViewModelFactoryImpl(
- private val data: PeopleHubModel,
+ private val model: PeopleHubModel,
private val activityStarter: ActivityStarter
) : PeopleHubViewModelFactory {
override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
- val personViewModels = data.people.asSequence().map { personModel ->
+ val personViewModels = model.people.asSequence().map { personModel ->
val onClick = {
activityStarter.startPendingIntentDismissingKeyguard(
personModel.clickIntent,
@@ -122,7 +158,42 @@
}
PersonViewModel(personModel.name, personModel.avatar, onClick)
}
- return PeopleHubViewModel(personViewModels, data.people.isNotEmpty())
+ return PeopleHubViewModel(personViewModels, model.people.isNotEmpty())
+ }
+}
+
+@Singleton
+class PeopleHubSettingChangeDataSourceImpl @Inject constructor(
+ @Main private val handler: Handler,
+ context: Context
+) : DataSource<Boolean> {
+
+ private val settingUri = Settings.Secure.getUriFor(Settings.Secure.PEOPLE_STRIP)
+ private val contentResolver = context.contentResolver
+
+ override fun registerListener(listener: DataListener<Boolean>): Subscription {
+ // Immediately report current value of setting
+ updateListener(listener)
+ val observer = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?, userId: Int) {
+ super.onChange(selfChange, uri, userId)
+ updateListener(listener)
+ }
+ }
+ contentResolver.registerContentObserver(settingUri, false, observer, UserHandle.USER_ALL)
+ return object : Subscription {
+ override fun unsubscribe() = contentResolver.unregisterContentObserver(observer)
+ }
+ }
+
+ private fun updateListener(listener: DataListener<Boolean>) {
+ val setting = Settings.Secure.getIntForUser(
+ contentResolver,
+ Settings.Secure.PEOPLE_STRIP,
+ 0,
+ UserHandle.USER_CURRENT
+ )
+ listener.onDataChanged(setting != 0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
index 782aad1..315ea0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -34,6 +34,7 @@
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.Window
+import android.view.WindowInsets.Type
import android.view.WindowManager
import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
@@ -287,6 +288,7 @@
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
addFlags(wmFlags)
setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL)
+ setFitWindowInsetsTypes(getFitWindowInsetsTypes() and Type.statusBars().inv())
setWindowAnimations(com.android.internal.R.style.Animation_InputMethod)
attributes = attributes.apply {
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 9ac5b44..65423e9 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
@@ -813,15 +813,14 @@
* @param parent the new parent notification
*/
public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
- boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
mNotificationParent.setChildIsExpanding(false);
mNotificationParent.setExtraWidthForClipping(0.0f);
mNotificationParent.setMinimumHeightForClipping(0);
}
- mNotificationParent = childInGroup ? parent : null;
- mPrivateLayout.setIsChildInGroup(childInGroup);
- mNotificationInflater.setIsChildInGroup(childInGroup);
+ mNotificationParent = isChildInGroup ? parent : null;
+ mPrivateLayout.setIsChildInGroup(isChildInGroup);
+ mNotificationInflater.setIsChildInGroup(isChildInGroup);
resetBackgroundAlpha();
updateBackgroundForGroupState();
updateClickAndFocus();
@@ -2354,8 +2353,8 @@
}
private void onChildrenCountChanged() {
- mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS
- && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
+ mIsSummaryWithChildren = mChildrenContainer != null
+ && mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
mChildrenContainer.recreateNotificationHeader(mExpandClickListener
);
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 9dd7f48..6f2abba 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
@@ -42,7 +42,7 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
@@ -108,7 +108,7 @@
@Inject
public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
- Lazy<StatusBar> statusBarLazy, @MainHandler Handler mainHandler,
+ Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
AccessibilityManager accessibilityManager) {
mContext = context;
mVisualStabilityManager = visualStabilityManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index 90ea6e3..0a1a2fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -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 com.android.systemui.statusbar.notification.row.wrapper;
@@ -244,17 +244,18 @@
mUiOffloadThread = Dependency.get(UiOffloadThread.class);
}
if (view.isAttachedToWindow()) {
- mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener));
+ mUiOffloadThread.execute(() -> pendingIntent.registerCancelListener(listener));
}
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
- mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener));
+ mUiOffloadThread.execute(() -> pendingIntent.registerCancelListener(listener));
}
@Override
public void onViewDetachedFromWindow(View v) {
- mUiOffloadThread.submit(() -> pendingIntent.unregisterCancelListener(listener));
+ mUiOffloadThread.execute(
+ () -> pendingIntent.unregisterCancelListener(listener));
}
});
}
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 8e9a051e..2761689 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
@@ -249,10 +249,8 @@
}
}
- if (adjustPeopleHubVisibilityAndPosition(lastPersonIndex)) {
- // make room for peopleHub
- firstGentleNotifIndex++;
- }
+ // make room for peopleHub
+ firstGentleNotifIndex += adjustPeopleHubVisibilityAndPosition(lastPersonIndex);
adjustGentleHeaderVisibilityAndPosition(firstGentleNotifIndex);
@@ -296,7 +294,7 @@
}
}
- private boolean adjustPeopleHubVisibilityAndPosition(int lastPersonIndex) {
+ private int adjustPeopleHubVisibilityAndPosition(int lastPersonIndex) {
final boolean showPeopleHeader = mPeopleHubVisible
&& mNumberOfSections > 2
&& mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
@@ -307,6 +305,7 @@
if (!showPeopleHeader) {
if (currentlyVisible) {
mParent.removeView(mPeopleHubView);
+ return -1;
}
} else {
mPeopleHubView.unDismiss();
@@ -317,7 +316,7 @@
mPeopleHubView.setTransientContainer(null);
}
mParent.addView(mPeopleHubView, targetIndex);
- return true;
+ return 1;
} else if (currentHubIndex != targetIndex) {
if (currentHubIndex < targetIndex) {
targetIndex--;
@@ -325,7 +324,7 @@
mParent.changeViewPosition(mPeopleHubView, targetIndex);
}
}
- return false;
+ return 0;
}
/**
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 43af3aa..71342c5 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
@@ -5519,7 +5519,7 @@
if (viewsToRemove.isEmpty()) {
if (closeShade) {
- mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
}
return;
}
@@ -5581,7 +5581,7 @@
setDismissAllInProgress(false);
onAnimationComplete.run();
});
- mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
} else {
setDismissAllInProgress(false);
onAnimationComplete.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index f9b9367..3165597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -23,7 +23,7 @@
import android.view.IWindowManager;
import android.view.MotionEvent;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import javax.inject.Inject;
@@ -52,7 +52,7 @@
};
@Inject
- public AutoHideController(Context context, @MainHandler Handler handler,
+ public AutoHideController(Context context, @Main Handler handler,
NotificationRemoteInputManager notificationRemoteInputManager,
IWindowManager iWindowManager) {
mHandler = handler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 837517e..0680c7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -21,7 +21,7 @@
import android.provider.Settings.Secure;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.SecureSetting;
@@ -57,7 +57,7 @@
@Inject
public AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host,
- @BgHandler Handler handler,
+ @Background Handler handler,
HotspotController hotspotController,
DataSaverController dataSaverController,
ManagedProfileController managedProfileController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 865d7e7..4880520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -37,10 +37,11 @@
import com.android.systemui.Dependency;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -136,6 +137,7 @@
private final Handler mHandler;
private final KeyguardBypassController mKeyguardBypassController;
private PowerManager.WakeLock mWakeLock;
+ private final ShadeController mShadeController;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
@@ -159,20 +161,23 @@
@Inject
public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
- StatusBar statusBar, KeyguardStateController keyguardStateController, Handler handler,
+ StatusBar statusBar, ShadeController shadeController,
+ StatusBarWindowController statusBarWindowController,
+ KeyguardStateController keyguardStateController, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- @MainResources Resources resources,
+ @Main Resources resources,
KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters,
MetricsLogger metricsLogger, DumpController dumpController) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
+ mShadeController = shadeController;
mUpdateMonitor = keyguardUpdateMonitor;
mDozeParameters = dozeParameters;
mUpdateMonitor.registerCallback(this);
mMediaManager = Dependency.get(NotificationMediaManager.class);
Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
Dependency.get(ScreenLifecycle.class).addObserver(mScreenObserver);
- mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
+ mStatusBarWindowController = statusBarWindowController;
mDozeScrimController = dozeScrimController;
mKeyguardViewMediator = keyguardViewMediator;
mScrimController = scrimController;
@@ -358,8 +363,8 @@
if (mMode == MODE_SHOW_BOUNCER) {
mStatusBarKeyguardViewManager.showBouncer(false);
}
- mStatusBarKeyguardViewManager.animateCollapsePanels(
- BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+ false /* delayed */, BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index bc48235..f5999f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -25,7 +25,7 @@
import android.util.MathUtils;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
import com.android.systemui.tuner.TunerService;
@@ -58,7 +58,7 @@
@Inject
protected DozeParameters(
- @MainResources Resources resources,
+ @Main Resources resources,
AmbientDisplayConfiguration ambientDisplayConfiguration,
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
PowerManager powerManager,
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 8ee964f..f25f910 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -300,6 +300,7 @@
layoutParams.setTitle(TAG + mContext.getDisplayId());
layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
layoutParams.windowAnimations = 0;
+ layoutParams.setFitWindowInsetsTypes(0 /* types */);
return layoutParams;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
index a784984..783e7ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -84,6 +84,7 @@
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("FloatingRotationButton");
+ lp.setFitWindowInsetsTypes(0 /*types */);
switch (mWindowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_0:
lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 66b1dd8..858023d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -101,7 +101,8 @@
R.dimen.keyguard_affordance_touch_target_size);
mHintGrowAmount =
mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
- mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
+ mFlingAnimationUtils = new FlingAnimationUtils(mContext.getResources().getDisplayMetrics(),
+ 0.4f);
}
private void initIcons() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
index 1281953..8c3420a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java
@@ -35,8 +35,6 @@
@Singleton
public class LockscreenGestureLogger {
private ArrayMap<Integer, Integer> mLegacyMap;
- private LogMaker mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
- .setType(MetricsEvent.TYPE_ACTION);
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
@Inject
@@ -48,7 +46,7 @@
}
public void write(int gesture, int length, int velocity) {
- mMetricsLogger.write(mLogMaker.setCategory(gesture)
+ mMetricsLogger.write(new LogMaker(gesture)
.setType(MetricsEvent.TYPE_ACTION)
.addTaggedData(MetricsEvent.FIELD_GESTURE_LENGTH, length)
.addTaggedData(MetricsEvent.FIELD_GESTURE_VELOCITY, velocity));
@@ -64,7 +62,7 @@
*/
public void writeAtFractionalPosition(
int category, int xPercent, int yPercent, int rotation) {
- mMetricsLogger.write(mLogMaker.setCategory(category)
+ mMetricsLogger.write(new LogMaker(category)
.setType(MetricsEvent.TYPE_ACTION)
.addTaggedData(MetricsEvent.FIELD_GESTURE_X_PERCENT, xPercent)
.addTaggedData(MetricsEvent.FIELD_GESTURE_Y_PERCENT, yPercent)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index f3e9b6b..4ee13bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -37,12 +37,19 @@
import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.IndentingPrintWriter;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.NotificationMediaManager;
import libcore.io.IoUtils;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Objects;
import javax.inject.Inject;
@@ -52,16 +59,15 @@
* Manages the lockscreen wallpaper.
*/
@Singleton
-public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable {
+public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
+ Dumpable {
private static final String TAG = "LockscreenWallpaper";
- private final NotificationMediaManager mMediaManager =
- Dependency.get(NotificationMediaManager.class);
-
+ private final NotificationMediaManager mMediaManager;
private final WallpaperManager mWallpaperManager;
- private Handler mH;
private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final Handler mH;
private boolean mCached;
private Bitmap mCache;
@@ -74,10 +80,16 @@
@Inject
public LockscreenWallpaper(WallpaperManager wallpaperManager,
@Nullable IWallpaperManager iWallpaperManager,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DumpController dumpController,
+ NotificationMediaManager mediaManager,
+ @Main Handler mainHandler) {
+ dumpController.registerDumpable(getClass().getSimpleName(), this);
mWallpaperManager = wallpaperManager;
mCurrentUserId = ActivityManager.getCurrentUser();
mUpdateMonitor = keyguardUpdateMonitor;
+ mMediaManager = mediaManager;
+ mH = mainHandler;
if (iWallpaperManager != null) {
// Service is disabled on some devices like Automotive
@@ -89,14 +101,6 @@
}
}
- void setHandler(Handler handler) {
- if (mH != null) {
- Log.wtfStack(TAG, "Handler has already been set. Trying to double initialize?");
- return;
- }
- mH = handler;
- }
-
public Bitmap getBitmap() {
if (mCached) {
return mCache;
@@ -227,6 +231,16 @@
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println(getClass().getSimpleName() + ":");
+ IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " ").increaseIndent();
+ iPw.println("mCached=" + mCached);
+ iPw.println("mCache=" + mCache);
+ iPw.println("mCurrentUserId=" + mCurrentUserId);
+ iPw.println("mSelectedUser=" + mSelectedUser);
+ }
+
private static class LoaderResult {
public final boolean success;
public final Bitmap bitmap;
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 0703d8c..d4cf272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -93,7 +93,7 @@
import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.model.SysUiState;
@@ -164,6 +164,7 @@
private int mDisabledFlags1;
private int mDisabledFlags2;
private final Lazy<StatusBar> mStatusBarLazy;
+ private final ShadeController mShadeController;
private Recents mRecents;
private StatusBar mStatusBar;
private final Divider mDivider;
@@ -216,7 +217,7 @@
mNavigationBarView.getRotationButtonController().setRotateSuggestionButtonState(false);
// Hide the notifications panel when quick step starts
- mStatusBarLazy.get().collapsePanel(true /* animate */);
+ mShadeController.collapsePanel(true /* animate */);
}
@Override
@@ -272,7 +273,8 @@
BroadcastDispatcher broadcastDispatcher,
CommandQueue commandQueue, Divider divider,
Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy,
- @MainHandler Handler mainHandler) {
+ ShadeController shadeController,
+ @Main Handler mainHandler) {
mAccessibilityManagerWrapper = accessibilityManagerWrapper;
mDeviceProvisionedController = deviceProvisionedController;
mStatusBarStateController = statusBarStateController;
@@ -280,6 +282,7 @@
mAssistManager = assistManager;
mSysUiFlagsContainer = sysUiFlagsContainer;
mStatusBarLazy = statusBarLazy;
+ mShadeController = shadeController;
mAssistantAvailable = mAssistManager.getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index 1df9411..d6336ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -53,7 +53,7 @@
import android.util.SparseBooleanArray;
import com.android.systemui.Dumpable;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -61,6 +61,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -82,7 +83,7 @@
private Context mCurrentUserContext;
private final IOverlayManager mOverlayManager;
private final DeviceProvisionedController mDeviceProvisionedController;
- private final UiOffloadThread mUiOffloadThread;
+ private final Executor mUiBgExecutor;
private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
@@ -146,12 +147,12 @@
@Inject
public NavigationModeController(Context context,
DeviceProvisionedController deviceProvisionedController,
- UiOffloadThread uiOffloadThread) {
+ @UiBackground Executor uiBgExecutor) {
mContext = context;
mCurrentUserContext = context;
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
- mUiOffloadThread = uiOffloadThread;
+ mUiBgExecutor = uiBgExecutor;
mDeviceProvisionedController = deviceProvisionedController;
mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
@@ -242,7 +243,7 @@
mCurrentUserContext = getCurrentUserContext();
int mode = getCurrentInteractionMode(mCurrentUserContext);
mMode = mode;
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
Secure.NAVIGATION_MODE, String.valueOf(mode));
});
@@ -379,7 +380,7 @@
}
public void setModeOverlay(String overlayPkg, int userId) {
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
try {
mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
if (DEBUG) {
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 ed2fb0f..199d52f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -609,7 +609,7 @@
@Override
protected void loadDimens() {
super.loadDimens();
- mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
+ 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);
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 e8e5e1f..78a5eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -26,6 +26,7 @@
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;
@@ -208,11 +209,12 @@
super(context, attrs);
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = statusBarStateController;
- mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f /* maxLengthSeconds */,
- 0.6f /* speedUpFactor */);
- mFlingAnimationUtilsClosing = new FlingAnimationUtils(context, 0.5f /* maxLengthSeconds */,
- 0.6f /* speedUpFactor */);
- mFlingAnimationUtilsDismissing = new FlingAnimationUtils(context,
+ 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();
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 8d43c66..5b34aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -37,12 +37,11 @@
import android.text.format.DateFormat;
import android.util.Log;
-import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.statusbar.CommandQueue;
@@ -64,6 +63,7 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.Locale;
+import java.util.concurrent.Executor;
/**
* This class contains all of the policy about which icons are installed in the status bar at boot
@@ -115,7 +115,7 @@
private final DeviceProvisionedController mProvisionedController;
private final KeyguardStateController mKeyguardStateController;
private final LocationController mLocationController;
- private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+ private final Executor mUiBgExecutor;
private final SensorPrivacyController mSensorPrivacyController;
// Assume it's all good unless we hear otherwise. We don't always seem
@@ -132,7 +132,8 @@
private AlarmManager.AlarmClockInfo mNextAlarm;
public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController,
- CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher) {
+ CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher,
+ @UiBackground Executor uiBgExecutor) {
mContext = context;
mIconController = iconController;
mCast = Dependency.get(CastController.class);
@@ -149,6 +150,7 @@
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
mLocationController = Dependency.get(LocationController.class);
mSensorPrivacyController = Dependency.get(SensorPrivacyController.class);
+ mUiBgExecutor = uiBgExecutor;
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -289,21 +291,21 @@
}
private final void updateSimState(Intent intent) {
- String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
- if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
+ String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE);
+ if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) {
mSimState = TelephonyManager.SIM_STATE_READY;
- } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+ } else if (Intent.SIM_STATE_CARD_IO_ERROR.equals(stateExtra)) {
mSimState = TelephonyManager.SIM_STATE_CARD_IO_ERROR;
- } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED.equals(stateExtra)) {
+ } else if (Intent.SIM_STATE_CARD_RESTRICTED.equals(stateExtra)) {
mSimState = TelephonyManager.SIM_STATE_CARD_RESTRICTED;
- } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
+ } else if (Intent.SIM_STATE_READY.equals(stateExtra)) {
mSimState = TelephonyManager.SIM_STATE_READY;
- } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
+ } else if (Intent.SIM_STATE_LOCKED.equals(stateExtra)) {
final String lockedReason =
- intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
- if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
+ intent.getStringExtra(Intent.EXTRA_SIM_LOCKED_REASON);
+ if (Intent.SIM_LOCKED_ON_PIN.equals(lockedReason)) {
mSimState = TelephonyManager.SIM_STATE_PIN_REQUIRED;
- } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
+ } else if (Intent.SIM_LOCKED_ON_PUK.equals(lockedReason)) {
mSimState = TelephonyManager.SIM_STATE_PUK_REQUIRED;
} else {
mSimState = TelephonyManager.SIM_STATE_NETWORK_LOCKED;
@@ -385,8 +387,11 @@
mContext.getString(R.string.accessibility_quick_settings_bluetooth_on);
boolean bluetoothVisible = false;
if (mBluetooth != null) {
- if (mBluetooth.isBluetoothConnected()) {
- contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected);
+ if (mBluetooth.isBluetoothConnected()
+ && (mBluetooth.isBluetoothAudioActive()
+ || !mBluetooth.isBluetoothAudioProfileOnly())) {
+ contentDescription = mContext.getString(
+ R.string.accessibility_bluetooth_connected);
bluetoothVisible = mBluetooth.isBluetoothEnabled();
}
}
@@ -450,7 +455,7 @@
// getLastResumedActivityUserId needds to acquire the AM lock, which may be contended in
// some cases. Since it doesn't really matter here whether it's updated in this frame
// or in the next one, we call this method from our UI offload thread.
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
final int userId;
try {
userId = ActivityTaskManager.getService().getLastResumedActivityUserId();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 1454e25..2b9fc8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -45,7 +45,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -192,7 +192,7 @@
@Inject
public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters,
AlarmManager alarmManager, KeyguardStateController keyguardStateController,
- @MainResources Resources resources,
+ @Main Resources resources,
DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor, SysuiColorExtractor sysuiColorExtractor,
DockManager dockManager) {
@@ -360,7 +360,8 @@
return false;
}
- if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
+ if (mState == ScrimState.AOD
+ && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
return true;
}
@@ -560,7 +561,7 @@
}
protected void scheduleUpdate() {
- if (mUpdatePending) return;
+ if (mUpdatePending || mScrimBehind == null) return;
// Make sure that a frame gets scheduled.
mScrimBehind.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
index deea3f1..2fa6795 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeController.java
@@ -32,12 +32,24 @@
*/
void instantExpandNotificationsPanel();
+ /** See {@link #animateCollapsePanels(int, boolean)}. */
+ void animateCollapsePanels();
+
+ /** See {@link #animateCollapsePanels(int, boolean)}. */
+ void animateCollapsePanels(int flags);
+
/**
* Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
* dismissing {@link StatusBar} when on {@link StatusBarState#SHADE}.
*/
void animateCollapsePanels(int flags, boolean force);
+ /** See {@link #animateCollapsePanels(int, boolean)}. */
+ void animateCollapsePanels(int flags, boolean force, boolean delayed);
+
+ /** See {@link #animateCollapsePanels(int, boolean)}. */
+ void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
+
/**
* If the notifications panel is not fully expanded, collapse it animated.
*
@@ -61,11 +73,9 @@
void addPostCollapseAction(Runnable action);
/**
- * Notify the shade controller that the current user changed
- *
- * @param newUserId userId of the new user
+ * Run all of the runnables added by {@link #addPostCollapseAction}.
*/
- void setLockscreenUser(int newUserId);
+ void runPostCollapseRunnables();
/**
* If secure with redaction: Show bouncer, go to unlocked shade.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
new file mode 100644
index 0000000..57e7014
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.StatusBarState;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/** An implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
+@Singleton
+public class ShadeControllerImpl implements ShadeController {
+
+ private static final String TAG = "ShadeControllerImpl";
+ private static final boolean SPEW = false;
+
+ private final CommandQueue mCommandQueue;
+ private final StatusBarStateController mStatusBarStateController;
+ protected final StatusBarWindowController mStatusBarWindowController;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final int mDisplayId;
+ protected final Lazy<StatusBar> mStatusBarLazy;
+ private final Lazy<AssistManager> mAssistManagerLazy;
+ private final Lazy<BubbleController> mBubbleControllerLazy;
+
+ private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+
+ @Inject
+ public ShadeControllerImpl(
+ CommandQueue commandQueue,
+ StatusBarStateController statusBarStateController,
+ StatusBarWindowController statusBarWindowController,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ WindowManager windowManager,
+ Lazy<StatusBar> statusBarLazy,
+ Lazy<AssistManager> assistManagerLazy,
+ Lazy<BubbleController> bubbleControllerLazy
+ ) {
+ mCommandQueue = commandQueue;
+ mStatusBarStateController = statusBarStateController;
+ mStatusBarWindowController = statusBarWindowController;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
+ // TODO: Remove circular reference to StatusBar when possible.
+ mStatusBarLazy = statusBarLazy;
+ mAssistManagerLazy = assistManagerLazy;
+ mBubbleControllerLazy = bubbleControllerLazy;
+ }
+
+ @Override
+ public void instantExpandNotificationsPanel() {
+ // Make our window larger and the panel expanded.
+ getStatusBar().makeExpandedVisible(true /* force */);
+ getNotificationPanelView().expand(false /* animate */);
+ mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
+ }
+
+ @Override
+ public void animateCollapsePanels() {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ }
+
+ @Override
+ public void animateCollapsePanels(int flags) {
+ animateCollapsePanels(flags, false /* force */, false /* delayed */,
+ 1.0f /* speedUpFactor */);
+ }
+
+ @Override
+ public void animateCollapsePanels(int flags, boolean force) {
+ animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+ }
+
+ @Override
+ public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
+ animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+ }
+
+ @Override
+ public void animateCollapsePanels(int flags, boolean force, boolean delayed,
+ float speedUpFactor) {
+ if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
+ runPostCollapseRunnables();
+ return;
+ }
+ if (SPEW) {
+ Log.d(TAG, "animateCollapse():"
+ + " mExpandedVisible=" + getStatusBar().isExpandedVisible()
+ + " flags=" + flags);
+ }
+
+ if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) {
+ getStatusBar().postHideRecentApps();
+ }
+
+ // TODO(b/62444020): remove when this bug is fixed
+ Log.v(TAG, "mStatusBarWindow: " + getStatusBarWindowView() + " canPanelBeCollapsed(): "
+ + getNotificationPanelView().canPanelBeCollapsed());
+ if (getStatusBarWindowView() != null && getNotificationPanelView().canPanelBeCollapsed()) {
+ // release focus immediately to kick off focus change transition
+ mStatusBarWindowController.setStatusBarFocusable(false);
+
+ getStatusBar().getStatusBarWindowViewController().cancelExpandHelper();
+ getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
+ } else {
+ mBubbleControllerLazy.get().collapseStack();
+ }
+ }
+
+
+ @Override
+ public boolean closeShadeIfOpen() {
+ if (!getNotificationPanelView().isFullyCollapsed()) {
+ mCommandQueue.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+ getStatusBar().visibilityChanged(false);
+ mAssistManagerLazy.get().hideAssist();
+ }
+ return false;
+ }
+
+ @Override
+ public void postOnShadeExpanded(Runnable executable) {
+ getNotificationPanelView().getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (getStatusBar().getStatusBarWindow().getHeight()
+ != getStatusBar().getStatusBarHeight()) {
+ getNotificationPanelView().getViewTreeObserver()
+ .removeOnGlobalLayoutListener(this);
+ getNotificationPanelView().post(executable);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void addPostCollapseAction(Runnable action) {
+ mPostCollapseRunnables.add(action);
+ }
+
+ @Override
+ public void runPostCollapseRunnables() {
+ ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
+ mPostCollapseRunnables.clear();
+ int size = clonedList.size();
+ for (int i = 0; i < size; i++) {
+ clonedList.get(i).run();
+ }
+ mStatusBarKeyguardViewManager.readyForKeyguardDone();
+ }
+
+ @Override
+ public void goToLockedShade(View startingChild) {
+ // TODO: Move this code out of StatusBar into ShadeController.
+ getStatusBar().goToLockedShade(startingChild);
+ }
+
+ @Override
+ public boolean collapsePanel() {
+ if (!getNotificationPanelView().isFullyCollapsed()) {
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */, true /* delayed */);
+ getStatusBar().visibilityChanged(false);
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void collapsePanel(boolean animate) {
+ if (animate) {
+ boolean willCollapse = collapsePanel();
+ if (!willCollapse) {
+ runPostCollapseRunnables();
+ }
+ } else if (!getPresenter().isPresenterFullyCollapsed()) {
+ getStatusBar().instantCollapseNotificationPanel();
+ getStatusBar().visibilityChanged(false);
+ } else {
+ runPostCollapseRunnables();
+ }
+ }
+
+ private StatusBar getStatusBar() {
+ return mStatusBarLazy.get();
+ }
+
+ private NotificationPresenter getPresenter() {
+ return getStatusBar().getPresenter();
+ }
+
+ protected StatusBarWindowView getStatusBarWindowView() {
+ return getStatusBar().getStatusBarWindow();
+ }
+
+ protected PhoneStatusBarView getStatusBarView() {
+ return (PhoneStatusBarView) getStatusBar().getStatusBarView();
+ }
+
+ private NotificationPanelView getNotificationPanelView() {
+ return getStatusBar().getPanel();
+ }
+}
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 709143d..aaed610 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -107,7 +107,6 @@
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -125,7 +124,6 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.ActivityStarterDelegate;
import com.android.systemui.AutoReinflateContainer;
import com.android.systemui.DejankUtils;
import com.android.systemui.DemoMode;
@@ -138,13 +136,13 @@
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -232,9 +230,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Named;
import javax.inject.Provider;
@@ -245,13 +243,9 @@
ActivityStarter, KeyguardStateController.Callback,
OnHeadsUpChangedListener, CommandQueue.Callbacks,
ColorExtractor.OnColorsChangedListener, ConfigurationListener,
- StatusBarStateController.StateListener, ShadeController,
- ActivityLaunchAnimator.Callback {
+ StatusBarStateController.StateListener, ActivityLaunchAnimator.Callback {
public static final boolean MULTIUSER_DEBUG = false;
- public static final boolean ENABLE_CHILD_NOTIFICATIONS
- = SystemProperties.getBoolean("debug.child_notifs", true);
-
protected static final int MSG_HIDE_RECENT_APPS = 1020;
protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
@@ -387,6 +381,7 @@
private final Optional<Divider> mDividerOptional;
private final StatusBarNotificationActivityStarter.Builder
mStatusBarNotificationActivityStarterBuilder;
+ private final ShadeController mShadeController;
private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
private final LightsOutNotifController mLightsOutNotifController;
private final DismissCallbackRegistry mDismissCallbackRegistry;
@@ -409,7 +404,6 @@
private boolean mExpandedVisible;
private final int[] mAbsPos = new int[2];
- private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
@@ -478,7 +472,7 @@
private ViewMediatorCallback mKeyguardViewMediatorCallback;
private final ScrimController mScrimController;
protected DozeScrimController mDozeScrimController;
- private final UiOffloadThread mUiOffloadThread;
+ private final Executor mUiBgExecutor;
protected boolean mDozing;
@@ -641,7 +635,7 @@
NotificationAlertingManager notificationAlertingManager,
DisplayMetrics displayMetrics,
MetricsLogger metricsLogger,
- UiOffloadThread uiOffloadThread,
+ @UiBackground Executor uiBgExecutor,
NotificationMediaManager notificationMediaManager,
NotificationLockscreenUserManager lockScreenUserManager,
NotificationRemoteInputManager remoteInputManager,
@@ -683,6 +677,7 @@
LightsOutNotifController lightsOutNotifController,
StatusBarNotificationActivityStarter.Builder
statusBarNotificationActivityStarterBuilder,
+ ShadeController shadeController,
SuperStatusBarViewFactory superStatusBarViewFactory,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ViewMediatorCallback viewMediatorCallback,
@@ -714,7 +709,7 @@
mNotificationAlertingManager = notificationAlertingManager;
mDisplayMetrics = displayMetrics;
mMetricsLogger = metricsLogger;
- mUiOffloadThread = uiOffloadThread;
+ mUiBgExecutor = uiBgExecutor;
mMediaManager = notificationMediaManager;
mLockscreenUserManager = lockScreenUserManager;
mRemoteInputManager = remoteInputManager;
@@ -754,6 +749,7 @@
mRemoteInputUriController = remoteInputUriController;
mDividerOptional = dividerOptional;
mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder;
+ mShadeController = shadeController;
mSuperStatusBarViewFactory = superStatusBarViewFactory;
mLightsOutNotifController = lightsOutNotifController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -814,6 +810,8 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mWallpaperSupported =
+ mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
// Connect in to the status bar manager service
mCommandQueue.addCallback(this);
@@ -827,9 +825,6 @@
createAndAddWindows(result);
- mWallpaperSupported =
- mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
-
if (mWallpaperSupported) {
// Make sure we always have the most current wallpaper info.
IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
@@ -891,7 +886,7 @@
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController, mCommandQueue,
- mBroadcastDispatcher);
+ mBroadcastDispatcher, mUiBgExecutor);
mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController);
mKeyguardStateController.addCallback(this);
@@ -903,8 +898,6 @@
mStatusBarWindowViewController,
mNotificationPanel, mAmbientIndicationContainer);
- Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
-
mConfigurationController.addCallback(this);
// set the initial view visibility
@@ -1062,7 +1055,6 @@
if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
- mLockscreenWallpaper.setHandler(mHandler);
}
mKeyguardIndicationController =
@@ -1240,7 +1232,7 @@
mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
mNotificationAlertingManager, rowBinder, mKeyguardStateController,
mKeyguardIndicationController,
- this /* statusBar */, mCommandQueue);
+ this /* statusBar */, mShadeController, mCommandQueue);
mNotificationListController =
new NotificationListController(
@@ -1264,7 +1256,7 @@
mRemoteInputUriController.attach(mEntryManager);
rowBinder.setNotificationClicker(new NotificationClicker(
- this, mBubbleController, mNotificationActivityStarter));
+ Optional.of(this), mBubbleController, mNotificationActivityStarter));
mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
mNotificationListController.bind();
@@ -1272,6 +1264,7 @@
if (mFeatureFlags.isNewNotifPipelineEnabled()) {
mNewNotifPipeline.get().initialize(mNotificationListener);
}
+ mEntryManager.attach(mNotificationListener);
}
/**
@@ -1318,7 +1311,7 @@
mRemoteInputManager.checkRemoteInputOutside(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mExpandedVisible) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
}
}
return mStatusBarWindow.onTouchEvent(event);
@@ -1419,6 +1412,10 @@
return mStatusBarWindow;
}
+ public StatusBarWindowViewController getStatusBarWindowViewController() {
+ return mStatusBarWindowViewController;
+ }
+
protected ViewGroup getBouncerContainer() {
return mStatusBarWindow;
}
@@ -1574,7 +1571,7 @@
if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
}
}
@@ -1598,7 +1595,7 @@
if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
updateQsExpansionEnabled();
if ((state1 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
}
}
}
@@ -1840,7 +1837,7 @@
&& !mActivityLaunchAnimator.isLaunchForActivity()) {
onClosingFinished();
} else {
- collapsePanel(true /* animate */);
+ mShadeController.collapsePanel(true /* animate */);
}
}
@@ -1888,7 +1885,7 @@
animateExpandSettingsPanel((String) m.obj);
break;
case MSG_CLOSE_PANELS:
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
break;
case MSG_LAUNCH_TRANSITION_TIMEOUT:
onLaunchTransitionTimeout();
@@ -1985,20 +1982,13 @@
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
}
- public void animateCollapsePanels() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- }
-
- private final Runnable mAnimateCollapsePanels = this::animateCollapsePanels;
-
public void postAnimateCollapsePanels() {
- mHandler.post(mAnimateCollapsePanels);
+ mHandler.post(mShadeController::animateCollapsePanels);
}
public void postAnimateForceCollapsePanels() {
- mHandler.post(() -> {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
- });
+ mHandler.post(() -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
+ true /* force */));
}
public void postAnimateOpenPanels() {
@@ -2008,67 +1998,35 @@
@Override
public void togglePanel() {
if (mPanelExpanded) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
} else {
animateExpandNotificationsPanel();
}
}
- public void animateCollapsePanels(int flags) {
- animateCollapsePanels(flags, false /* force */, false /* delayed */,
+ @Override
+ public void animateCollapsePanels(int flags, boolean force) {
+ mShadeController.animateCollapsePanels(flags, force, false /* delayed */,
1.0f /* speedUpFactor */);
}
- @Override
- public void animateCollapsePanels(int flags, boolean force) {
- animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
- }
-
- public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
- animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
- }
-
- public void animateCollapsePanels(int flags, boolean force, boolean delayed,
- float speedUpFactor) {
- if (!force && mState != StatusBarState.SHADE) {
- runPostCollapseRunnables();
- return;
- }
- if (SPEW) {
- Log.d(TAG, "animateCollapse():"
- + " mExpandedVisible=" + mExpandedVisible
- + " flags=" + flags);
- }
-
- if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) {
- if (!mHandler.hasMessages(MSG_HIDE_RECENT_APPS)) {
- mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
- mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
- }
- }
-
- // TODO(b/62444020): remove when this bug is fixed
- Log.v(TAG, "mStatusBarWindow: " + mStatusBarWindow + " canPanelBeCollapsed(): "
- + mNotificationPanel.canPanelBeCollapsed());
- if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) {
- // release focus immediately to kick off focus change transition
- mStatusBarWindowController.setStatusBarFocusable(false);
-
- mStatusBarWindowViewController.cancelExpandHelper();
- mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
- } else {
- mBubbleController.collapseStack();
+ /**
+ * Called by {@link ShadeController} when it calls
+ * {@link ShadeController#animateCollapsePanels(int, boolean, boolean, float)}.
+ */
+ void postHideRecentApps() {
+ if (!mHandler.hasMessages(MSG_HIDE_RECENT_APPS)) {
+ mHandler.removeMessages(MSG_HIDE_RECENT_APPS);
+ mHandler.sendEmptyMessage(MSG_HIDE_RECENT_APPS);
}
}
- private void runPostCollapseRunnables() {
- ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
- mPostCollapseRunnables.clear();
- int size = clonedList.size();
- for (int i = 0; i < size; i++) {
- clonedList.get(i).run();
- }
- mStatusBarKeyguardViewManager.readyForKeyguardDone();
+ public boolean isExpandedVisible() {
+ return mExpandedVisible;
+ }
+
+ public boolean isPanelExpanded() {
+ return mPanelExpanded;
}
/**
@@ -2147,7 +2105,7 @@
mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
- runPostCollapseRunnables();
+ mShadeController.runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
showBouncerIfKeyguard();
@@ -2672,12 +2630,12 @@
}
if (dismissShade) {
if (mExpandedVisible && !mBouncerShowing) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
- true /* delayed*/);
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */, true /* delayed*/);
} else {
// Do it after DismissAction has been processed to conserve the needed ordering.
- mHandler.post(this::runPostCollapseRunnables);
+ mHandler.post(mShadeController::runPostCollapseRunnables);
}
} else if (isInLaunchTransition() && mNotificationPanel.isLaunchTransitionFinished()) {
@@ -2709,7 +2667,7 @@
if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
}
- animateCollapsePanels(flags);
+ mShadeController.animateCollapsePanels(flags);
}
}
else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
@@ -2803,7 +2761,11 @@
mScreenPinningRequest.onConfigurationChanged();
}
- @Override
+ /**
+ * Notify the shade controller that the current user changed
+ *
+ * @param newUserId userId of the new user
+ */
public void setLockscreenUser(int newUserId) {
if (mLockscreenWallpaper != null) {
mLockscreenWallpaper.setCurrentUser(newUserId);
@@ -2879,7 +2841,7 @@
notificationLoad = 1;
}
final int finalNotificationLoad = notificationLoad;
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
try {
mBarService.onPanelRevealed(clearNotificationEffects,
finalNotificationLoad);
@@ -2888,7 +2850,7 @@
}
});
} else {
- mUiOffloadThread.submit(() -> {
+ mUiBgExecutor.execute(() -> {
try {
mBarService.onPanelHidden();
} catch (RemoteException ex) {
@@ -3145,7 +3107,7 @@
private void updatePanelExpansionForKeyguard() {
if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
!= BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
- instantExpandNotificationsPanel();
+ mShadeController.instantExpandNotificationsPanel();
} else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
instantCollapseNotificationPanel();
}
@@ -3160,10 +3122,6 @@
mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
}
- public void addPostCollapseAction(Runnable r) {
- mPostCollapseRunnables.add(r);
- }
-
public boolean isInLaunchTransition() {
return mNotificationPanel.isLaunchTransitionRunning()
|| mNotificationPanel.isLaunchTransitionFinished();
@@ -3396,7 +3354,7 @@
public boolean onMenuPressed() {
if (shouldUnlockOnMenuPressed()) {
- animateCollapsePanels(
+ mShadeController.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
return true;
}
@@ -3426,7 +3384,7 @@
}
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
if (mNotificationPanel.canPanelBeCollapsed()) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
} else {
mBubbleController.performBackPressIfNeeded();
}
@@ -3440,7 +3398,7 @@
public boolean onSpacePressed() {
if (mDeviceInteractive && mState != StatusBarState.SHADE) {
- animateCollapsePanels(
+ mShadeController.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
return true;
}
@@ -3454,43 +3412,9 @@
}
}
- @Override
- public void instantExpandNotificationsPanel() {
- // Make our window larger and the panel expanded.
- makeExpandedVisible(true);
- mNotificationPanel.expand(false /* animate */);
- mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
- }
-
- @Override
- public boolean closeShadeIfOpen() {
- if (!mNotificationPanel.isFullyCollapsed()) {
- mCommandQueue.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
- visibilityChanged(false);
- mAssistManagerLazy.get().hideAssist();
- }
- return false;
- }
-
- @Override
- public void postOnShadeExpanded(Runnable executable) {
- mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (getStatusBarWindow().getHeight() != getStatusBarHeight()) {
- mNotificationPanel.getViewTreeObserver()
- .removeOnGlobalLayoutListener(this);
- mNotificationPanel.post(executable);
- }
- }
- });
- }
-
- private void instantCollapseNotificationPanel() {
+ void instantCollapseNotificationPanel() {
mNotificationPanel.instantCollapse();
- runPostCollapseRunnables();
+ mShadeController.runPostCollapseRunnables();
}
@Override
@@ -3576,11 +3500,11 @@
}
public void onTrackingStarted() {
- runPostCollapseRunnables();
+ mShadeController.runPostCollapseRunnables();
}
public void onClosingFinished() {
- runPostCollapseRunnables();
+ mShadeController.runPostCollapseRunnables();
if (!mPresenter.isPresenterFullyCollapsed()) {
// if we set it not to be focusable when collapsing, we have to undo it when we aborted
// the closing
@@ -3641,7 +3565,7 @@
*
* @param expandView The view to expand after going to the shade.
*/
- public void goToLockedShade(View expandView) {
+ void goToLockedShade(View expandView) {
if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
return;
}
@@ -3700,7 +3624,7 @@
mStatusBarWindowViewController.cancelCurrentTouch();
}
if (mPanelExpanded && mState == StatusBarState.SHADE) {
- animateCollapsePanels();
+ mShadeController.animateCollapsePanels();
}
}
@@ -4067,7 +3991,7 @@
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
if (BANNER_ACTION_SETUP.equals(action)) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */);
mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -4078,35 +4002,6 @@
}
};
- @Override
- public void collapsePanel(boolean animate) {
- if (animate) {
- boolean willCollapse = collapsePanel();
- if (!willCollapse) {
- runPostCollapseRunnables();
- }
- } else if (!mPresenter.isPresenterFullyCollapsed()) {
- instantCollapseNotificationPanel();
- visibilityChanged(false);
- } else {
- runPostCollapseRunnables();
- }
- }
-
- @Override
- public boolean collapsePanel() {
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
- true /* delayed */);
- visibilityChanged(false);
-
- return true;
- } else {
- return false;
- }
- }
-
private final NotificationListener mNotificationListener;
public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
@@ -4125,7 +4020,7 @@
}
void awakenDreams() {
- Dependency.get(UiOffloadThread.class).submit(() -> {
+ mUiBgExecutor.execute(() -> {
try {
mDreamManager.awaken();
} catch (RemoteException e) {
@@ -4217,7 +4112,7 @@
action.run();
}).start();
- return collapsePanel();
+ return mShadeController.collapsePanel();
}, afterKeyguardGone);
}
@@ -4277,7 +4172,7 @@
return options.toBundle();
}
- protected void visibilityChanged(boolean visible) {
+ void visibilityChanged(boolean visible) {
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
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 dac4e58..f51174b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -48,7 +48,6 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.RemoteInputController;
@@ -921,12 +920,6 @@
mStatusBar.keyguardGoingAway();
}
- public void animateCollapsePanels(float speedUpFactor) {
- mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
- false /* delayed */, speedUpFactor);
- }
-
-
/**
* Called when cancel button in bouncer is pressed.
*/
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 312c85f..ce56381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -27,11 +27,11 @@
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -75,6 +75,7 @@
import com.android.systemui.volume.VolumeComponent;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Named;
import javax.inject.Provider;
@@ -122,7 +123,7 @@
NotificationAlertingManager notificationAlertingManager,
DisplayMetrics displayMetrics,
MetricsLogger metricsLogger,
- UiOffloadThread uiOffloadThread,
+ @UiBackground Executor uiBgExecutor,
NotificationMediaManager notificationMediaManager,
NotificationLockscreenUserManager lockScreenUserManager,
NotificationRemoteInputManager remoteInputManager,
@@ -164,6 +165,7 @@
LightsOutNotifController lightsOutNotifController,
StatusBarNotificationActivityStarter.Builder
statusBarNotificationActivityStarterBuilder,
+ ShadeController shadeController,
SuperStatusBarViewFactory superStatusBarViewFactory,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ViewMediatorCallback viewMediatorCallback,
@@ -196,7 +198,7 @@
notificationAlertingManager,
displayMetrics,
metricsLogger,
- uiOffloadThread,
+ uiBgExecutor,
notificationMediaManager,
lockScreenUserManager,
remoteInputManager,
@@ -237,6 +239,7 @@
dividerOptional,
lightsOutNotifController,
statusBarNotificationActivityStarterBuilder,
+ shadeController,
superStatusBarViewFactory,
statusBarKeyguardViewManager,
viewMediatorCallback,
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 1988b42..661a7b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -47,13 +47,12 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.dagger.qualifiers.BgHandler;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -74,6 +73,8 @@
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -115,6 +116,7 @@
private final Handler mBackgroundHandler;
private final ActivityIntentHelper mActivityIntentHelper;
private final BubbleController mBubbleController;
+ private final Executor mUiBgExecutor;
private boolean mIsCollapsingToShowActivityOverLockscreen;
@@ -133,7 +135,7 @@
KeyguardStateController keyguardStateController,
NotificationInterruptionStateProvider notificationInterruptionStateProvider,
MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
- Handler mainThreadHandler, Handler backgroundHandler,
+ Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
ActivityIntentHelper activityIntentHelper, BubbleController bubbleController) {
mContext = context;
mNotificationPanel = panel;
@@ -160,6 +162,7 @@
mGroupManager = groupManager;
mLockPatternUtils = lockPatternUtils;
mBackgroundHandler = backgroundHandler;
+ mUiBgExecutor = uiBgExecutor;
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
@@ -418,7 +421,7 @@
} else {
// Stop screensaver if the notification has a fullscreen intent.
// (like an incoming phone call)
- Dependency.get(UiOffloadThread.class).submit(() -> {
+ mUiBgExecutor.execute(() -> {
try {
mDreamManager.awaken();
} catch (RemoteException e) {
@@ -521,10 +524,11 @@
private final LockPatternUtils mLockPatternUtils;
private final Handler mMainThreadHandler;
private final Handler mBackgroundHandler;
+ private final Executor mUiBgExecutor;
private final ActivityIntentHelper mActivityIntentHelper;
private final BubbleController mBubbleController;
private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
- private ShadeController mShadeController;
+ private final ShadeController mShadeController;
private NotificationPresenter mNotificationPresenter;
private ActivityLaunchAnimator mActivityLaunchAnimator;
private StatusBar mStatusBar;
@@ -549,10 +553,12 @@
NotificationInterruptionStateProvider notificationInterruptionStateProvider,
MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils,
- @MainHandler Handler mainThreadHandler,
- @BgHandler Handler backgroundHandler,
+ @Main Handler mainThreadHandler,
+ @Background Handler backgroundHandler,
+ @UiBackground Executor uiBgExecutor,
ActivityIntentHelper activityIntentHelper,
BubbleController bubbleController,
+ ShadeController shadeController,
SuperStatusBarViewFactory superStatusBarViewFactory) {
mContext = context;
mCommandQueue = commandQueue;
@@ -575,15 +581,16 @@
mLockPatternUtils = lockPatternUtils;
mMainThreadHandler = mainThreadHandler;
mBackgroundHandler = backgroundHandler;
+ mUiBgExecutor = uiBgExecutor;
mActivityIntentHelper = activityIntentHelper;
mBubbleController = bubbleController;
+ mShadeController = shadeController;
mSuperStatusBarViewFactory = superStatusBarViewFactory;
}
- /** Sets the status bar to use as {@link StatusBar} and {@link ShadeController}. */
+ /** Sets the status bar to use as {@link StatusBar}. */
public Builder setStatusBar(StatusBar statusBar) {
mStatusBar = statusBar;
- mShadeController = statusBar;
return this;
}
@@ -623,6 +630,7 @@
mLockPatternUtils,
mMainThreadHandler,
mBackgroundHandler,
+ mUiBgExecutor,
mActivityIntentHelper,
mBubbleController);
}
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 2649166..8fc624d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -88,7 +88,6 @@
private static final String TAG = "StatusBarNotificationPresenter";
- private final ShadeController mShadeController = Dependency.get(ShadeController.class);
private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
private final KeyguardStateController mKeyguardStateController;
private final NotificationViewHierarchyManager mViewHierarchyManager =
@@ -116,6 +115,7 @@
private final Context mContext;
private final KeyguardIndicationController mKeyguardIndicationController;
private final StatusBar mStatusBar;
+ private final ShadeController mShadeController;
private final CommandQueue mCommandQueue;
private final AccessibilityManager mAccessibilityManager;
@@ -145,6 +145,7 @@
KeyguardStateController keyguardStateController,
KeyguardIndicationController keyguardIndicationController,
StatusBar statusBar,
+ ShadeController shadeController,
CommandQueue commandQueue) {
mContext = context;
mKeyguardStateController = keyguardStateController;
@@ -154,6 +155,7 @@
mKeyguardIndicationController = keyguardIndicationController;
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mStatusBar = statusBar;
+ mShadeController = shadeController;
mCommandQueue = commandQueue;
mAboveShelfObserver = new AboveShelfObserver(stackScroller);
mActivityLaunchAnimator = activityLaunchAnimator;
@@ -387,7 +389,7 @@
}
updateNotificationViews();
mMediaManager.clearCurrentMediaNotification();
- mShadeController.setLockscreenUser(newUserId);
+ mStatusBar.setLockscreenUser(newUserId);
updateMediaMetaData(true, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 2012b57..6193a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -50,8 +50,6 @@
import javax.inject.Inject;
import javax.inject.Singleton;
-import dagger.Lazy;
-
/**
*/
@Singleton
@@ -62,9 +60,9 @@
private final SysuiStatusBarStateController mStatusBarStateController;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final ActivityStarter mActivityStarter;
- private final Lazy<ShadeController> mShadeControllerLazy;
private final Context mContext;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final ShadeController mShadeController;
private final ActivityIntentHelper mActivityIntentHelper;
private final NotificationGroupManager mGroupManager;
private View mPendingWorkRemoteInputView;
@@ -83,16 +81,16 @@
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- ActivityStarter activityStarter, Lazy<ShadeController> shadeControllerLazy,
+ ActivityStarter activityStarter, ShadeController shadeController,
CommandQueue commandQueue) {
mContext = context;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mShadeController = shadeController;
mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
mLockscreenUserManager = notificationLockscreenUserManager;
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
- mShadeControllerLazy = shadeControllerLazy;
mActivityStarter = activityStarter;
mStatusBarStateController.addCallback(this);
mKeyguardManager = context.getSystemService(KeyguardManager.class);
@@ -167,8 +165,8 @@
});
}
};
- mShadeControllerLazy.get().postOnShadeExpanded(clickPendingViewRunnable);
- mShadeControllerLazy.get().instantExpandNotificationsPanel();
+ mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
+ mShadeController.instantExpandNotificationsPanel();
}
}
@@ -256,7 +254,7 @@
boolean handled = defaultHandler.handleClick();
// close the shade if it was open and maybe wait for activity start.
- return handled && mShadeControllerLazy.get().closeShadeIfOpen();
+ return handled && mShadeController.closeShadeIfOpen();
}, null, afterKeyguardGone);
return true;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 2ecceba..ce498a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -41,7 +41,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -105,7 +105,7 @@
ConfigurationController configurationController,
KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor,
SuperStatusBarViewFactory superStatusBarViewFactory,
- @MainResources Resources resources) {
+ @Main Resources resources) {
mContext = context;
mWindowManager = windowManager;
mActivityManager = activityManager;
@@ -204,6 +204,7 @@
PixelFormat.TRANSLUCENT);
mLp.token = new Binder();
mLp.gravity = Gravity.TOP;
+ mLp.setFitWindowInsetsTypes(0 /* types */);
mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("StatusBar");
mLp.packageName = mContext.getPackageName();
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 f8929e0..eb86bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
@@ -57,12 +57,9 @@
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* Controller for {@link StatusBarWindowView}.
*/
-//@Singleton
public class StatusBarWindowViewController {
private final InjectionInflationController mInjectionInflationController;
private final NotificationWakeUpCoordinator mCoordinator;
@@ -80,7 +77,7 @@
private final DozeParameters mDozeParameters;
private final CommandQueue mCommandQueue;
private final StatusBarWindowView mView;
- private final Lazy<ShadeController> mShadeControllerLazy;
+ private final ShadeController mShadeController;
private GestureDetector mGestureDetector;
private View mBrightnessMirror;
@@ -114,7 +111,7 @@
DozeLog dozeLog,
DozeParameters dozeParameters,
CommandQueue commandQueue,
- Lazy<ShadeController> shadeControllerLazy,
+ ShadeController shadeController,
DockManager dockManager,
StatusBarWindowView statusBarWindowView) {
mInjectionInflationController = injectionInflationController;
@@ -133,7 +130,7 @@
mDozeParameters = dozeParameters;
mCommandQueue = commandQueue;
mView = statusBarWindowView;
- mShadeControllerLazy = shadeControllerLazy;
+ mShadeController = shadeController;
mDockManager = dockManager;
// This view is not part of the newly inflated expanded status bar.
@@ -153,7 +150,7 @@
mBypassController,
mFalsingManager,
mPluginManager,
- mShadeControllerLazy.get(),
+ mShadeController,
mNotificationLockscreenUserManager,
mNotificationEntryManager,
mKeyguardStateController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 44be6bc..28b6c38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -23,6 +23,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
+import android.view.Window;
+import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
@@ -102,16 +104,22 @@
public static void setWindowOnTop(Dialog dialog) {
if (Dependency.get(KeyguardStateController.class).isShowing()) {
- dialog.getWindow().setType(LayoutParams.TYPE_STATUS_BAR_PANEL);
+ final Window window = dialog.getWindow();
+ window.setType(LayoutParams.TYPE_STATUS_BAR_PANEL);
+ window.setFitWindowInsetsTypes(
+ window.getFitWindowInsetsTypes() & ~Type.statusBars());
} else {
dialog.getWindow().setType(LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
}
}
public static AlertDialog applyFlags(AlertDialog dialog) {
- dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
- dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ final Window window = dialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ window.setFitWindowInsetsTypes(
+ window.getFitWindowInsetsTypes() & ~Type.statusBars());
return dialog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index dc80906..f132058 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -34,8 +34,8 @@
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.BgHandler;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.power.EnhancedEstimates;
import java.io.FileDescriptor;
@@ -82,7 +82,7 @@
@Inject
BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates,
PowerManager powerManager, BroadcastDispatcher broadcastDispatcher,
- @MainHandler Handler mainHandler, @BgHandler Handler bgHandler) {
+ @Main Handler mainHandler, @Background Handler bgHandler) {
mContext = context;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 42e02d5..0c5b851 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -31,6 +31,8 @@
boolean isBluetoothConnected();
boolean isBluetoothConnecting();
+ boolean isBluetoothAudioProfileOnly();
+ boolean isBluetoothAudioActive();
String getConnectedDeviceName();
void setBluetoothEnabled(boolean enabled);
Collection<CachedBluetoothDevice> getDevices();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 76683b6..0fc3d84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -32,9 +32,10 @@
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.systemui.dagger.qualifiers.BgLooper;
-import com.android.systemui.dagger.qualifiers.MainLooper;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -65,6 +66,8 @@
private boolean mEnabled;
private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
+ private boolean mAudioProfileOnly;
+ private boolean mIsActive;
private final H mHandler;
private int mState;
@@ -72,8 +75,8 @@
/**
*/
@Inject
- public BluetoothControllerImpl(Context context, @BgLooper Looper bgLooper,
- @MainLooper Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) {
+ public BluetoothControllerImpl(Context context, @Background Looper bgLooper,
+ @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) {
mLocalBluetoothManager = localBluetoothManager;
mBgHandler = new Handler(bgLooper);
mHandler = new H(mainLooper);
@@ -103,6 +106,8 @@
}
pw.print(" mEnabled="); pw.println(mEnabled);
pw.print(" mConnectionState="); pw.println(stateToString(mConnectionState));
+ pw.print(" mAudioProfileOnly="); pw.println(mAudioProfileOnly);
+ pw.print(" mIsActive="); pw.println(mIsActive);
pw.print(" mConnectedDevices="); pw.println(mConnectedDevices);
pw.print(" mCallbacks.size="); pw.println(mHandler.mCallbacks.size());
pw.println(" Bluetooth Devices:");
@@ -176,6 +181,16 @@
}
@Override
+ public boolean isBluetoothAudioProfileOnly() {
+ return mAudioProfileOnly;
+ }
+
+ @Override
+ public boolean isBluetoothAudioActive() {
+ return mIsActive;
+ }
+
+ @Override
public void setBluetoothEnabled(boolean enabled) {
if (mLocalBluetoothManager != null) {
mLocalBluetoothManager.getBluetoothAdapter().setBluetoothEnabled(enabled);
@@ -239,6 +254,48 @@
mConnectionState = state;
mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
}
+ updateAudioProfile();
+ }
+
+ private void updateActive() {
+ boolean isActive = false;
+
+ for (CachedBluetoothDevice device : getDevices()) {
+ isActive |= device.isActiveDevice(BluetoothProfile.HEADSET)
+ || device.isActiveDevice(BluetoothProfile.A2DP)
+ || device.isActiveDevice(BluetoothProfile.HEARING_AID);
+ }
+
+ if (mIsActive != isActive) {
+ mIsActive = isActive;
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ }
+ }
+
+ private void updateAudioProfile() {
+ boolean audioProfileConnected = false;
+ boolean otherProfileConnected = false;
+
+ for (CachedBluetoothDevice device : getDevices()) {
+ for (LocalBluetoothProfile profile : device.getProfiles()) {
+ int profileId = profile.getProfileId();
+ boolean isConnected = device.isConnectedProfile(profile);
+ if (profileId == BluetoothProfile.HEADSET
+ || profileId == BluetoothProfile.A2DP
+ || profileId == BluetoothProfile.HEARING_AID) {
+ audioProfileConnected |= isConnected;
+ } else {
+ otherProfileConnected |= isConnected;
+ }
+ }
+ }
+
+ boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected);
+ if (audioProfileOnly != mAudioProfileOnly) {
+ mAudioProfileOnly = audioProfileOnly;
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ }
+
}
@Override
@@ -306,6 +363,16 @@
}
@Override
+ public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+ if (DEBUG) {
+ Log.d(TAG, "ActiveDeviceChanged=" + activeDevice.getAddress()
+ + " profileId=" + bluetoothProfile);
+ }
+ updateActive();
+ mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
+ }
+
+ @Override
public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
if (DEBUG) {
Log.d(TAG, "ACLConnectionStateChanged=" + cachedDevice.getAddress() + " "
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index b6ffd58..a3e2e76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -25,7 +25,7 @@
import android.util.Log;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.CurrentUserTracker;
import java.util.ArrayList;
@@ -40,7 +40,7 @@
DeviceProvisionedController {
protected static final String TAG = DeviceProvisionedControllerImpl.class.getSimpleName();
- private final ArrayList<DeviceProvisionedListener> mListeners = new ArrayList<>();
+ protected final ArrayList<DeviceProvisionedListener> mListeners = new ArrayList<>();
private final ContentResolver mContentResolver;
private final Context mContext;
private final Uri mDeviceProvisionedUri;
@@ -50,7 +50,7 @@
/**
*/
@Inject
- public DeviceProvisionedControllerImpl(Context context, @MainHandler Handler mainHandler,
+ public DeviceProvisionedControllerImpl(Context context, @Main Handler mainHandler,
BroadcastDispatcher broadcastDispatcher) {
super(broadcastDispatcher);
mContext = context;
@@ -104,7 +104,7 @@
}
}
- private void startListening(int user) {
+ protected void startListening(int user) {
mContentResolver.registerContentObserver(mDeviceProvisionedUri, true,
mSettingsObserver, 0);
mContentResolver.registerContentObserver(mUserSetupUri, true,
@@ -112,7 +112,7 @@
startTracking();
}
- private void stopListening() {
+ protected void stopListening() {
stopTracking();
mContentResolver.unregisterContentObserver(mSettingsObserver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index cd6ec05..df9c3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -26,7 +26,7 @@
import android.os.UserManager;
import android.util.Log;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -59,7 +59,7 @@
* Controller used to retrieve information related to a hotspot.
*/
@Inject
- public HotspotControllerImpl(Context context, @MainHandler Handler mainHandler) {
+ public HotspotControllerImpl(Context context, @Main Handler mainHandler) {
mContext = context;
mConnectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
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 d36bd75..570f153 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -37,7 +37,7 @@
import com.android.systemui.BootCompleteCache;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.util.Utils;
import java.util.ArrayList;
@@ -69,7 +69,7 @@
private final H mHandler = new H();
@Inject
- public LocationControllerImpl(Context context, @BgLooper Looper bgLooper,
+ public LocationControllerImpl(Context context, @Background Looper bgLooper,
BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
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 b0cd90c..a6108a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -91,7 +91,8 @@
@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.MAX_NETWORK_TYPE + 1;
+ 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.
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 24492bf..f20a47b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -64,7 +64,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
@@ -177,7 +177,7 @@
* Construct this controller object and register for updates.
*/
@Inject
- public NetworkControllerImpl(Context context, @BgLooper Looper bgLooper,
+ public NetworkControllerImpl(Context context, @Background Looper bgLooper,
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher) {
this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
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 c161458..019ef3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -49,7 +49,7 @@
import com.android.internal.net.VpnConfig;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.CurrentUserTracker;
import java.io.FileDescriptor;
@@ -101,7 +101,7 @@
/**
*/
@Inject
- public SecurityControllerImpl(Context context, @BgHandler Handler bgHandler,
+ public SecurityControllerImpl(Context context, @Background Handler bgHandler,
BroadcastDispatcher broadcastDispatcher) {
this(context, bgHandler, broadcastDispatcher, null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 347d300..86fe3008 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -28,7 +28,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -65,7 +65,7 @@
private final KeyValueListParser mParser = new KeyValueListParser(',');
@Inject
- public SmartReplyConstants(@MainHandler Handler handler, Context context) {
+ public SmartReplyConstants(@Main Handler handler, Context context) {
mHandler = handler;
mContext = context;
final Resources resources = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 13c0db9..2907cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -60,7 +60,7 @@
import com.android.systemui.R;
import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.tiles.UserDetailView;
@@ -113,7 +113,7 @@
@Inject
public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
- @MainHandler Handler handler, ActivityStarter activityStarter,
+ @Main Handler handler, ActivityStarter activityStarter,
BroadcastDispatcher broadcastDispatcher) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index a2028e6..4376a01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -40,7 +40,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.GlobalSetting;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.util.Utils;
@@ -78,7 +78,7 @@
private NotificationManager.Policy mConsolidatedNotificationPolicy;
@Inject
- public ZenModeControllerImpl(Context context, @MainHandler Handler handler,
+ public ZenModeControllerImpl(Context context, @Main Handler handler,
BroadcastDispatcher broadcastDispatcher) {
super(broadcastDispatcher);
mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java
index 9b685f0..74739e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java
@@ -16,32 +16,44 @@
package com.android.systemui.statusbar.tv;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.IntDef;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
+import android.util.ArraySet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
-import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.R;
-import java.util.ArrayList;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
-import java.util.List;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.Set;
+/**
+ * A component of {@link TvStatusBar} responsible for notifying the user whenever an application is
+ * recording audio.
+ *
+ * @see TvStatusBar
+ */
class AudioRecordingDisclosureBar {
private static final String TAG = "AudioRecordingDisclosureBar";
private static final boolean DEBUG = false;
@@ -50,121 +62,310 @@
// CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator";
- private static final int ANIM_DURATION_MS = 150;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"STATE_"}, value = {
+ STATE_NOT_SHOWN,
+ STATE_APPEARING,
+ STATE_SHOWN,
+ STATE_MINIMIZING,
+ STATE_MINIMIZED,
+ STATE_MAXIMIZING,
+ STATE_DISAPPEARING
+ })
+ public @interface State {}
+
+ private static final int STATE_NOT_SHOWN = 0;
+ private static final int STATE_APPEARING = 1;
+ private static final int STATE_SHOWN = 2;
+ private static final int STATE_MINIMIZING = 3;
+ private static final int STATE_MINIMIZED = 4;
+ private static final int STATE_MAXIMIZING = 5;
+ private static final int STATE_DISAPPEARING = 6;
+
+ private static final int ANIMATION_DURATION = 600;
+ private static final int MAXIMIZED_DURATION = 3000;
+ private static final int PULSE_BIT_DURATION = 1000;
+ private static final float PULSE_SCALE = 1.25f;
private final Context mContext;
- private final List<String> mAudioRecordingApps = new ArrayList<>();
- private View mView;
- private ViewGroup mAppsInfoContainer;
+
+ private View mIndicatorView;
+ private View mIconTextsContainer;
+ private View mIconContainerBg;
+ private View mIcon;
+ private View mBgRight;
+ private View mTextsContainers;
+ private TextView mTextView;
+
+ @State private int mState = STATE_NOT_SHOWN;
+ private final Set<String> mAudioRecordingApps = new HashSet<>();
+ private final Queue<String> mPendingNotifications = new LinkedList<>();
AudioRecordingDisclosureBar(Context context) {
mContext = context;
}
void start() {
- // Inflate and add audio recording disclosure bar
- createView();
-
// Register AppOpsManager callback
final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService(
Context.APP_OPS_SERVICE);
appOpsManager.startWatchingActive(
- new String[]{AppOpsManager.OPSTR_RECORD_AUDIO}, mContext.getMainExecutor(),
+ new String[]{AppOpsManager.OPSTR_RECORD_AUDIO},
+ mContext.getMainExecutor(),
new OnActiveRecordingListener());
}
- private void createView() {
- //TODO(b/142228704): this is to be re-implemented once proper design is completed
- mView = View.inflate(mContext,
- R.layout.tv_status_bar_audio_recording, null);
- mAppsInfoContainer = mView.findViewById(R.id.container);
+ private void onStartedRecording(String packageName) {
+ if (!mAudioRecordingApps.add(packageName)) {
+ // This app is already known to perform recording
+ return;
+ }
+
+ switch (mState) {
+ case STATE_NOT_SHOWN:
+ show(packageName);
+ break;
+
+ case STATE_MINIMIZED:
+ expand(packageName);
+ break;
+
+ case STATE_DISAPPEARING:
+ case STATE_APPEARING:
+ case STATE_MAXIMIZING:
+ case STATE_SHOWN:
+ case STATE_MINIMIZING:
+ // Currently animating or expanded. Thus add to the pending notifications, and it
+ // will be picked up once the indicator comes to the STATE_MINIMIZED.
+ mPendingNotifications.add(packageName);
+ break;
+ }
+ }
+
+ private void onDoneRecording(String packageName) {
+ if (!mAudioRecordingApps.remove(packageName)) {
+ // Was not marked as an active recorder, do nothing
+ return;
+ }
+
+ // If not MINIMIZED, will check whether the indicator should be hidden when the indicator
+ // comes to the STATE_MINIMIZED eventually. If is in the STATE_MINIMIZED, but there are
+ // other active recorders - simply ignore.
+ if (mState == STATE_MINIMIZED && mAudioRecordingApps.isEmpty()) {
+ hide();
+ }
+ }
+
+ private void show(String packageName) {
+ // Inflate the indicator view
+ mIndicatorView = LayoutInflater.from(mContext).inflate(
+ R.layout.tv_audio_recording_indicator,
+ null);
+ mIconTextsContainer = mIndicatorView.findViewById(R.id.icon_texts_container);
+ mIconContainerBg = mIconTextsContainer.findViewById(R.id.icon_container_bg);
+ mIcon = mIconTextsContainer.findViewById(R.id.icon_mic);
+ mTextsContainers = mIconTextsContainer.findViewById(R.id.texts_container);
+ mTextView = mTextsContainers.findViewById(R.id.text);
+ mBgRight = mIndicatorView.findViewById(R.id.bg_right);
+
+ // Set up the notification text
+ final String label = getApplicationLabel(packageName);
+ mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
+
+ // Initially change the visibility to INVISIBLE, wait until and receives the size and
+ // then animate it moving from "off" the screen correctly
+ mIndicatorView.setVisibility(View.INVISIBLE);
+ mIndicatorView
+ .getViewTreeObserver()
+ .addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // Remove the observer
+ mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
+ this);
+
+ // Now that the width of the indicator has been assigned, we can
+ // move it in from off the screen.
+ final int initialOffset = mIndicatorView.getWidth();
+ final AnimatorSet set = new AnimatorSet();
+ set.setDuration(ANIMATION_DURATION);
+ set.playTogether(
+ ObjectAnimator.ofFloat(mIndicatorView,
+ View.TRANSLATION_X, initialOffset, 0),
+ ObjectAnimator.ofFloat(mIndicatorView, View.ALPHA, 0f,
+ 1f));
+ set.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation,
+ boolean isReverse) {
+ // Indicator is INVISIBLE at the moment, change it.
+ mIndicatorView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ startPulsatingAnimation();
+ onExpanded();
+ }
+ });
+ set.start();
+ }
+ });
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
- MATCH_PARENT,
+ WRAP_CONTENT,
WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
- layoutParams.gravity = Gravity.BOTTOM;
+ layoutParams.gravity = Gravity.TOP | Gravity.RIGHT;
layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
layoutParams.packageName = mContext.getPackageName();
-
final WindowManager windowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
- windowManager.addView(mView, layoutParams);
+ windowManager.addView(mIndicatorView, layoutParams);
- // Set invisible first until it gains its actual size and we are able to hide it by moving
- // off the screen
- mView.setVisibility(View.INVISIBLE);
- mView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
+ mState = STATE_APPEARING;
+ }
+
+ private void expand(String packageName) {
+ final String label = getApplicationLabel(packageName);
+ mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));
+
+ final AnimatorSet set = new AnimatorSet();
+ set.playTogether(
+ ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, 0),
+ ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 1f),
+ ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 1f),
+ ObjectAnimator.ofFloat(mBgRight, View.ALPHA, 1f));
+ set.setDuration(ANIMATION_DURATION);
+ set.addListener(
+ new AnimatorListenerAdapter() {
@Override
- public void onGlobalLayout() {
- // Now that we get the height, we can move the bar off ("below") the screen
- final int height = mView.getHeight();
- mView.setTranslationY(height);
- // Remove the observer
- mView.getViewTreeObserver()
- .removeOnGlobalLayoutListener(this);
- // Now, that the view has been measured, and the translation was set to
- // move it off the screen, we change the visibility to GONE
- mView.setVisibility(View.GONE);
+ public void onAnimationEnd(Animator animation) {
+ onExpanded();
}
});
+ set.start();
+
+ mState = STATE_MAXIMIZING;
}
- private void showAudioRecordingDisclosureBar() {
- mView.setVisibility(View.VISIBLE);
- mView.animate()
- .translationY(0f)
- .setDuration(ANIM_DURATION_MS)
- .start();
+ private void minimize() {
+ final int targetOffset = mTextsContainers.getWidth();
+ final AnimatorSet set = new AnimatorSet();
+ set.playTogether(
+ ObjectAnimator.ofFloat(mIconTextsContainer, View.TRANSLATION_X, targetOffset),
+ ObjectAnimator.ofFloat(mIconContainerBg, View.ALPHA, 0f),
+ ObjectAnimator.ofFloat(mTextsContainers, View.ALPHA, 0f),
+ ObjectAnimator.ofFloat(mBgRight, View.ALPHA, 0f));
+ set.setDuration(ANIMATION_DURATION);
+ set.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onMinimized();
+ }
+ });
+ set.start();
+
+ mState = STATE_MINIMIZING;
}
- private void addToAudioRecordingDisclosureBar(String packageName) {
+ private void hide() {
+ final int targetOffset =
+ mIndicatorView.getWidth() - (int) mIconTextsContainer.getTranslationX();
+ final AnimatorSet set = new AnimatorSet();
+ set.playTogether(
+ ObjectAnimator.ofFloat(mIndicatorView, View.TRANSLATION_X, targetOffset),
+ ObjectAnimator.ofFloat(mIcon, View.ALPHA, 0f));
+ set.setDuration(ANIMATION_DURATION);
+ set.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onHidden();
+ }
+ });
+ set.start();
+
+ mState = STATE_DISAPPEARING;
+ }
+
+ private void onExpanded() {
+ mState = STATE_SHOWN;
+
+ mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
+ }
+
+ private void onMinimized() {
+ mState = STATE_MINIMIZED;
+
+ if (!mPendingNotifications.isEmpty()) {
+ // There is a new application that started recording, tell the user about it.
+ expand(mPendingNotifications.poll());
+ } else if (mAudioRecordingApps.isEmpty()) {
+ // Nobody is recording anymore, remove the indicator.
+ hide();
+ }
+ }
+
+ private void onHidden() {
+ final WindowManager windowManager = (WindowManager) mContext.getSystemService(
+ Context.WINDOW_SERVICE);
+ windowManager.removeView(mIndicatorView);
+
+ mIndicatorView = null;
+ mIconTextsContainer = null;
+ mIconContainerBg = null;
+ mIcon = null;
+ mTextsContainers = null;
+ mTextView = null;
+ mBgRight = null;
+
+ mState = STATE_NOT_SHOWN;
+ }
+
+ private void startPulsatingAnimation() {
+ final View pulsatingView = mIconTextsContainer.findViewById(R.id.pulsating_circle);
+ final ObjectAnimator animator =
+ ObjectAnimator.ofPropertyValuesHolder(
+ pulsatingView,
+ PropertyValuesHolder.ofFloat(View.SCALE_X, PULSE_SCALE),
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, PULSE_SCALE));
+ animator.setDuration(PULSE_BIT_DURATION);
+ animator.setRepeatCount(ObjectAnimator.INFINITE);
+ animator.setRepeatMode(ObjectAnimator.REVERSE);
+ animator.start();
+ }
+
+ private String getApplicationLabel(String packageName) {
final PackageManager pm = mContext.getPackageManager();
final ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
- return;
+ return packageName;
}
- final CharSequence label = pm.getApplicationLabel(appInfo);
- final Drawable icon = pm.getApplicationIcon(appInfo);
-
- final View view = LayoutInflater.from(mContext).inflate(R.layout.tv_item_app_info,
- mAppsInfoContainer, false);
- ((TextView) view.findViewById(R.id.title)).setText(label);
- ((ImageView) view.findViewById(R.id.icon)).setImageDrawable(icon);
-
- mAppsInfoContainer.addView(view);
- }
-
- private void removeFromAudioRecordingDisclosureBar(int index) {
- mAppsInfoContainer.removeViewAt(index);
- }
-
- private void hideAudioRecordingDisclosureBar() {
- final ViewPropertyAnimator animator = mView.animate();
- animator.translationY(mView.getHeight())
- .setDuration(ANIM_DURATION_MS)
- .withEndAction(() -> mView.setVisibility(View.GONE))
- .start();
+ return pm.getApplicationLabel(appInfo).toString();
}
private class OnActiveRecordingListener implements AppOpsManager.OnOpActiveChangedListener {
- private final List<String> mExemptApps;
+ private final Set<String> mExemptApps;
private OnActiveRecordingListener() {
- mExemptApps = Arrays.asList(mContext.getResources().getStringArray(
- R.array.audio_recording_disclosure_exempt_apps));
+ mExemptApps = new ArraySet<>(Arrays.asList(mContext.getResources().getStringArray(
+ R.array.audio_recording_disclosure_exempt_apps)));
}
@Override
public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
if (DEBUG) {
Log.d(TAG,
- "OP_RECORD_AUDIO active change, active=" + active + ", app=" + packageName);
+ "OP_RECORD_AUDIO active change, active=" + active + ", app="
+ + packageName);
}
if (mExemptApps.contains(packageName)) {
@@ -174,37 +375,10 @@
return;
}
- final boolean alreadyTracking = mAudioRecordingApps.contains(packageName);
- if ((active && alreadyTracking) || (!active && !alreadyTracking)) {
- if (DEBUG) {
- Log.d(TAG, "\t- nothing changed");
- }
- return;
- }
-
if (active) {
- if (DEBUG) {
- Log.d(TAG, "\t- new recording app");
- }
-
- if (mAudioRecordingApps.isEmpty()) {
- showAudioRecordingDisclosureBar();
- }
-
- mAudioRecordingApps.add(packageName);
- addToAudioRecordingDisclosureBar(packageName);
+ onStartedRecording(packageName);
} else {
- if (DEBUG) {
- Log.d(TAG, "\t- not recording any more");
- }
-
- final int index = mAudioRecordingApps.indexOf(packageName);
- removeFromAudioRecordingDisclosureBar(index);
- mAudioRecordingApps.remove(index);
-
- if (mAudioRecordingApps.isEmpty()) {
- hideAudioRecordingDisclosureBar();
- }
+ onDoneRecording(packageName);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index f9d39b0..7758aba 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -36,7 +36,7 @@
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.Background;
import com.google.android.collect.Sets;
@@ -70,7 +70,7 @@
@Inject
public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
- @BgHandler Handler bgHandler) {
+ @Background Handler bgHandler) {
super(context);
mBroadcastDispatcher = broadcastDispatcher;
mBgHandler = bgHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index ce0032e..19f0ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -35,7 +35,7 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.DemoMode;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -82,7 +82,7 @@
/**
*/
@Inject
- public TunerServiceImpl(Context context, @MainHandler Handler mainHandler,
+ public TunerServiceImpl(Context context, @Main Handler mainHandler,
LeakDetector leakDetector, BroadcastDispatcher broadcastDispatcher) {
mContext = context;
mContentResolver = mContext.getContentResolver();
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
index 2f13f39..367d4d2 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java
@@ -16,19 +16,13 @@
package com.android.systemui.usb;
-import android.app.Activity;
import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.IntentFilter;
import android.debug.IAdbManager;
-import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.util.EventLog;
import android.util.Log;
import android.view.LayoutInflater;
@@ -42,25 +36,14 @@
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import javax.inject.Inject;
public class UsbDebuggingActivity extends AlertActivity
implements DialogInterface.OnClickListener {
private static final String TAG = "UsbDebuggingActivity";
private CheckBox mAlwaysAllow;
- private UsbDisconnectedReceiver mDisconnectedReceiver;
- private final BroadcastDispatcher mBroadcastDispatcher;
private String mKey;
- @Inject
- public UsbDebuggingActivity(BroadcastDispatcher broadcastDispatcher) {
- super();
- mBroadcastDispatcher = broadcastDispatcher;
- }
-
@Override
public void onCreate(Bundle icicle) {
Window window = getWindow();
@@ -70,10 +53,6 @@
super.onCreate(icicle);
- if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
- mDisconnectedReceiver = new UsbDisconnectedReceiver(this);
- }
-
Intent intent = getIntent();
String fingerprints = intent.getStringExtra("fingerprints");
mKey = intent.getStringExtra("key");
@@ -126,40 +105,6 @@
super.onWindowAttributesChanged(params);
}
- private class UsbDisconnectedReceiver extends BroadcastReceiver {
- private final Activity mActivity;
- public UsbDisconnectedReceiver(Activity activity) {
- mActivity = activity;
- }
-
- @Override
- public void onReceive(Context content, Intent intent) {
- String action = intent.getAction();
- if (!UsbManager.ACTION_USB_STATE.equals(action)) {
- return;
- }
- boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
- if (!connected) {
- mActivity.finish();
- }
- }
- }
-
- @Override
- public void onStart() {
- super.onStart();
- IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE);
- mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter);
- }
-
- @Override
- protected void onStop() {
- if (mDisconnectedReceiver != null) {
- mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver);
- }
- super.onStop();
- }
-
@Override
public void onClick(DialogInterface dialog, int which) {
boolean allow = (which == AlertDialog.BUTTON_POSITIVE);
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
index 032b72e..4214242 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingSecondaryUserActivity.java
@@ -16,41 +16,19 @@
package com.android.systemui.usb;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbManager;
import android.os.Bundle;
-import android.os.SystemProperties;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import javax.inject.Inject;
public class UsbDebuggingSecondaryUserActivity extends AlertActivity
implements DialogInterface.OnClickListener {
- private UsbDisconnectedReceiver mDisconnectedReceiver;
- private final BroadcastDispatcher mBroadcastDispatcher;
-
- @Inject
- public UsbDebuggingSecondaryUserActivity(BroadcastDispatcher broadcastDispatcher) {
- mBroadcastDispatcher = broadcastDispatcher;
- }
-
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) {
- mDisconnectedReceiver = new UsbDisconnectedReceiver(this);
- }
-
final AlertController.AlertParams ap = mAlertParams;
ap.mTitle = getString(R.string.usb_debugging_secondary_user_title);
ap.mMessage = getString(R.string.usb_debugging_secondary_user_message);
@@ -60,40 +38,6 @@
setupAlert();
}
- private class UsbDisconnectedReceiver extends BroadcastReceiver {
- private final Activity mActivity;
- public UsbDisconnectedReceiver(Activity activity) {
- mActivity = activity;
- }
-
- @Override
- public void onReceive(Context content, Intent intent) {
- String action = intent.getAction();
- if (UsbManager.ACTION_USB_STATE.equals(action)) {
- boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
- if (!connected) {
- mActivity.finish();
- }
- }
- }
- }
-
- @Override
- public void onStart() {
- super.onStart();
-
- IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE);
- mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter);
- }
-
- @Override
- protected void onStop() {
- if (mDisconnectedReceiver != null) {
- mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver);
- }
- super.onStop();
- }
-
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
new file mode 100644
index 0000000..cca76bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -0,0 +1,718 @@
+/*
+ * 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.animation
+
+import android.os.Looper
+import android.util.ArrayMap
+import android.util.Log
+import android.view.View
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FlingAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
+import java.util.WeakHashMap
+
+/**
+ * Extension function for all objects which will return a PhysicsAnimator instance for that object.
+ */
+val <T : View> T.physicsAnimator: PhysicsAnimator<T> get() { return getInstance(this) }
+
+private const val TAG = "PhysicsAnimator"
+
+typealias EndAction = () -> Unit
+
+/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
+typealias UpdateMap<T> =
+ ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+/**
+ * Map of the animators associated with a given object. This ensures that only one animator
+ * per object exists.
+ */
+internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
+
+/**
+ * Default spring configuration to use for animations where stiffness and/or damping ratio
+ * were not provided.
+ */
+private val defaultSpring = PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM,
+ SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+
+/** Default fling configuration to use for animations where friction was not provided. */
+private val defaultFling = PhysicsAnimator.FlingConfig(
+ friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
+
+/** Whether to log helpful debug information about animations. */
+private var verboseLogging = false
+
+/**
+ * Animator that uses physics-based animations to animate properties on views and objects. Physics
+ * animations use real-world physical concepts, such as momentum and mass, to realistically simulate
+ * motion. PhysicsAnimator is heavily inspired by [android.view.ViewPropertyAnimator], and
+ * also uses the builder pattern to configure and start animations.
+ *
+ * The physics animations are backed by [DynamicAnimation].
+ *
+ * @param T The type of the object being animated.
+ */
+class PhysicsAnimator<T> private constructor (val target: T) {
+
+ /** Data class for representing animation frame updates. */
+ data class AnimationUpdate(val value: Float, val velocity: Float)
+
+ /** [DynamicAnimation] instances for the given properties. */
+ private val springAnimations = ArrayMap<FloatPropertyCompat<in T>, SpringAnimation>()
+ private val flingAnimations = ArrayMap<FloatPropertyCompat<in T>, FlingAnimation>()
+
+ /**
+ * Spring and fling configurations for the properties to be animated on the target. We'll
+ * configure and start the DynamicAnimations for these properties according to the provided
+ * configurations.
+ */
+ private val springConfigs = ArrayMap<FloatPropertyCompat<in T>, SpringConfig>()
+ private val flingConfigs = ArrayMap<FloatPropertyCompat<in T>, FlingConfig>()
+
+ /**
+ * Animation listeners for the animation. These will be notified when each property animation
+ * updates or ends.
+ */
+ private val updateListeners = ArrayList<UpdateListener<T>>()
+ private val endListeners = ArrayList<EndListener<T>>()
+
+ /** End actions to run when all animations have completed. */
+ private val endActions = ArrayList<EndAction>()
+
+ /**
+ * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
+ * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
+ * just one permanent update and end listener to the DynamicAnimations.
+ */
+ internal var internalListeners = ArrayList<InternalListener>()
+
+ /**
+ * Action to run when [start] is called. This can be changed by
+ * [PhysicsAnimatorTestUtils.prepareForTest] to enable animators to run under test and provide
+ * helpful test utilities.
+ */
+ internal var startAction: () -> Unit = ::startInternal
+
+ /**
+ * Springs a property to the given value, using the provided configuration settings.
+ *
+ * Springs are used when you know the exact value to which you want to animate. They can be
+ * configured with a start velocity (typically used when the spring is initiated by a touch
+ * event), but this velocity will be realistically attenuated as forces are applied to move the
+ * property towards the end value.
+ *
+ * If you find yourself repeating the same stiffness and damping ratios many times, consider
+ * storing a single [SpringConfig] instance and passing that in instead of individual values.
+ *
+ * @param property The property to spring to the given value. The property must be an instance
+ * of FloatPropertyCompat<? super T>. For example, if this is a
+ * PhysicsAnimator<FrameLayout>, you can use a FloatPropertyCompat<FrameLayout>, as
+ * well as a FloatPropertyCompat<ViewGroup>, and so on.
+ * @param toPosition The value to spring the given property to.
+ * @param startVelocity The initial velocity to use for the animation.
+ * @param stiffness The stiffness to use for the spring. Higher stiffness values result in
+ * faster animations, while lower stiffness means a slower animation. Reasonable values for
+ * low, medium, and high stiffness can be found as constants in [SpringForce].
+ * @param dampingRatio The damping ratio (bounciness) to use for the spring. Higher values
+ * result in a less 'springy' animation, while lower values allow the animation to bounce
+ * back and forth for a longer time after reaching the final position. Reasonable values for
+ * low, medium, and high damping can be found in [SpringForce].
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ startVelocity: Float = 0f,
+ stiffness: Float = defaultSpring.stiffness,
+ dampingRatio: Float = defaultSpring.dampingRatio
+ ): PhysicsAnimator<T> {
+ if (verboseLogging) {
+ Log.d(TAG, "Springing ${getReadablePropertyName(property)} to $toPosition.")
+ }
+
+ springConfigs[property] =
+ SpringConfig(stiffness, dampingRatio, startVelocity, toPosition)
+ return this
+ }
+
+ /**
+ * Springs a property to a given value using the provided start velocity and configuration
+ * options.
+ *
+ * @see spring
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ startVelocity: Float,
+ config: SpringConfig = defaultSpring
+ ): PhysicsAnimator<T> {
+ return spring(
+ property, toPosition, startVelocity, config.stiffness, config.dampingRatio)
+ }
+
+ /**
+ * Springs a property to a given value using the provided configuration options, and a start
+ * velocity of 0f.
+ *
+ * @see spring
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float,
+ config: SpringConfig = defaultSpring
+ ): PhysicsAnimator<T> {
+ return spring(property, toPosition, 0f, config)
+ }
+
+ /**
+ * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+ * the provided configuration settings.
+ *
+ * Flings are used when you have a start velocity, and want the property value to realistically
+ * decrease as friction is applied until the velocity reaches zero. Flings do not have a
+ * deterministic end value. If you are attempting to animate to a specific end value, use
+ * [spring].
+ *
+ * If you find yourself repeating the same friction/min/max values, consider storing a single
+ * [FlingConfig] and passing that in instead.
+ *
+ * @param property The property to fling using the given start velocity.
+ * @param startVelocity The start velocity (in pixels per second) with which to start the fling.
+ * @param friction Friction value applied to slow down the animation over time. Higher values
+ * will more quickly slow the animation. Typical friction values range from 1f to 10f.
+ * @param min The minimum value allowed for the animation. If this value is reached, the
+ * animation will end abruptly.
+ * @param max The maximum value allowed for the animation. If this value is reached, the
+ * animation will end abruptly.
+ */
+ fun fling(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ friction: Float = defaultFling.friction,
+ min: Float = defaultFling.min,
+ max: Float = defaultFling.max
+ ): PhysicsAnimator<T> {
+ if (verboseLogging) {
+ Log.d(TAG, "Flinging ${getReadablePropertyName(property)} " +
+ "with velocity $startVelocity.")
+ }
+
+ flingConfigs[property] = FlingConfig(friction, min, max, startVelocity)
+ return this
+ }
+
+ /**
+ * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+ * the provided configuration settings.
+ *
+ * @see fling
+ */
+ fun fling(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ config: FlingConfig = defaultFling
+ ): PhysicsAnimator<T> {
+ return fling(property, startVelocity, config.friction, config.min, config.max)
+ }
+
+ /**
+ * 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.
+ */
+ fun addUpdateListener(listener: UpdateListener<T>): PhysicsAnimator<T> {
+ updateListeners.add(listener)
+ return this
+ }
+
+ /**
+ * Adds a listener that will be called whenever a property's animation ends. 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].
+ */
+ fun addEndListener(listener: EndListener<T>): PhysicsAnimator<T> {
+ endListeners.add(listener)
+ return this
+ }
+
+ /**
+ * Adds end actions that will be run sequentially when animations for every property involved in
+ * this specific animation have ended (unless they were explicitly canceled). For example, if
+ * you call:
+ *
+ * animator
+ * .spring(TRANSLATION_X, ...)
+ * .spring(TRANSLATION_Y, ...)
+ * .withEndAction(action)
+ * .start()
+ *
+ * 'action' will be run when both TRANSLATION_X and TRANSLATION_Y end.
+ *
+ * Other properties may still be animating, if those animations were not started in the same
+ * call. For example:
+ *
+ * animator
+ * .spring(ALPHA, ...)
+ * .start()
+ *
+ * animator
+ * .spring(TRANSLATION_X, ...)
+ * .spring(TRANSLATION_Y, ...)
+ * .withEndAction(action)
+ * .start()
+ *
+ * 'action' will still be run as soon as TRANSLATION_X and TRANSLATION_Y end, even if ALPHA is
+ * still animating.
+ *
+ * If you want to run actions as soon as a subset of property animations have ended, you want
+ * access to the animation's end value/velocity, or you want to run these actions even if the
+ * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
+ * which indicates that all relevant animations have ended.
+ */
+ fun withEndActions(vararg endActions: EndAction): PhysicsAnimator<T> {
+ this.endActions.addAll(endActions)
+ return this
+ }
+
+ /** Starts the animations! */
+ fun start() {
+ startAction()
+ }
+
+ /**
+ * Starts the animations for real! This is typically called immediately by [start] unless this
+ * animator is under test.
+ */
+ internal fun startInternal() {
+ if (!Looper.getMainLooper().isCurrentThread) {
+ Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
+ "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
+ "your test setup.")
+ }
+
+ // Add an internal listener that will dispatch animation events to the provided listeners.
+ internalListeners.add(InternalListener(
+ getAnimatedProperties(),
+ ArrayList(updateListeners),
+ 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()
+ }
+
+ clearAnimator()
+ }
+
+ /** Clear the animator's builder variables. */
+ private fun clearAnimator() {
+ springConfigs.clear()
+ flingConfigs.clear()
+
+ updateListeners.clear()
+ endListeners.clear()
+ endActions.clear()
+ }
+
+ /** Retrieves a spring animation for the given property, building one if needed. */
+ private fun getSpringAnimation(property: FloatPropertyCompat<in T>): SpringAnimation {
+ return springAnimations.getOrPut(
+ property,
+ { configureDynamicAnimation(SpringAnimation(target, property), property)
+ as SpringAnimation })
+ }
+
+ /** Retrieves a fling animation for the given property, building one if needed. */
+ private fun getFlingAnimation(property: FloatPropertyCompat<in T>): FlingAnimation {
+ return flingAnimations.getOrPut(
+ property,
+ { configureDynamicAnimation(FlingAnimation(target, property), property)
+ as FlingAnimation })
+ }
+
+ /**
+ * Adds update and end listeners to the DynamicAnimation which will dispatch to the internal
+ * listeners.
+ */
+ private fun configureDynamicAnimation(
+ anim: DynamicAnimation<*>,
+ property: FloatPropertyCompat<in T>
+ ): DynamicAnimation<*> {
+ anim.addUpdateListener { _, value, velocity ->
+ for (i in 0 until internalListeners.size) {
+ internalListeners[i].onInternalAnimationUpdate(property, value, velocity)
+ }
+ }
+ anim.addEndListener { _, canceled, value, velocity ->
+ internalListeners.removeAll {
+ it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+ return anim
+ }
+
+ /**
+ * Internal listener class that receives updates from DynamicAnimation listeners, and dispatches
+ * them to the appropriate update/end listeners. This class is also aware of which properties
+ * were being animated when the end listeners were passed in, so that we can provide the
+ * appropriate value for allEnded to [EndListener.onAnimationEnd].
+ */
+ internal inner class InternalListener constructor(
+ private var properties: Set<FloatPropertyCompat<in T>>,
+ private var updateListeners: List<UpdateListener<T>>,
+ private var endListeners: List<EndListener<T>>,
+ private var endActions: List<EndAction>
+ ) {
+
+ /** The number of properties whose animations haven't ended. */
+ private var numPropertiesAnimating = properties.size
+
+ /**
+ * Update values that haven't yet been dispatched because not all property animations have
+ * updated yet.
+ */
+ private val undispatchedUpdates =
+ ArrayMap<FloatPropertyCompat<in T>, AnimationUpdate>()
+
+ /** Called when a DynamicAnimation updates. */
+ internal fun onInternalAnimationUpdate(
+ property: FloatPropertyCompat<in T>,
+ value: Float,
+ velocity: Float
+ ) {
+
+ // If this property animation isn't relevant to this listener, ignore it.
+ if (!properties.contains(property)) {
+ return
+ }
+
+ undispatchedUpdates[property] = AnimationUpdate(value, velocity)
+ maybeDispatchUpdates()
+ }
+
+ /**
+ * Called when a DynamicAnimation ends.
+ *
+ * @return True if this listener should be removed from the list of internal listeners, so
+ * it no longer receives updates from DynamicAnimations.
+ */
+ internal fun onInternalAnimationEnd(
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float
+ ): Boolean {
+
+ // If this property animation isn't relevant to this listener, ignore it.
+ if (!properties.contains(property)) {
+ return false
+ }
+
+ // Dispatch updates if we have one for each property.
+ numPropertiesAnimating--
+ maybeDispatchUpdates()
+
+ // If we didn't have an update for each property, dispatch the update for the ending
+ // property. This guarantees that an update isn't sent for this property *after* we call
+ // onAnimationEnd for that property.
+ if (undispatchedUpdates.contains(property)) {
+ updateListeners.forEach { updateListener ->
+ updateListener.onAnimationUpdateForProperty(
+ target,
+ UpdateMap<T>().also { it[property] = undispatchedUpdates[property] })
+ }
+
+ undispatchedUpdates.remove(property)
+ }
+
+ val allEnded = !arePropertiesAnimating(properties)
+ endListeners.forEach {
+ it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+
+ // If all of the animations that this listener cares about have ended, run the end
+ // actions unless the animation was canceled.
+ if (allEnded && !canceled) {
+ endActions.forEach { it() }
+ }
+
+ return allEnded
+ }
+
+ /**
+ * Dispatch undispatched values if we've received an update from each of the animating
+ * properties.
+ */
+ private fun maybeDispatchUpdates() {
+ if (undispatchedUpdates.size >= numPropertiesAnimating &&
+ undispatchedUpdates.size > 0) {
+ updateListeners.forEach {
+ it.onAnimationUpdateForProperty(target, ArrayMap(undispatchedUpdates))
+ }
+
+ undispatchedUpdates.clear()
+ }
+ }
+ }
+
+ /** Return true if any animations are running on the object. */
+ fun isRunning(): Boolean {
+ return arePropertiesAnimating(springAnimations.keys.union(flingAnimations.keys))
+ }
+
+ /** Returns whether the given property is animating. */
+ fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
+ return springAnimations[property]?.isRunning ?: false
+ }
+
+ /** Returns whether any of the given properties are animating. */
+ fun arePropertiesAnimating(properties: Set<FloatPropertyCompat<in T>>): Boolean {
+ return properties.any { isPropertyAnimating(it) }
+ }
+
+ /** Return the set of properties that will begin animating upon calling [start]. */
+ internal fun getAnimatedProperties(): Set<FloatPropertyCompat<in T>> {
+ return springConfigs.keys.union(flingConfigs.keys)
+ }
+
+ /** Cancels all in progress animations on all properties. */
+ fun cancel() {
+ for (dynamicAnim in flingAnimations.values.union(springAnimations.values)) {
+ dynamicAnim.cancel()
+ }
+ }
+
+ /**
+ * Container object for spring animation configuration settings. This allows you to store
+ * default stiffness and damping ratio values in a single configuration object, which you can
+ * pass to [spring].
+ */
+ 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
+ ) {
+
+ constructor() :
+ this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+
+ constructor(stiffness: Float, dampingRatio: Float) :
+ this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+
+ /** Apply these configuration settings to the given SpringAnimation. */
+ internal fun applyToAnimation(anim: SpringAnimation) {
+ val springForce = anim.spring ?: SpringForce()
+ anim.spring = springForce.apply {
+ stiffness = this@SpringConfig.stiffness
+ dampingRatio = this@SpringConfig.dampingRatio
+ finalPosition = this@SpringConfig.finalPosition
+ }
+
+ if (startVel != 0f) anim.setStartVelocity(startVel)
+ }
+ }
+
+ /**
+ * Container object for fling animation configuration settings. This allows you to store default
+ * friction values (as well as optional min/max values) in a single configuration object, which
+ * you can pass to [fling] and related methods.
+ */
+ data class FlingConfig internal constructor(
+ internal var friction: Float,
+ internal var min: Float,
+ internal var max: Float,
+ internal var startVel: Float
+ ) {
+
+ constructor() : this(defaultFling.friction)
+
+ constructor(friction: Float) :
+ this(friction, defaultFling.min, defaultFling.max)
+
+ constructor(friction: Float, min: Float, max: Float) :
+ this(friction, min, max, startVel = 0f)
+
+ /** Apply these configuration settings to the given FlingAnimation. */
+ internal fun applyToAnimation(anim: FlingAnimation) {
+ anim.apply {
+ friction = this@FlingConfig.friction
+ setMinValue(min)
+ setMaxValue(max)
+ setStartVelocity(startVel)
+ }
+ }
+ }
+
+ /**
+ * Listener for receiving values from in progress animations. Used with
+ * [PhysicsAnimator.addUpdateListener].
+ *
+ * @param <T> The type of the object being animated.
+ </T> */
+ interface UpdateListener<T> {
+
+ /**
+ * Called on each animation frame with the target object, and a map of FloatPropertyCompat
+ * -> AnimationUpdate, containing the latest value and velocity for that property. When
+ * multiple properties are animating together, the map will typically contain one entry for
+ * each property. However, you should never assume that this is the case - when a property
+ * animation ends earlier than the others, you'll receive an UpdateMap containing only that
+ * property's final update. Subsequently, you'll only receive updates for the properties
+ * that are still animating.
+ *
+ * Always check that the map contains an update for the property you're interested in before
+ * accessing it.
+ *
+ * @param target The animated object itself.
+ * @param values Map of property to AnimationUpdate, which contains that property
+ * animation's latest value and velocity. You should never assume that a particular property
+ * is present in this map.
+ */
+ fun onAnimationUpdateForProperty(
+ target: T,
+ values: UpdateMap<T>
+ )
+ }
+
+ /**
+ * Listener for receiving callbacks when animations end.
+ *
+ * @param <T> The type of the object being animated.
+ </T> */
+ interface EndListener<T> {
+
+ /**
+ * Called with the final animation values as each property animation ends. This can be used
+ * to respond to specific property animations concluding (such as hiding a view when ALPHA
+ * ends, even if the corresponding TRANSLATION animations have not ended).
+ *
+ * If you just want to run an action when all of the property animations have ended, you can
+ * use [PhysicsAnimator.withEndActions].
+ *
+ * @param target The animated object itself.
+ * @param property The property whose animation has just ended.
+ * @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.
+ * This is typically zero, unless this was a fling animation which ended abruptly due to
+ * reaching its configured min/max values.
+ * @param allRelevantPropertyAnimsEnded Whether all properties relevant to this end listener
+ * have ended. Relevant properties are those which were animated alongside the
+ * [addEndListener] call where this animator was passed in. For example:
+ *
+ * animator
+ * .spring(TRANSLATION_X, 100f)
+ * .spring(TRANSLATION_Y, 200f)
+ * .withEndListener(firstEndListener)
+ * .start()
+ *
+ * firstEndListener will be called first for TRANSLATION_X, with allEnded = false,
+ * because TRANSLATION_Y is still running. When TRANSLATION_Y ends, it'll be called with
+ * allEnded = true.
+ *
+ * If a subsequent call to start() is made with other properties, those properties are not
+ * considered relevant and allEnded will still equal true when only TRANSLATION_X and
+ * TRANSLATION_Y end. For example, if immediately after the prior example, while
+ * TRANSLATION_X and TRANSLATION_Y are still animating, we called:
+ *
+ * animator.
+ * .spring(SCALE_X, 2f, stiffness = 10f) // That will take awhile...
+ * .withEndListener(secondEndListener)
+ * .start()
+ *
+ * firstEndListener will still be called with allEnded = true when TRANSLATION_X/Y end, even
+ * though SCALE_X is still animating. Similarly, secondEndListener will be called with
+ * allEnded = true as soon as SCALE_X ends, even if the translation animations are still
+ * running.
+ */
+ fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ )
+ }
+
+ companion object {
+
+ /**
+ * Constructor to use to for new physics animator instances in [getInstance]. This is
+ * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
+ * all code using the physics animator is given testable instances instead.
+ */
+ internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
+
+ @JvmStatic
+ @Suppress("UNCHECKED_CAST")
+ fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
+ if (!animators.containsKey(target)) {
+ animators[target] = instanceConstructor(target)
+ }
+
+ return animators[target] as PhysicsAnimator<T>
+ }
+
+ /**
+ * Set whether all physics animators should log a lot of information about animations.
+ * Useful for debugging!
+ */
+ @JvmStatic
+ fun setVerboseLogging(debug: Boolean) {
+ verboseLogging = debug
+ }
+
+ @JvmStatic
+ fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
+ return when (property) {
+ DynamicAnimation.TRANSLATION_X -> "translationX"
+ DynamicAnimation.TRANSLATION_Y -> "translationY"
+ DynamicAnimation.TRANSLATION_Z -> "translationZ"
+ DynamicAnimation.SCALE_X -> "scaleX"
+ DynamicAnimation.SCALE_Y -> "scaleY"
+ DynamicAnimation.ROTATION -> "rotation"
+ DynamicAnimation.ROTATION_X -> "rotationX"
+ DynamicAnimation.ROTATION_Y -> "rotationY"
+ DynamicAnimation.SCROLL_X -> "scrollX"
+ DynamicAnimation.SCROLL_Y -> "scrollY"
+ DynamicAnimation.ALPHA -> "alpha"
+ else -> "Custom FloatPropertyCompat instance"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
new file mode 100644
index 0000000..e86970c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -0,0 +1,467 @@
+/*
+ * 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.animation
+
+import android.os.Handler
+import android.os.Looper
+import android.util.ArrayMap
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import java.util.ArrayDeque
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean
+typealias UpdateFramesPerProperty<T> =
+ ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>
+
+/**
+ * Utilities for testing code that uses [PhysicsAnimator].
+ *
+ * Start by calling [prepareForTest] at the beginning of each test - this will modify the behavior
+ * of all PhysicsAnimator instances so that they post animations to the main thread (so they don't
+ * crash). It'll also enable the use of the other static helper methods in this class, which you can
+ * use to do things like block the test until animations complete (so you can test end states), or
+ * verify keyframes.
+ */
+object PhysicsAnimatorTestUtils {
+ var timeoutMs: Long = 2000
+ private var startBlocksUntilAnimationsEnd = false
+ private val animationThreadHandler = Handler(Looper.getMainLooper())
+ private val allAnimatedObjects = HashSet<Any>()
+ private val animatorTestHelpers = HashMap<PhysicsAnimator<*>, AnimatorTestHelper<*>>()
+
+ /**
+ * Modifies the behavior of all [PhysicsAnimator] instances so that they post animations to the
+ * main thread, and report all of their
+ */
+ @JvmStatic
+ fun prepareForTest() {
+ val defaultConstructor = PhysicsAnimator.instanceConstructor
+ PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
+ val animator = defaultConstructor(target)
+ allAnimatedObjects.add(target)
+ animatorTestHelpers[animator] = AnimatorTestHelper(animator)
+ return animator
+ }
+
+ timeoutMs = 2000
+ startBlocksUntilAnimationsEnd = false
+ allAnimatedObjects.clear()
+ }
+
+ @JvmStatic
+ fun tearDown() {
+ val latch = CountDownLatch(1)
+ animationThreadHandler.post {
+ animatorTestHelpers.keys.forEach { it.cancel() }
+ latch.countDown()
+ }
+
+ latch.await()
+
+ animatorTestHelpers.clear()
+ animators.clear()
+ allAnimatedObjects.clear()
+ }
+
+ /**
+ * Sets the maximum time (in milliseconds) to block the test thread while waiting for animations
+ * before throwing an exception.
+ */
+ @JvmStatic
+ fun setBlockTimeout(timeoutMs: Long) {
+ this.timeoutMs = timeoutMs
+ }
+
+ /**
+ * Sets whether all animations should block the test thread until they end. This is typically
+ * the desired behavior, since you can invoke code that runs an animation and then assert things
+ * about its end state.
+ */
+ @JvmStatic
+ fun setAllAnimationsBlock(block: Boolean) {
+ startBlocksUntilAnimationsEnd = block
+ }
+
+ /**
+ * Blocks the calling thread until animations of the given property on the target object end.
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ fun <T : Any> blockUntilAnimationsEnd(
+ animator: PhysicsAnimator<T>,
+ vararg properties: FloatPropertyCompat<in T>
+ ) {
+ val animatingProperties = HashSet<FloatPropertyCompat<in T>>()
+ for (property in properties) {
+ if (animator.isPropertyAnimating(property)) {
+ animatingProperties.add(property)
+ }
+ }
+
+ if (animatingProperties.size > 0) {
+ val latch = CountDownLatch(animatingProperties.size)
+ getAnimationTestHelper(animator).addTestEndListener(
+ object : PhysicsAnimator.EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ if (animatingProperties.contains(property)) {
+ latch.countDown()
+ }
+ }
+ })
+
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ /**
+ * Blocks the calling thread until all animations of the given property (on all target objects)
+ * have ended. Useful when you don't have access to the objects being animated, but still need
+ * to wait for them to end so that other testable side effects occur (such as update/end
+ * listeners).
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ @Suppress("UNCHECKED_CAST")
+ fun <T : Any> blockUntilAnimationsEnd(
+ properties: FloatPropertyCompat<in T>
+ ) {
+ for (target in allAnimatedObjects) {
+ try {
+ blockUntilAnimationsEnd(
+ PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
+ } catch (e: ClassCastException) {
+ // Keep checking the other objects for ones whose types match the provided
+ // properties.
+ }
+ }
+ }
+
+ /**
+ * Blocks the calling thread until the first animation frame in which predicate returns true. If
+ * the given object isn't animating, returns without blocking.
+ */
+ @JvmStatic
+ @Throws(InterruptedException::class)
+ fun <T : Any> blockUntilFirstAnimationFrameWhereTrue(
+ animator: PhysicsAnimator<T>,
+ predicate: (T) -> Boolean
+ ) {
+ if (animator.isRunning()) {
+ val latch = CountDownLatch(1)
+ getAnimationTestHelper(animator).addTestUpdateListener(object : PhysicsAnimator
+ .UpdateListener<T> {
+ override fun onAnimationUpdateForProperty(
+ target: T,
+ values: UpdateMap<T>
+ ) {
+ if (predicate(target)) {
+ latch.countDown()
+ }
+ }
+ })
+
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ /**
+ * Verifies that the animator reported animation frame values to update listeners that satisfy
+ * the given matchers, in order. Not all frames need to satisfy a matcher - we'll run through
+ * all animation frames, and check them against the current predicate. If it returns false, we
+ * continue through the frames until it returns true, and then move on to the next matcher.
+ * Verification fails if we run out of frames while unsatisfied matchers remain.
+ *
+ * If verification is successful, all frames to this point are considered 'verified' and will be
+ * cleared. Subsequent calls to this method will start verification at the next animation frame.
+ *
+ * Example: Verify that an animation surpassed x = 50f before going negative.
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 50f },
+ * { u -> u.value < 0f })
+ *
+ * Example: verify that an animation went backwards at some point while still being on-screen.
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.velocity < 0f && u.value >= 0f })
+ *
+ * This method is intended to help you test longer, more complicated animations where it's
+ * critical that certain values were reached. Using this method to test short animations can
+ * fail due to the animation having fewer frames than provided matchers. For example, an
+ * animation from x = 1f to x = 5f might only have two frames, at x = 3f and x = 5f. The
+ * following would then fail despite it seeming logically sound:
+ *
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 1f },
+ * { u -> u.value > 2f },
+ * { u -> u.value > 3f })
+ *
+ * Tests might also fail if your matchers are too granular, such as this example test after an
+ * animation from x = 0f to x = 100f. It's unlikely there was a frame specifically between 2f
+ * and 3f.
+ *
+ * verifyAnimationUpdateFrames(
+ * animator, TRANSLATION_X,
+ * { u -> u.value > 2f && u.value < 3f },
+ * { u -> u.value >= 50f })
+ *
+ * Failures will print a helpful log of all animation frames so you can see what caused the test
+ * to fail.
+ */
+ fun <T : Any> verifyAnimationUpdateFrames(
+ animator: PhysicsAnimator<T>,
+ property: FloatPropertyCompat<in T>,
+ firstUpdateMatcher: UpdateMatcher,
+ vararg additionalUpdateMatchers: UpdateMatcher
+ ) {
+ val updateFrames: UpdateFramesPerProperty<T> = getAnimationUpdateFrames(animator)
+
+ if (!updateFrames.containsKey(property)) {
+ error("No frames for given target object and property.")
+ }
+
+ // Copy the frames to avoid a ConcurrentModificationException if the animation update
+ // listeners attempt to add a new frame while we're verifying these.
+ val framesForProperty = ArrayList(updateFrames[property]!!)
+ val matchers = ArrayDeque<UpdateMatcher>(
+ additionalUpdateMatchers.toList())
+ val frameTraceMessage = StringBuilder()
+
+ var curMatcher = firstUpdateMatcher
+
+ // Loop through the updates from the testable animator.
+ for (update in framesForProperty) {
+
+ // Check whether this frame satisfies the current matcher.
+ if (curMatcher(update)) {
+
+ // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
+ // frames and return without failing.
+ if (matchers.size == 0) {
+ getAnimationUpdateFrames(animator).remove(property)
+ return
+ }
+
+ frameTraceMessage.append("$update\t(satisfied matcher)\n")
+ curMatcher = matchers.pop() // Get the next matcher and keep going.
+ } else {
+ frameTraceMessage.append("${update}\n")
+ }
+ }
+
+ val readablePropertyName = PhysicsAnimator.getReadablePropertyName(property)
+ getAnimationUpdateFrames(animator).remove(property)
+
+ throw RuntimeException(
+ "Failed to verify animation frames for property $readablePropertyName: " +
+ "Provided ${additionalUpdateMatchers.size + 1} matchers, " +
+ "however ${matchers.size + 1} remained unsatisfied.\n\n" +
+ "All frames:\n$frameTraceMessage")
+ }
+
+ /**
+ * Overload of [verifyAnimationUpdateFrames] that builds matchers for you, from given float
+ * values. For example, to verify that an animations passed from 0f to 50f to 100f back to 50f:
+ *
+ * verifyAnimationUpdateFrames(animator, TRANSLATION_X, 0f, 50f, 100f, 50f)
+ *
+ * This verifies that update frames were received with values of >= 0f, >= 50f, >= 100f, and
+ * <= 50f.
+ *
+ * The same caveats apply: short animations might not have enough frames to satisfy all of the
+ * matchers, and overly specific calls (such as 0f, 1f, 2f, 3f, etc. for an animation from
+ * x = 0f to x = 100f) might fail as the animation only had frames at 0f, 25f, 50f, 75f, and
+ * 100f. As with [verifyAnimationUpdateFrames], failures will print a helpful log of all frames
+ * so you can see what caused the test to fail.
+ */
+ fun <T : Any> verifyAnimationUpdateFrames(
+ animator: PhysicsAnimator<T>,
+ property: FloatPropertyCompat<in T>,
+ startValue: Float,
+ firstTargetValue: Float,
+ vararg additionalTargetValues: Float
+ ) {
+ val matchers = ArrayList<UpdateMatcher>()
+
+ val values = ArrayList<Float>().also {
+ it.add(firstTargetValue)
+ it.addAll(additionalTargetValues.toList())
+ }
+
+ var prevVal = startValue
+ for (value in values) {
+ if (value > prevVal) {
+ matchers.add { update -> update.value >= value }
+ } else {
+ matchers.add { update -> update.value <= value }
+ }
+
+ prevVal = value
+ }
+
+ verifyAnimationUpdateFrames(
+ animator, property, matchers[0], *matchers.drop(0).toTypedArray())
+ }
+
+ /**
+ * Returns all of the values that have ever been reported to update listeners, per property.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
+ UpdateFramesPerProperty<T> {
+ return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
+ }
+
+ /**
+ * Clears animation frame updates from the given animator so they aren't used the next time its
+ * passed to [verifyAnimationUpdateFrames].
+ */
+ fun <T : Any> clearAnimationUpdateFrames(animator: PhysicsAnimator<T>) {
+ animatorTestHelpers[animator]?.clearUpdates()
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <T> getAnimationTestHelper(animator: PhysicsAnimator<T>): AnimatorTestHelper<T> {
+ return animatorTestHelpers[animator] as AnimatorTestHelper<T>
+ }
+
+ /**
+ * Helper class for testing an animator. This replaces the animator's start action with
+ * [startForTest] and adds test listeners to enable other test utility behaviors. We build one
+ * these for each Animator and keep them around so we can access the updates.
+ */
+ class AnimatorTestHelper<T> (private val animator: PhysicsAnimator<T>) {
+
+ /** All updates received for each property animation. */
+ private val allUpdates =
+ ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>()
+
+ private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
+ private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
+
+ init {
+ animator.startAction = ::startForTest
+ }
+
+ internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
+ testEndListeners.add(listener)
+ }
+
+ internal fun addTestUpdateListener(listener: PhysicsAnimator.UpdateListener<T>) {
+ testUpdateListeners.add(listener)
+ }
+
+ internal fun getUpdates(): UpdateFramesPerProperty<T> {
+ return allUpdates
+ }
+
+ internal fun clearUpdates() {
+ allUpdates.clear()
+ }
+
+ private fun startForTest() {
+ // The testable animator needs to block the main thread until super.start() has been
+ // called, since callers expect .start() to be synchronous but we're posting it to a
+ // handler here. We may also continue blocking until all animations end, if
+ // startBlocksUntilAnimationsEnd = true.
+ 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> {
+ override fun onAnimationUpdateForProperty(
+ target: T,
+ values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+ ) {
+ for (listener in testUpdateListeners) {
+ listener.onAnimationUpdateForProperty(target, values)
+ }
+ }
+ })
+
+ // Add an end listener that dispatches to any test end listeners added by tests, and
+ // unblocks the main thread if required.
+ animator.addEndListener(object : PhysicsAnimator.EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ for (listener in testEndListeners) {
+ listener.onAnimationEnd(
+ target, property, canceled, finalValue, finalVelocity,
+ allRelevantPropertyAnimsEnded)
+ }
+
+ if (allRelevantPropertyAnimsEnded) {
+ testEndListeners.clear()
+ testUpdateListeners.clear()
+
+ if (startBlocksUntilAnimationsEnd) {
+ unblockLatch.countDown()
+ }
+ }
+ }
+ })
+
+ 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()
+ }
+
+ unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
+ }
+ }
+}
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 24f49ff..7cdba86 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -18,13 +18,18 @@
import android.content.Context;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.BgHandler;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@@ -34,12 +39,52 @@
*/
@Module
public abstract class ConcurrencyModule {
+ /** Background Looper */
+ @Provides
+ @Singleton
+ @Background
+ public static Looper provideBgLooper() {
+ HandlerThread thread = new HandlerThread("SysUiBg",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ return thread.getLooper();
+ }
+
+ /** Main Looper */
+ @Provides
+ @Main
+ public static Looper provideMainLooper() {
+ return Looper.getMainLooper();
+ }
+
+ /**
+ * Background Handler.
+ *
+ * Prefer the Background Executor when possible.
+ */
+ @Provides
+ @Background
+ public static Handler provideBgHandler(@Background Looper bgLooper) {
+ return new Handler(bgLooper);
+ }
+
+ /**
+ * Main Handler.
+ *
+ * Prefer the Main Executor when possible.
+ */
+ @Provides
+ @Main
+ public static Handler provideMainHandler(@Main Looper mainLooper) {
+ return new Handler(mainLooper);
+ }
+
/**
* Provide a Background-Thread Executor by default.
*/
@Provides
- public static Executor provideExecutor(@BgHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static Executor provideExecutor(@Background Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
/**
@@ -47,8 +92,8 @@
*/
@Provides
@Background
- public static Executor provideBackgroundExecutor(@BgHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static Executor provideBackgroundExecutor(@Background Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
/**
@@ -64,8 +109,8 @@
* Provide a Background-Thread Executor by default.
*/
@Provides
- public static DelayableExecutor provideDelayableExecutor(@BgHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
/**
@@ -73,8 +118,8 @@
*/
@Provides
@Background
- public static DelayableExecutor provideBackgroundDelayableExecutor(@BgHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
}
/**
@@ -82,7 +127,19 @@
*/
@Provides
@Main
- public static DelayableExecutor provideMainDelayableExecutor(@MainHandler Handler handler) {
- return new ExecutorImpl(handler);
+ public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
+ return new ExecutorImpl(new Handler(looper));
+ }
+
+ /**
+ * Provide an Executor specifically for running UI operations on a separate thread.
+ *
+ * Keep submitted runnables short and to the point, just as with any other UI code.
+ */
+ @Provides
+ @Singleton
+ @UiBackground
+ public static Executor provideUiBackgroundExecutor() {
+ return Executors.newSingleThreadExecutor();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index bff405c..2c7c52e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -47,7 +47,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
@@ -109,7 +109,7 @@
@Inject
public GarbageMonitor(
Context context,
- @BgLooper Looper bgLooper,
+ @Background Looper bgLooper,
LeakDetector leakDetector,
LeakReporter leakReporter) {
mContext = context.getApplicationContext();
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index a96977a..b5bede4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -26,7 +26,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.dagger.qualifiers.Main;
import java.util.ArrayList;
import java.util.List;
@@ -65,7 +65,7 @@
};
@Inject
- public ProximitySensor(@MainResources Resources resources,
+ public ProximitySensor(@Main Resources resources,
AsyncSensorManager sensorManager) {
mSensorManager = sensorManager;
Sensor sensor = findBrightnessSensor(resources);
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java b/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
index 4316df1..6fef59f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
@@ -37,10 +37,4 @@
/** @see android.os.SystemClock#currentThreadTimeMillis() */
long currentThreadTimeMillis();
-
- /** @see android.os.SystemClock#currentThreadTimeMicro() */
- long currentThreadTimeMicro();
-
- /** @see android.os.SystemClock#currentTimeMicro() */
- long currentTimeMicro();
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java b/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
index 532ea05..f0c7014 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
@@ -42,14 +42,4 @@
public long currentThreadTimeMillis() {
return android.os.SystemClock.currentThreadTimeMillis();
}
-
- @Override
- public long currentThreadTimeMicro() {
- return android.os.SystemClock.currentThreadTimeMicro();
- }
-
- @Override
- public long currentTimeMicro() {
- return android.os.SystemClock.currentTimeMicro();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index 5ed8b8f..e121001 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -21,7 +21,11 @@
import android.provider.Settings.Global;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.plugins.VolumeDialogController.State;
@@ -37,7 +41,7 @@
public static final int EVENT_DISMISS_DIALOG = 1; // (reason|int)
public static final int EVENT_ACTIVE_STREAM_CHANGED = 2; // (stream|int)
public static final int EVENT_EXPAND = 3; // (expand|bool)
- public static final int EVENT_KEY = 4;
+ public static final int EVENT_KEY = 4; // (stream|int) (lastAudibleStreamVolume)
public static final int EVENT_COLLECTION_STARTED = 5;
public static final int EVENT_COLLECTION_STOPPED = 6;
public static final int EVENT_ICON_CLICK = 7; // (stream|int) (icon_state|int)
@@ -49,7 +53,7 @@
public static final int EVENT_ZEN_MODE_CHANGED = 13; // (mode|int)
public static final int EVENT_SUPPRESSOR_CHANGED = 14; // (component|string) (name|string)
public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool)
- public static final int EVENT_TOUCH_LEVEL_DONE = 16; // (stream|int) (level|bool)
+ public static final int EVENT_TOUCH_LEVEL_DONE = 16; // (stream|int) (level|int)
public static final int EVENT_ZEN_CONFIG_CHANGED = 17; // (allow/disallow|string)
public static final int EVENT_RINGER_TOGGLE = 18; // (ringer_mode)
public static final int EVENT_SHOW_USB_OVERHEAT_ALARM = 19; // (reason|int) (keyguard|bool)
@@ -122,105 +126,365 @@
public static final int ICON_STATE_MUTE = 2;
public static final int ICON_STATE_VIBRATE = 3;
+ @VisibleForTesting
+ public enum VolumeDialogOpenEvent implements UiEventLogger.UiEventEnum {
+ //TODO zap the lock/unlock distinction
+ INVALID(0),
+ @UiEvent(doc = "The volume dialog was shown because the volume changed")
+ VOLUME_DIALOG_SHOW_VOLUME_CHANGED(128),
+ @UiEvent(doc = "The volume dialog was shown because the volume changed remotely")
+ VOLUME_DIALOG_SHOW_REMOTE_VOLUME_CHANGED(129),
+ @UiEvent(doc = "The volume dialog was shown because the usb high temperature alarm changed")
+ VOLUME_DIALOG_SHOW_USB_TEMP_ALARM_CHANGED(130);
+
+ private final int mId;
+ VolumeDialogOpenEvent(int id) {
+ mId = id;
+ }
+ public int getId() {
+ return mId;
+ }
+ static VolumeDialogOpenEvent fromReasons(int reason) {
+ switch (reason) {
+ case SHOW_REASON_VOLUME_CHANGED:
+ return VOLUME_DIALOG_SHOW_VOLUME_CHANGED;
+ case SHOW_REASON_REMOTE_VOLUME_CHANGED:
+ return VOLUME_DIALOG_SHOW_REMOTE_VOLUME_CHANGED;
+ case SHOW_REASON_USB_OVERHEAD_ALARM_CHANGED:
+ return VOLUME_DIALOG_SHOW_USB_TEMP_ALARM_CHANGED;
+ }
+ return INVALID;
+ }
+ }
+
+ @VisibleForTesting
+ public enum VolumeDialogCloseEvent implements UiEventLogger.UiEventEnum {
+ INVALID(0),
+ @UiEvent(doc = "The volume dialog was dismissed because of a touch outside the dialog")
+ VOLUME_DIALOG_DISMISS_TOUCH_OUTSIDE(134),
+ @UiEvent(doc = "The system asked the volume dialog to close, e.g. for a navigation bar "
+ + "touch, or ActivityManager ACTION_CLOSE_SYSTEM_DIALOGS broadcast.")
+ VOLUME_DIALOG_DISMISS_SYSTEM(135),
+ @UiEvent(doc = "The volume dialog was dismissed because it timed out")
+ VOLUME_DIALOG_DISMISS_TIMEOUT(136),
+ @UiEvent(doc = "The volume dialog was dismissed because the screen turned off")
+ VOLUME_DIALOG_DISMISS_SCREEN_OFF(137),
+ @UiEvent(doc = "The volume dialog was dismissed because the settings icon was clicked")
+ VOLUME_DIALOG_DISMISS_SETTINGS(138),
+ // reserving 139 for DISMISS_REASON_DONE_CLICKED which is currently unused
+ @UiEvent(doc = "The volume dialog was dismissed because the stream no longer exists")
+ VOLUME_DIALOG_DISMISS_STREAM_GONE(140),
+ // reserving 141 for DISMISS_REASON_OUTPUT_CHOOSER which is currently unused
+ @UiEvent(doc = "The volume dialog was dismissed because the usb high temperature alarm "
+ + "changed")
+ VOLUME_DIALOG_DISMISS_USB_TEMP_ALARM_CHANGED(142);
+
+ private final int mId;
+ VolumeDialogCloseEvent(int id) {
+ mId = id;
+ }
+ public int getId() {
+ return mId;
+ }
+
+ static VolumeDialogCloseEvent fromReason(int reason) {
+ switch (reason) {
+ case DISMISS_REASON_TOUCH_OUTSIDE:
+ return VOLUME_DIALOG_DISMISS_TOUCH_OUTSIDE;
+ case DISMISS_REASON_VOLUME_CONTROLLER:
+ return VOLUME_DIALOG_DISMISS_SYSTEM;
+ case DISMISS_REASON_TIMEOUT:
+ return VOLUME_DIALOG_DISMISS_TIMEOUT;
+ case DISMISS_REASON_SCREEN_OFF:
+ return VOLUME_DIALOG_DISMISS_SCREEN_OFF;
+ case DISMISS_REASON_SETTINGS_CLICKED:
+ return VOLUME_DIALOG_DISMISS_SETTINGS;
+ case DISMISS_STREAM_GONE:
+ return VOLUME_DIALOG_DISMISS_STREAM_GONE;
+ case DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED:
+ return VOLUME_DIALOG_DISMISS_USB_TEMP_ALARM_CHANGED;
+ }
+ return INVALID;
+ }
+ }
+
+ @VisibleForTesting
+ public enum VolumeDialogEvent implements UiEventLogger.UiEventEnum {
+ INVALID(0),
+ @UiEvent(doc = "The volume dialog settings icon was clicked")
+ VOLUME_DIALOG_SETTINGS_CLICK(143),
+ @UiEvent(doc = "The volume dialog details were expanded")
+ VOLUME_DIALOG_EXPAND_DETAILS(144),
+ @UiEvent(doc = "The volume dialog details were collapsed")
+ VOLUME_DIALOG_COLLAPSE_DETAILS(145),
+ @UiEvent(doc = "The active audio stream changed")
+ VOLUME_DIALOG_ACTIVE_STREAM_CHANGED(146),
+ @UiEvent(doc = "The audio stream was muted via icon")
+ VOLUME_DIALOG_MUTE_STREAM(147),
+ @UiEvent(doc = "The audio stream was unmuted via icon")
+ VOLUME_DIALOG_UNMUTE_STREAM(148),
+ @UiEvent(doc = "The audio stream was set to vibrate via icon")
+ VOLUME_DIALOG_TO_VIBRATE_STREAM(149),
+ @UiEvent(doc = "The audio stream was set to non-silent via slider")
+ VOLUME_DIALOG_SLIDER(150),
+ @UiEvent(doc = "The audio stream was set to silent via slider")
+ VOLUME_DIALOG_SLIDER_TO_ZERO(151),
+ @UiEvent(doc = "The audio volume was adjusted to silent via key")
+ VOLUME_KEY_TO_ZERO(152),
+ @UiEvent(doc = "The audio volume was adjusted to non-silent via key")
+ VOLUME_KEY(153),
+ @UiEvent(doc = "The ringer mode was toggled to silent")
+ RINGER_MODE_SILENT(154),
+ @UiEvent(doc = "The ringer mode was toggled to vibrate")
+ RINGER_MODE_VIBRATE(155),
+ @UiEvent(doc = "The ringer mode was toggled to normal")
+ RINGER_MODE_NORMAL(156),
+ @UiEvent(doc = "USB Overheat alarm was raised")
+ USB_OVERHEAT_ALARM(160),
+ @UiEvent(doc = "USB Overheat alarm was dismissed")
+ USB_OVERHEAT_ALARM_DISMISSED(161);
+
+ private final int mId;
+
+ VolumeDialogEvent(int id) {
+ mId = id;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ static VolumeDialogEvent fromIconState(int iconState) {
+ switch (iconState) {
+ case ICON_STATE_UNMUTE:
+ return VOLUME_DIALOG_UNMUTE_STREAM;
+ case ICON_STATE_MUTE:
+ return VOLUME_DIALOG_MUTE_STREAM;
+ case ICON_STATE_VIBRATE:
+ return VOLUME_DIALOG_TO_VIBRATE_STREAM;
+ default:
+ return INVALID;
+ }
+ }
+
+ static VolumeDialogEvent fromSliderLevel(int level) {
+ return level == 0 ? VOLUME_DIALOG_SLIDER_TO_ZERO : VOLUME_DIALOG_SLIDER;
+ }
+
+ static VolumeDialogEvent fromKeyLevel(int level) {
+ return level == 0 ? VOLUME_KEY_TO_ZERO : VOLUME_KEY;
+ }
+
+ static VolumeDialogEvent fromRingerMode(int ringerMode) {
+ switch (ringerMode) {
+ case AudioManager.RINGER_MODE_SILENT:
+ return RINGER_MODE_SILENT;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ return RINGER_MODE_VIBRATE;
+ case AudioManager.RINGER_MODE_NORMAL:
+ return RINGER_MODE_NORMAL;
+ default:
+ return INVALID;
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public enum ZenModeEvent implements UiEventLogger.UiEventEnum {
+ INVALID(0),
+ @UiEvent(doc = "Zen (do not disturb) mode was toggled to off")
+ ZEN_MODE_OFF(156),
+ @UiEvent(doc = "Zen (do not disturb) mode was toggled to important interruptions only")
+ ZEN_MODE_IMPORTANT_ONLY(157),
+ @UiEvent(doc = "Zen (do not disturb) mode was toggled to alarms only")
+ ZEN_MODE_ALARMS_ONLY(158),
+ @UiEvent(doc = "Zen (do not disturb) mode was toggled to block all interruptions")
+ ZEN_MODE_NO_INTERRUPTIONS(159);
+
+ private final int mId;
+ ZenModeEvent(int id) {
+ mId = id;
+ }
+ public int getId() {
+ return mId;
+ }
+
+ static ZenModeEvent fromZenMode(int zenMode) {
+ switch (zenMode) {
+ case Global.ZEN_MODE_OFF: return ZEN_MODE_OFF;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return ZEN_MODE_IMPORTANT_ONLY;
+ case Global.ZEN_MODE_ALARMS: return ZEN_MODE_ALARMS_ONLY;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS: return ZEN_MODE_NO_INTERRUPTIONS;
+ default: return INVALID;
+ }
+ }
+ }
+
public static Callback sCallback;
+ @VisibleForTesting
+ static MetricsLogger sLegacyLogger = new MetricsLogger();
+ @VisibleForTesting
+ static UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
/**
- * Logs an event to the system log and the event log.
+ * Logs an event to the system log, to sCallback if present, and to the logEvent destinations.
* @param tag One of the EVENT_* codes above.
* @param list Any additional event-specific arguments, documented above.
*/
public static void writeEvent(int tag, Object... list) {
- MetricsLogger logger = new MetricsLogger();
final long time = System.currentTimeMillis();
- final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]);
- if (list != null && list.length > 0) {
- sb.append(" ");
- switch (tag) {
- case EVENT_SHOW_DIALOG:
- logger.visible(MetricsEvent.VOLUME_DIALOG);
- logger.histogram("volume_from_keyguard",
- (Boolean) list[1] ? 1 : 0);
- sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]);
- break;
- case EVENT_EXPAND:
- logger.visibility(MetricsEvent.VOLUME_DIALOG_DETAILS,
- (Boolean) list[0]);
- sb.append(list[0]);
- break;
- case EVENT_DISMISS_DIALOG:
- logger.hidden(MetricsEvent.VOLUME_DIALOG);
- sb.append(DISMISS_REASONS[(Integer) list[0]]);
- break;
- case EVENT_ACTIVE_STREAM_CHANGED:
- logger.action(MetricsEvent.ACTION_VOLUME_STREAM,
- (Integer) list[0]);
- sb.append(AudioSystem.streamToString((Integer) list[0]));
- break;
- case EVENT_ICON_CLICK:
- logger.action(MetricsEvent.ACTION_VOLUME_ICON,
- (Integer) list[0]);
- sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
- .append(iconStateToString((Integer) list[1]));
- break;
- case EVENT_TOUCH_LEVEL_DONE:
- logger.action(MetricsEvent.ACTION_VOLUME_SLIDER,
- (Integer) list[1]);
- // fall through
- case EVENT_TOUCH_LEVEL_CHANGED:
- case EVENT_LEVEL_CHANGED:
- case EVENT_MUTE_CHANGED:
- sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
- .append(list[1]);
- break;
- case EVENT_KEY:
- logger.action(MetricsEvent.ACTION_VOLUME_KEY,
- (Integer) list[0]);
- sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
- .append(list[1]);
- break;
- case EVENT_RINGER_TOGGLE:
- logger.action(MetricsEvent.ACTION_VOLUME_RINGER_TOGGLE, (Integer) list[0]);
- break;
- case EVENT_SETTINGS_CLICK:
- logger.action(MetricsEvent.ACTION_VOLUME_SETTINGS);
- break;
- case EVENT_EXTERNAL_RINGER_MODE_CHANGED:
- logger.action(MetricsEvent.ACTION_RINGER_MODE,
- (Integer) list[0]);
- // fall through
- case EVENT_INTERNAL_RINGER_MODE_CHANGED:
- sb.append(ringerModeToString((Integer) list[0]));
- break;
- case EVENT_ZEN_MODE_CHANGED:
- sb.append(zenModeToString((Integer) list[0]));
- break;
- case EVENT_SUPPRESSOR_CHANGED:
- sb.append(list[0]).append(' ').append(list[1]);
- break;
- case EVENT_SHOW_USB_OVERHEAT_ALARM:
- logger.visible(MetricsEvent.POWER_OVERHEAT_ALARM);
- logger.histogram("show_usb_overheat_alarm",
- (Boolean) list[1] ? 1 : 0);
- sb.append(SHOW_REASONS[(Integer) list[0]]).append(" keyguard=").append(list[1]);
- break;
- case EVENT_DISMISS_USB_OVERHEAT_ALARM:
- logger.hidden(MetricsEvent.POWER_OVERHEAT_ALARM);
- logger.histogram("dismiss_usb_overheat_alarm",
- (Boolean) list[1] ? 1 : 0);
- sb.append(DISMISS_REASONS[(Integer) list[0]])
- .append(" keyguard=").append(list[1]);
- break;
- default:
- sb.append(Arrays.asList(list));
- break;
- }
- }
- Log.i(TAG, sb.toString());
+ Log.i(TAG, logEvent(tag, list));
if (sCallback != null) {
sCallback.writeEvent(time, tag, list);
}
}
+ /**
+ * Logs an event to the event log and UiEvent (Westworld) logging. Compare writeEvent, which
+ * adds more log destinations.
+ * @param tag One of the EVENT_* codes above.
+ * @param list Any additional event-specific arguments, documented above.
+ * @return String a readable description of the event. Begins "writeEvent <tag_description>"
+ * if the tag is valid.
+ */
+ public static String logEvent(int tag, Object... list) {
+ if (tag >= EVENT_TAGS.length) {
+ return "";
+ }
+ final StringBuilder sb = new StringBuilder("writeEvent ").append(EVENT_TAGS[tag]);
+ // Handle events without extra data
+ if (list == null || list.length == 0) {
+ if (tag == EVENT_SETTINGS_CLICK) {
+ sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_SETTINGS);
+ sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_SETTINGS_CLICK);
+ }
+ return sb.toString();
+ }
+ // Handle events with extra data. We've established list[0] exists.
+ sb.append(" ");
+ switch (tag) {
+ case EVENT_SHOW_DIALOG:
+ sLegacyLogger.visible(MetricsEvent.VOLUME_DIALOG);
+ if (list.length > 1) {
+ final Integer reason = (Integer) list[0];
+ final Boolean keyguard = (Boolean) list[1];
+ sLegacyLogger.histogram("volume_from_keyguard", keyguard ? 1 : 0);
+ sUiEventLogger.log(VolumeDialogOpenEvent.fromReasons(reason));
+ sb.append(SHOW_REASONS[reason]).append(" keyguard=").append(keyguard);
+ }
+ break;
+ case EVENT_EXPAND: {
+ final Boolean expand = (Boolean) list[0];
+ sLegacyLogger.visibility(MetricsEvent.VOLUME_DIALOG_DETAILS, expand);
+ sUiEventLogger.log(expand ? VolumeDialogEvent.VOLUME_DIALOG_EXPAND_DETAILS
+ : VolumeDialogEvent.VOLUME_DIALOG_COLLAPSE_DETAILS);
+ sb.append(expand);
+ break;
+ }
+ case EVENT_DISMISS_DIALOG: {
+ sLegacyLogger.hidden(MetricsEvent.VOLUME_DIALOG);
+ final Integer reason = (Integer) list[0];
+ sUiEventLogger.log(VolumeDialogCloseEvent.fromReason(reason));
+ sb.append(DISMISS_REASONS[reason]);
+ break;
+ }
+ case EVENT_ACTIVE_STREAM_CHANGED: {
+ final Integer stream = (Integer) list[0];
+ sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_STREAM, stream);
+ sUiEventLogger.log(VolumeDialogEvent.VOLUME_DIALOG_ACTIVE_STREAM_CHANGED);
+ sb.append(AudioSystem.streamToString(stream));
+ break;
+ }
+ case EVENT_ICON_CLICK:
+ if (list.length > 1) {
+ final Integer stream = (Integer) list[0];
+ sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_ICON, stream);
+ final Integer iconState = (Integer) list[1];
+ sUiEventLogger.log(VolumeDialogEvent.fromIconState(iconState));
+ sb.append(AudioSystem.streamToString(stream)).append(' ')
+ .append(iconStateToString(iconState));
+ }
+ break;
+ case EVENT_TOUCH_LEVEL_DONE: // (stream|int) (level|int)
+ if (list.length > 1) {
+ final Integer level = (Integer) list[1];
+ sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_SLIDER, level);
+ sUiEventLogger.log(VolumeDialogEvent.fromSliderLevel(level));
+ }
+ // fall through
+ case EVENT_TOUCH_LEVEL_CHANGED:
+ case EVENT_LEVEL_CHANGED:
+ case EVENT_MUTE_CHANGED: // (stream|int) (level|int)
+ if (list.length > 1) {
+ sb.append(AudioSystem.streamToString((Integer) list[0])).append(' ')
+ .append(list[1]);
+ }
+ break;
+ case EVENT_KEY: // (stream|int) (lastAudibleStreamVolume)
+ if (list.length > 1) {
+ final Integer stream = (Integer) list[0];
+ sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_KEY, stream);
+ final Integer level = (Integer) list[1];
+ sUiEventLogger.log(VolumeDialogEvent.fromKeyLevel(level));
+ sb.append(AudioSystem.streamToString(stream)).append(' ').append(level);
+ }
+ break;
+ case EVENT_RINGER_TOGGLE: {
+ final Integer ringerMode = (Integer) list[0];
+ sLegacyLogger.action(MetricsEvent.ACTION_VOLUME_RINGER_TOGGLE, ringerMode);
+ sUiEventLogger.log(VolumeDialogEvent.fromRingerMode(ringerMode));
+ sb.append(ringerModeToString(ringerMode));
+ break;
+ }
+ case EVENT_EXTERNAL_RINGER_MODE_CHANGED: {
+ final Integer ringerMode = (Integer) list[0];
+ sLegacyLogger.action(MetricsEvent.ACTION_RINGER_MODE, ringerMode);
+ }
+ // fall through
+ case EVENT_INTERNAL_RINGER_MODE_CHANGED: {
+ final Integer ringerMode = (Integer) list[0];
+ sb.append(ringerModeToString(ringerMode));
+ break;
+ }
+ case EVENT_ZEN_MODE_CHANGED: {
+ final Integer zenMode = (Integer) list[0];
+ sb.append(zenModeToString(zenMode));
+ sUiEventLogger.log(ZenModeEvent.fromZenMode(zenMode));
+ break;
+ }
+ case EVENT_SUPPRESSOR_CHANGED: // (component|string) (name|string)
+ if (list.length > 1) {
+ sb.append(list[0]).append(' ').append(list[1]);
+ }
+ break;
+ case EVENT_SHOW_USB_OVERHEAT_ALARM:
+ sLegacyLogger.visible(MetricsEvent.POWER_OVERHEAT_ALARM);
+ sUiEventLogger.log(VolumeDialogEvent.USB_OVERHEAT_ALARM);
+ if (list.length > 1) {
+ final Boolean keyguard = (Boolean) list[1];
+ sLegacyLogger.histogram("show_usb_overheat_alarm", keyguard ? 1 : 0);
+ final Integer reason = (Integer) list[0];
+ sb.append(SHOW_REASONS[reason]).append(" keyguard=").append(keyguard);
+ }
+ break;
+ case EVENT_DISMISS_USB_OVERHEAT_ALARM:
+ sLegacyLogger.hidden(MetricsEvent.POWER_OVERHEAT_ALARM);
+ sUiEventLogger.log(VolumeDialogEvent.USB_OVERHEAT_ALARM_DISMISSED);
+ if (list.length > 1) {
+ final Boolean keyguard = (Boolean) list[1];
+ sLegacyLogger.histogram("dismiss_usb_overheat_alarm", keyguard ? 1 : 0);
+ final Integer reason = (Integer) list[0];
+ sb.append(DISMISS_REASONS[reason])
+ .append(" keyguard=").append(keyguard);
+ }
+ break;
+ default:
+ sb.append(Arrays.asList(list));
+ break;
+ }
+ return sb.toString();
+ }
+
public static void writeState(long time, State state) {
if (sCallback != null) {
sCallback.writeState(time, state);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index f5c1587..af218c49 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -160,6 +160,7 @@
private boolean mHovering = false;
private boolean mShowActiveStreamOnly;
private boolean mConfigChanged = false;
+ private boolean mIsAnimatingDismiss = false;
private boolean mHasSeenODICaptionsTooltip;
private ViewStub mODICaptionsTooltipViewStub;
private View mODICaptionsTooltipView = null;
@@ -693,6 +694,7 @@
initSettingsH();
mShowing = true;
+ mIsAnimatingDismiss = false;
mDialog.show();
Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
mController.notifyVisible(true);
@@ -737,6 +739,10 @@
}
mHandler.removeMessages(H.DISMISS);
mHandler.removeMessages(H.SHOW);
+ if (mIsAnimatingDismiss) {
+ return;
+ }
+ mIsAnimatingDismiss = true;
mDialogView.animate().cancel();
if (mShowing) {
mShowing = false;
@@ -752,6 +758,7 @@
.withEndAction(() -> mHandler.postDelayed(() -> {
mDialog.dismiss();
tryToRemoveCaptionsTooltip();
+ mIsAnimatingDismiss = false;
}, 50));
if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f);
animator.start();
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
new file mode 100644
index 0000000..264a683
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
@@ -0,0 +1,474 @@
+/*
+ * 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 static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
+import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.Size;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.Surface;
+
+import com.android.internal.R;
+
+import java.util.List;
+
+/**
+ * Contains information about the layout-properties of a display. This refers to internal layout
+ * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to
+ * DisplayPolicy.
+ */
+public class DisplayLayout {
+ // Navigation bar position values
+ private static final int NAV_BAR_LEFT = 1 << 0;
+ private static final int NAV_BAR_RIGHT = 1 << 1;
+ private static final int NAV_BAR_BOTTOM = 1 << 2;
+
+ private int mUiMode;
+ private int mWidth;
+ private int mHeight;
+ private DisplayCutout mCutout;
+ private int mRotation;
+ private int mDensityDpi;
+ private final Rect mNonDecorInsets = new Rect();
+ private final Rect mStableInsets = new Rect();
+ private boolean mHasNavigationBar = false;
+ private boolean mHasStatusBar = false;
+
+ /**
+ * Create empty layout.
+ */
+ public DisplayLayout() {
+ }
+
+ /**
+ * Construct a custom display layout using a DisplayInfo.
+ * @param info
+ * @param res
+ */
+ public DisplayLayout(DisplayInfo info, Resources res, boolean hasNavigationBar,
+ boolean hasStatusBar) {
+ init(info, res, hasNavigationBar, hasStatusBar);
+ }
+
+ /**
+ * Construct a display layout based on a live display.
+ * @param context Used for resources.
+ */
+ public DisplayLayout(@NonNull Context context, @NonNull Display rawDisplay) {
+ final int displayId = rawDisplay.getDisplayId();
+ DisplayInfo info = new DisplayInfo();
+ rawDisplay.getDisplayInfo(info);
+ init(info, context.getResources(), hasNavigationBar(info, context, displayId),
+ hasStatusBar(displayId));
+ }
+
+ public DisplayLayout(DisplayLayout dl) {
+ set(dl);
+ }
+
+ /** sets this DisplayLayout to a copy of another on. */
+ public void set(DisplayLayout dl) {
+ mUiMode = dl.mUiMode;
+ mWidth = dl.mWidth;
+ mHeight = dl.mHeight;
+ mCutout = dl.mCutout;
+ mRotation = dl.mRotation;
+ mDensityDpi = dl.mDensityDpi;
+ mHasNavigationBar = dl.mHasNavigationBar;
+ mHasStatusBar = dl.mHasStatusBar;
+ mNonDecorInsets.set(dl.mNonDecorInsets);
+ mStableInsets.set(dl.mStableInsets);
+ }
+
+ private void init(DisplayInfo info, Resources res, boolean hasNavigationBar,
+ boolean hasStatusBar) {
+ mUiMode = res.getConfiguration().uiMode;
+ mWidth = info.logicalWidth;
+ mHeight = info.logicalHeight;
+ mRotation = info.rotation;
+ mCutout = info.displayCutout;
+ mDensityDpi = info.logicalDensityDpi;
+ mHasNavigationBar = hasNavigationBar;
+ mHasStatusBar = hasStatusBar;
+ recalcInsets(res);
+ }
+
+ private void recalcInsets(Resources res) {
+ computeNonDecorInsets(res, mRotation, mWidth, mHeight, mCutout, mUiMode, mNonDecorInsets,
+ mHasNavigationBar);
+ mStableInsets.set(mNonDecorInsets);
+ if (mHasStatusBar) {
+ convertNonDecorInsetsToStableInsets(res, mStableInsets, mWidth, mHeight, mHasStatusBar);
+ }
+ }
+
+ /**
+ * Apply a rotation to this layout and its parameters.
+ * @param res
+ * @param targetRotation
+ */
+ public void rotateTo(Resources res, @Surface.Rotation int targetRotation) {
+ final int rotationDelta = (targetRotation - mRotation + 4) % 4;
+ final boolean changeOrient = (rotationDelta % 2) != 0;
+
+ final int origWidth = mWidth;
+ final int origHeight = mHeight;
+
+ mRotation = targetRotation;
+ if (changeOrient) {
+ mWidth = origHeight;
+ mHeight = origWidth;
+ }
+
+ if (mCutout != null && !mCutout.isEmpty()) {
+ mCutout = calculateDisplayCutoutForRotation(mCutout, rotationDelta, origWidth,
+ origHeight);
+ }
+
+ recalcInsets(res);
+ }
+
+ /** Get this layout's non-decor insets. */
+ public Rect nonDecorInsets() {
+ return mNonDecorInsets;
+ }
+
+ /** Get this layout's stable insets. */
+ public Rect stableInsets() {
+ return mStableInsets;
+ }
+
+ /** Get this layout's width. */
+ public int width() {
+ return mWidth;
+ }
+
+ /** Get this layout's height. */
+ public int height() {
+ return mHeight;
+ }
+
+ /** Get this layout's display rotation. */
+ public int rotation() {
+ return mRotation;
+ }
+
+ /** Get this layout's display density. */
+ public int densityDpi() {
+ return mDensityDpi;
+ }
+
+ /** Get whether this layout is landscape. */
+ public boolean isLandscape() {
+ return mWidth > mHeight;
+ }
+
+ /** Gets the orientation of this layout */
+ public int getOrientation() {
+ return (mWidth > mHeight) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ }
+
+ /** Gets the calculated stable-bounds for this layout */
+ public void getStableBounds(Rect outBounds) {
+ outBounds.set(0, 0, mWidth, mHeight);
+ outBounds.inset(mStableInsets);
+ }
+
+ /**
+ * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta`
+ * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and
+ * remains at 0,0 after rotation.
+ *
+ * Only 'bounds' is mutated.
+ */
+ public static void rotateBounds(Rect inOutBounds, Rect parentBounds, int delta) {
+ int rdelta = ((delta % 4) + 4) % 4;
+ int origLeft = inOutBounds.left;
+ switch (rdelta) {
+ case 0:
+ return;
+ case 1:
+ inOutBounds.left = inOutBounds.top;
+ inOutBounds.top = parentBounds.right - inOutBounds.right;
+ inOutBounds.right = inOutBounds.bottom;
+ inOutBounds.bottom = parentBounds.right - origLeft;
+ return;
+ case 2:
+ inOutBounds.left = parentBounds.right - inOutBounds.right;
+ inOutBounds.right = parentBounds.right - origLeft;
+ return;
+ case 3:
+ inOutBounds.left = parentBounds.bottom - inOutBounds.bottom;
+ inOutBounds.bottom = inOutBounds.right;
+ inOutBounds.right = parentBounds.bottom - inOutBounds.top;
+ inOutBounds.top = origLeft;
+ return;
+ }
+ }
+
+ /**
+ * Calculates the stable insets if we already have the non-decor insets.
+ */
+ private static void convertNonDecorInsetsToStableInsets(Resources res, Rect inOutInsets,
+ int displayWidth, int displayHeight, boolean hasStatusBar) {
+ if (!hasStatusBar) {
+ return;
+ }
+ int statusBarHeight = getStatusBarHeight(displayWidth > displayHeight, res);
+ inOutInsets.top = Math.max(inOutInsets.top, statusBarHeight);
+ }
+
+ /**
+ * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
+ * bar or button bar.
+ *
+ * @param displayRotation the current display rotation
+ * @param displayWidth the current display width
+ * @param displayHeight the current display height
+ * @param displayCutout the current display cutout
+ * @param outInsets the insets to return
+ */
+ static void computeNonDecorInsets(Resources res, int displayRotation, int displayWidth,
+ int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
+ boolean hasNavigationBar) {
+ outInsets.setEmpty();
+
+ // Only navigation bar
+ if (hasNavigationBar) {
+ int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
+ int navBarSize =
+ getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
+ if (position == NAV_BAR_BOTTOM) {
+ outInsets.bottom = navBarSize;
+ } else if (position == NAV_BAR_RIGHT) {
+ outInsets.right = navBarSize;
+ } else if (position == NAV_BAR_LEFT) {
+ outInsets.left = navBarSize;
+ }
+ }
+
+ if (displayCutout != null) {
+ outInsets.left += displayCutout.getSafeInsetLeft();
+ outInsets.top += displayCutout.getSafeInsetTop();
+ outInsets.right += displayCutout.getSafeInsetRight();
+ outInsets.bottom += displayCutout.getSafeInsetBottom();
+ }
+ }
+
+ /**
+ * Calculates the stable insets without running a layout.
+ *
+ * @param displayRotation the current display rotation
+ * @param displayWidth the current display width
+ * @param displayHeight the current display height
+ * @param displayCutout the current display cutout
+ * @param outInsets the insets to return
+ */
+ static void computeStableInsets(Resources res, int displayRotation, int displayWidth,
+ int displayHeight, DisplayCutout displayCutout, int uiMode, Rect outInsets,
+ boolean hasNavigationBar, boolean hasStatusBar) {
+ outInsets.setEmpty();
+
+ // Navigation bar and status bar.
+ computeNonDecorInsets(res, displayRotation, displayWidth, displayHeight, displayCutout,
+ uiMode, outInsets, hasNavigationBar);
+ convertNonDecorInsetsToStableInsets(res, outInsets, displayWidth, displayHeight,
+ hasStatusBar);
+ }
+
+ /** Retrieve the statusbar height from resources. */
+ static int getStatusBarHeight(boolean landscape, Resources res) {
+ return landscape ? res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height_landscape)
+ : res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height_portrait);
+ }
+
+ /** Calculate the DisplayCutout for a particular display size/rotation. */
+ public static DisplayCutout calculateDisplayCutoutForRotation(
+ DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
+ if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
+ return null;
+ }
+ if (rotation == ROTATION_0) {
+ return computeSafeInsets(
+ cutout, displayWidth, displayHeight);
+ }
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ Rect[] cutoutRects = computeSafeInsets(cutout, displayWidth, displayHeight)
+ .getBoundingRectsAll();
+ final Rect[] newBounds = new Rect[cutoutRects.length];
+ final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight);
+ for (int i = 0; i < cutoutRects.length; ++i) {
+ newBounds[i] = new Rect(cutoutRects[i]);
+ rotateBounds(newBounds[i], displayBounds, rotation);
+ }
+ return computeSafeInsets(DisplayCutout.fromBounds(newBounds),
+ rotated ? displayHeight : displayWidth,
+ rotated ? displayWidth : displayHeight);
+ }
+
+ /** Calculate safe insets. */
+ public static DisplayCutout computeSafeInsets(DisplayCutout inner,
+ int displayWidth, int displayHeight) {
+ if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
+ return null;
+ }
+
+ final Size displaySize = new Size(displayWidth, displayHeight);
+ final Rect safeInsets = computeSafeInsets(displaySize, inner);
+ return inner.replaceSafeInsets(safeInsets);
+ }
+
+ private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
+ if (displaySize.getWidth() < displaySize.getHeight()) {
+ final List<Rect> boundingRects = cutout.replaceSafeInsets(
+ new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
+ .getBoundingRects();
+ int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
+ int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
+ return new Rect(0, topInset, 0, bottomInset);
+ } else if (displaySize.getWidth() > displaySize.getHeight()) {
+ final List<Rect> boundingRects = cutout.replaceSafeInsets(
+ new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
+ .getBoundingRects();
+ int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
+ int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
+ return new Rect(leftInset, 0, right, 0);
+ } else {
+ throw new UnsupportedOperationException("not implemented: display=" + displaySize
+ + " cutout=" + cutout);
+ }
+ }
+
+ private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
+ int inset = 0;
+ final int size = boundingRects.size();
+ for (int i = 0; i < size; i++) {
+ Rect boundingRect = boundingRects.get(i);
+ switch (gravity) {
+ case Gravity.TOP:
+ if (boundingRect.top == 0) {
+ inset = Math.max(inset, boundingRect.bottom);
+ }
+ break;
+ case Gravity.BOTTOM:
+ if (boundingRect.bottom == display.getHeight()) {
+ inset = Math.max(inset, display.getHeight() - boundingRect.top);
+ }
+ break;
+ case Gravity.LEFT:
+ if (boundingRect.left == 0) {
+ inset = Math.max(inset, boundingRect.right);
+ }
+ break;
+ case Gravity.RIGHT:
+ if (boundingRect.right == display.getWidth()) {
+ inset = Math.max(inset, display.getWidth() - boundingRect.left);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("unknown gravity: " + gravity);
+ }
+ }
+ return inset;
+ }
+
+ static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ // Allow a system property to override this. Used by the emulator.
+ final String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
+ if ("1".equals(navBarOverride)) {
+ return false;
+ } else if ("0".equals(navBarOverride)) {
+ return true;
+ }
+ return context.getResources().getBoolean(R.bool.config_showNavigationBar);
+ } else {
+ boolean isUntrustedVirtualDisplay = info.type == Display.TYPE_VIRTUAL
+ && info.ownerUid != SYSTEM_UID;
+ final ContentResolver resolver = context.getContentResolver();
+ boolean forceDesktopOnExternal = Settings.Global.getInt(resolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
+
+ return ((info.flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
+ || (forceDesktopOnExternal && !isUntrustedVirtualDisplay));
+ // TODO(b/142569966): make sure VR2D and DisplayWindowSettings are moved here somehow.
+ }
+ }
+
+ static boolean hasStatusBar(int displayId) {
+ return displayId == Display.DEFAULT_DISPLAY;
+ }
+
+ /** Retrieve navigation bar position from resources based on rotation and size. */
+ public static int navigationBarPosition(Resources res, int displayWidth, int displayHeight,
+ int rotation) {
+ boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean(
+ com.android.internal.R.bool.config_navBarCanMove);
+ if (navBarCanMove && displayWidth > displayHeight) {
+ if (rotation == Surface.ROTATION_90) {
+ return NAV_BAR_RIGHT;
+ } else {
+ return NAV_BAR_LEFT;
+ }
+ }
+ return NAV_BAR_BOTTOM;
+ }
+
+ /** Retrieve navigation bar size from resources based on side/orientation/ui-mode */
+ public static int getNavigationBarSize(Resources res, int navBarSide, boolean landscape,
+ int uiMode) {
+ final boolean carMode = (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR;
+ if (carMode) {
+ if (navBarSide == NAV_BAR_BOTTOM) {
+ return res.getDimensionPixelSize(landscape
+ ? R.dimen.navigation_bar_height_landscape_car_mode
+ : R.dimen.navigation_bar_height_car_mode);
+ } else {
+ return res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode);
+ }
+ } else {
+ if (navBarSide == NAV_BAR_BOTTOM) {
+ return res.getDimensionPixelSize(landscape
+ ? R.dimen.navigation_bar_height_landscape
+ : R.dimen.navigation_bar_height);
+ } else {
+ return res.getDimensionPixelSize(R.dimen.navigation_bar_width);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
index 19fff79..951d6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
@@ -16,18 +16,22 @@
package com.android.systemui.wm;
+import android.annotation.Nullable;
+import android.content.Context;
import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.Display;
import android.view.IDisplayWindowListener;
import android.view.IDisplayWindowRotationCallback;
import android.view.IDisplayWindowRotationController;
+import android.view.IWindowManager;
import android.view.WindowContainerTransaction;
-import android.view.WindowManagerGlobal;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.Main;
import java.util.ArrayList;
@@ -45,6 +49,8 @@
private static final String TAG = "DisplayWindowController";
private final Handler mHandler;
+ private final Context mContext;
+ private final IWindowManager mWmService;
private final ArrayList<OnDisplayWindowRotationController> mRotationControllers =
new ArrayList<>();
@@ -76,6 +82,14 @@
}
};
+ /**
+ * Get's a display by id from DisplayManager.
+ */
+ public Display getDisplay(int displayId) {
+ final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ return displayManager.getDisplay(displayId);
+ }
+
private final IDisplayWindowListener mDisplayContainerListener =
new IDisplayWindowListener.Stub() {
@Override
@@ -85,8 +99,17 @@
if (mDisplays.get(displayId) != null) {
return;
}
+ Display display = getDisplay(displayId);
+ if (display == null) {
+ // It's likely that the display is private to some app and thus not
+ // accessible by system-ui.
+ return;
+ }
DisplayRecord record = new DisplayRecord();
record.mDisplayId = displayId;
+ record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
+ : mContext.createDisplayContext(display);
+ record.mDisplayLayout = new DisplayLayout(record.mContext, display);
mDisplays.put(displayId, record);
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -105,6 +128,13 @@
+ " display.");
return;
}
+ Display display = getDisplay(displayId);
+ Context perDisplayContext = mContext;
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ perDisplayContext = mContext.createDisplayContext(display);
+ }
+ dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
+ dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
displayId, newConfig);
@@ -117,6 +147,9 @@
public void onDisplayRemoved(int displayId) {
mHandler.post(() -> {
synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null) {
+ return;
+ }
for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
}
@@ -127,19 +160,36 @@
};
@Inject
- public DisplayWindowController(@MainHandler Handler mainHandler) {
+ public DisplayWindowController(Context context, @Main Handler mainHandler,
+ IWindowManager wmService) {
mHandler = mainHandler;
+ mContext = context;
+ mWmService = wmService;
try {
- WindowManagerGlobal.getWindowManagerService().registerDisplayWindowListener(
- mDisplayContainerListener);
- WindowManagerGlobal.getWindowManagerService().setDisplayWindowRotationController(
- mDisplayRotationController);
+ mWmService.registerDisplayWindowListener(mDisplayContainerListener);
+ mWmService.setDisplayWindowRotationController(mDisplayRotationController);
} catch (RemoteException e) {
throw new RuntimeException("Unable to register hierarchy listener");
}
}
/**
+ * Gets the DisplayLayout associated with a display.
+ */
+ public @Nullable DisplayLayout getDisplayLayout(int displayId) {
+ final DisplayRecord r = mDisplays.get(displayId);
+ return r != null ? r.mDisplayLayout : null;
+ }
+
+ /**
+ * Gets a display-specific context for a display.
+ */
+ public @Nullable Context getDisplayContext(int displayId) {
+ final DisplayRecord r = mDisplays.get(displayId);
+ return r != null ? r.mContext : null;
+ }
+
+ /**
* Add a display window-container listener. It will get notified whenever a display's
* configuration changes or when displays are added/removed from the WM hierarchy.
*/
@@ -184,6 +234,8 @@
private static class DisplayRecord {
int mDisplayId;
+ Context mContext;
+ DisplayLayout mDisplayLayout;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
new file mode 100644
index 0000000..5ec61c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
@@ -0,0 +1,330 @@
+/*
+ * 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 static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.DragEvent;
+import android.view.IWindow;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+import android.view.IWindowSessionCallback;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
+import android.view.WindowlessWindowManager;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.util.HashMap;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Represents the "windowing" layer of the System-UI. This layer allows system-ui components to
+ * place and manipulate windows without talking to WindowManager.
+ */
+@Singleton
+public class SystemWindows {
+ private static final String TAG = "SystemWindows";
+
+ private final SparseArray<PerDisplay> mPerDisplay = new SparseArray<>();
+ final HashMap<View, WindowlessViewRoot> mViewRoots = new HashMap<>();
+ Context mContext;
+ IWindowSession mSession;
+ DisplayWindowController mDisplayController;
+ IWindowManager mWmService;
+
+ private final DisplayWindowController.DisplayWindowListener mDisplayListener =
+ new DisplayWindowController.DisplayWindowListener() {
+ @Override
+ public void onDisplayAdded(int displayId) { }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ PerDisplay pd = mPerDisplay.get(displayId);
+ if (pd == null) {
+ return;
+ }
+ pd.updateConfiguration(newConfig);
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) { }
+ };
+
+ @Inject
+ public SystemWindows(Context context, DisplayWindowController displayController,
+ IWindowManager wmService) {
+ mContext = context;
+ mWmService = wmService;
+ mDisplayController = displayController;
+ mDisplayController.addDisplayWindowListener(mDisplayListener);
+ try {
+ mSession = wmService.openSession(
+ new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float scale) {}
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to create layer", e);
+ }
+ }
+
+ /**
+ * Adds a view to system-ui window management.
+ */
+ public void addView(View view, WindowManager.LayoutParams attrs, int displayId,
+ int windowType) {
+ PerDisplay pd = mPerDisplay.get(displayId);
+ if (pd == null) {
+ pd = new PerDisplay(displayId);
+ mPerDisplay.put(displayId, pd);
+ }
+ pd.addView(view, attrs, windowType);
+ }
+
+ /**
+ * Removes a view from system-ui window management.
+ * @param view
+ */
+ public void removeView(View view) {
+ WindowlessViewRoot root = mViewRoots.remove(view);
+ root.die();
+ }
+
+ /**
+ * Updates the layout params of a view.
+ */
+ public void updateViewLayout(@NonNull View view, ViewGroup.LayoutParams params) {
+ WindowlessViewRoot root = mViewRoots.get(view);
+ if (root == null || !(params instanceof WindowManager.LayoutParams)) {
+ return;
+ }
+ view.setLayoutParams(params);
+ root.relayout((WindowManager.LayoutParams) params);
+ }
+
+ /**
+ * Adds a root for system-ui window management with no views. Only useful for IME.
+ */
+ public void addRoot(int displayId, int windowType) {
+ PerDisplay pd = mPerDisplay.get(displayId);
+ if (pd == null) {
+ pd = new PerDisplay(displayId);
+ mPerDisplay.put(displayId, pd);
+ }
+ pd.addRoot(windowType);
+ }
+
+ /**
+ * Get the IWindow token for a specific root.
+ *
+ * @param windowType A window type from {@link android.view.WindowManager}.
+ */
+ IWindow getWindow(int displayId, int windowType) {
+ PerDisplay pd = mPerDisplay.get(displayId);
+ if (pd == null) {
+ return null;
+ }
+ return pd.getWindow(windowType);
+ }
+
+ private class PerDisplay {
+ final int mDisplayId;
+ private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();
+
+ PerDisplay(int displayId) {
+ mDisplayId = displayId;
+ }
+
+ public void addView(View view, WindowManager.LayoutParams attrs, int windowType) {
+ SysUiWindowManager wwm = addRoot(windowType);
+ if (wwm == null) {
+ Slog.e(TAG, "Unable to create systemui root");
+ return;
+ }
+ final Display display = mDisplayController.getDisplay(mDisplayId);
+ WindowlessViewRoot viewRoot = new WindowlessViewRoot(mContext, display, wwm);
+ attrs.flags |= FLAG_HARDWARE_ACCELERATED;
+ viewRoot.addView(view, attrs);
+ mViewRoots.put(view, viewRoot);
+ }
+
+ SysUiWindowManager addRoot(int windowType) {
+ SysUiWindowManager wwm = mWwms.get(windowType);
+ if (wwm != null) {
+ return wwm;
+ }
+ SurfaceControl rootSurface = null;
+ ContainerWindow win = new ContainerWindow();
+ try {
+ rootSurface = mWmService.addShellRoot(mDisplayId, win, windowType);
+ } catch (RemoteException e) {
+ }
+ if (rootSurface == null) {
+ Slog.e(TAG, "Unable to get root surfacecontrol for systemui");
+ return null;
+ }
+ Context displayContext = mDisplayController.getDisplayContext(mDisplayId);
+ wwm = new SysUiWindowManager(mDisplayId, displayContext, rootSurface, win);
+ mWwms.put(windowType, wwm);
+ return wwm;
+ }
+
+ IWindow getWindow(int windowType) {
+ SysUiWindowManager wwm = mWwms.get(windowType);
+ if (wwm == null) {
+ return null;
+ }
+ return wwm.mContainerWindow;
+ }
+
+ void updateConfiguration(Configuration configuration) {
+ for (int i = 0; i < mWwms.size(); ++i) {
+ mWwms.valueAt(i).updateConfiguration(configuration);
+ }
+ }
+ }
+
+ /**
+ * A subclass of WindowlessWindowManager that provides insets to its viewroots.
+ */
+ public class SysUiWindowManager extends WindowlessWindowManager {
+ final int mDisplayId;
+ ContainerWindow mContainerWindow;
+ public SysUiWindowManager(int displayId, Context ctx, SurfaceControl rootSurface,
+ ContainerWindow container) {
+ super(ctx.getResources().getConfiguration(), rootSurface, null /* hostInputToken */);
+ mContainerWindow = container;
+ mDisplayId = displayId;
+ }
+
+ @Override
+ public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
+ int requestedWidth, int requestedHeight, int viewVisibility, int flags,
+ long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
+ Rect outVisibleInsets, Rect outStableInsets,
+ DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
+ SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
+ int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight,
+ viewVisibility, flags, frameNumber, outFrame, outOverscanInsets,
+ outContentInsets, outVisibleInsets, outStableInsets,
+ cutout, mergedConfiguration, outSurfaceControl, outInsetsState);
+ if (res != 0) {
+ return res;
+ }
+ DisplayLayout dl = mDisplayController.getDisplayLayout(mDisplayId);
+ outStableInsets.set(dl.stableInsets());
+ return 0;
+ }
+
+ void updateConfiguration(Configuration configuration) {
+ setConfiguration(configuration);
+ }
+ }
+
+ class ContainerWindow extends IWindow.Stub {
+ ContainerWindow() {}
+
+ @Override
+ public void resized(Rect frame, Rect contentInsets, Rect visibleInsets, Rect stableInsets,
+ boolean reportDraw, MergedConfiguration newMergedConfiguration, Rect backDropFrame,
+ boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
+ DisplayCutout.ParcelableWrapper displayCutout) {}
+
+ @Override
+ public void locationInParentDisplayChanged(Point offset) {}
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {}
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {}
+
+ @Override
+ public void showInsets(int types, boolean fromIme) {}
+
+ @Override
+ public void hideInsets(int types, boolean fromIme) {}
+
+ @Override
+ public void moved(int newX, int newY) {}
+
+ @Override
+ public void dispatchAppVisibility(boolean visible) {}
+
+ @Override
+ public void dispatchGetNewSurface() {}
+
+ @Override
+ public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {}
+
+ @Override
+ public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {}
+
+ @Override
+ public void closeSystemDialogs(String reason) {}
+
+ @Override
+ public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
+ boolean sync) {}
+
+ @Override
+ public void dispatchWallpaperCommand(String action, int x, int y,
+ int z, Bundle extras, boolean sync) {}
+
+ /* Drag/drop */
+ @Override
+ public void dispatchDragEvent(DragEvent event) {}
+
+ @Override
+ public void updatePointerIcon(float x, float y) {}
+
+ @Override
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
+ int localValue, int localChanges) {}
+
+ @Override
+ public void dispatchWindowShown() {}
+
+ @Override
+ public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {}
+
+ @Override
+ public void dispatchPointerCaptureChanged(boolean hasCapture) {}
+ }
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 81e2c22..e5f56d4 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -32,7 +32,8 @@
LOCAL_JNI_SHARED_LIBRARIES := \
libdexmakerjvmtiagent \
- libmultiplejvmtiagentsinterferenceagent
+ libmultiplejvmtiagentsinterferenceagent \
+ libstaticjvmtiagent
LOCAL_JAVA_LIBRARIES := \
android.test.runner \
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7be3e2b..4bf1e1c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -57,7 +57,6 @@
import android.testing.TestableContext;
import android.testing.TestableLooper;
-import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.DumpController;
@@ -183,8 +182,8 @@
@Test
public void testTelephonyCapable_SimState_Absent() {
Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
- IccCardConstants.INTENT_VALUE_ICC_ABSENT);
+ intent.putExtra(Intent.EXTRA_SIM_STATE,
+ Intent.SIM_STATE_ABSENT);
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(),
putPhoneInfo(intent, null, false));
mTestableLooper.processAllMessages();
@@ -194,8 +193,8 @@
@Test
public void testTelephonyCapable_SimState_CardIOError() {
Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
- IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
+ intent.putExtra(Intent.EXTRA_SIM_STATE,
+ Intent.SIM_STATE_CARD_IO_ERROR);
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext(),
putPhoneInfo(intent, null, false));
mTestableLooper.processAllMessages();
@@ -221,8 +220,8 @@
// 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.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
- , IccCardConstants.INTENT_VALUE_ICC_LOADED);
+ intent.putExtra(Intent.EXTRA_SIM_STATE
+ , Intent.SIM_STATE_LOADED);
Bundle data = new Bundle();
ServiceState state = new ServiceState();
state.setState(ServiceState.STATE_POWER_OFF);
@@ -261,8 +260,8 @@
state.setState(ServiceState.STATE_OUT_OF_SERVICE);
state.fillInNotifierBundle(data);
Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
- , IccCardConstants.INTENT_VALUE_ICC_NOT_READY);
+ intent.putExtra(Intent.EXTRA_SIM_STATE
+ , Intent.SIM_STATE_NOT_READY);
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
, putPhoneInfo(intent, data, false));
mTestableLooper.processAllMessages();
@@ -276,8 +275,8 @@
state.setState(ServiceState.STATE_OUT_OF_SERVICE);
state.fillInNotifierBundle(data);
Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
- , IccCardConstants.INTENT_VALUE_ICC_READY);
+ intent.putExtra(Intent.EXTRA_SIM_STATE
+ , Intent.SIM_STATE_READY);
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
, putPhoneInfo(intent, data, false));
mTestableLooper.processAllMessages();
@@ -317,8 +316,8 @@
state.setState(ServiceState.STATE_IN_SERVICE);
state.fillInNotifierBundle(data);
Intent intentSimState = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- intentSimState.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
- , IccCardConstants.INTENT_VALUE_ICC_LOADED);
+ intentSimState.putExtra(Intent.EXTRA_SIM_STATE
+ , Intent.SIM_STATE_LOADED);
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
, putPhoneInfo(intentSimState, data, true));
mTestableLooper.processAllMessages();
@@ -326,8 +325,8 @@
assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
Intent intentServiceState = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
- intentSimState.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE
- , IccCardConstants.INTENT_VALUE_ICC_LOADED);
+ intentSimState.putExtra(Intent.EXTRA_SIM_STATE
+ , Intent.SIM_STATE_LOADED);
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
, putPhoneInfo(intentServiceState, data, true));
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java
index d2b2654..4c0890a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java
@@ -57,7 +57,7 @@
@Test
public void testGetTitle() {
final String title = "title";
- ClockInfo info = ClockInfo.builder().setTitle(title).build();
+ ClockInfo info = ClockInfo.builder().setTitle(() -> title).build();
assertThat(info.getTitle()).isEqualTo(title);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java
index 0cd6f9a..d2832fb9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java
@@ -117,12 +117,12 @@
public void testQuery_listOptions() {
mClocks.add(ClockInfo.builder()
.setName("name_a")
- .setTitle("title_a")
+ .setTitle(() -> "title_a")
.setId("id_a")
.build());
mClocks.add(ClockInfo.builder()
.setName("name_b")
- .setTitle("title_b")
+ .setTitle(() -> "title_b")
.setId("id_b")
.build());
Cursor cursor = mProvider.query(mListOptionsUri, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 4cb5472..9c9a627f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -46,11 +46,11 @@
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
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.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import junit.framework.Assert;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
index 212c93d..46a473b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
@@ -27,8 +27,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 819a7f6..c85d600 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -117,7 +117,7 @@
}
protected void waitForUiOffloadThread() {
- Future<?> future = Dependency.get(UiOffloadThread.class).submit(() -> {});
+ Future<?> future = Dependency.get(UiOffloadThread.class).execute(() -> { });
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index df67637..25cc9a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -24,7 +26,6 @@
import static org.mockito.Mockito.verify;
import android.content.Context;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
@@ -292,9 +293,9 @@
private Bundle buildBiometricPromptBundle(boolean allowDeviceCredential) {
Bundle bundle = new Bundle();
bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
- int authenticators = Authenticator.TYPE_BIOMETRIC;
+ int authenticators = Authenticators.BIOMETRIC_WEAK;
if (allowDeviceCredential) {
- authenticators |= Authenticator.TYPE_CREDENTIAL;
+ authenticators |= Authenticators.DEVICE_CREDENTIAL;
} else {
bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative");
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 6e438e8..162b16e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -26,7 +28,6 @@
import static org.mockito.Mockito.verify;
import android.content.Context;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
@@ -64,7 +65,7 @@
@Test
public void testActionAuthenticated_sendsDismissedAuthenticated() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_AUTHENTICATED);
@@ -73,7 +74,7 @@
@Test
public void testActionUserCanceled_sendsDismissedUserCanceled() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_USER_CANCELED);
@@ -82,7 +83,7 @@
@Test
public void testActionButtonNegative_sendsDismissedButtonNegative() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
@@ -91,7 +92,7 @@
@Test
public void testActionTryAgain_sendsTryAgain() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
@@ -100,7 +101,7 @@
@Test
public void testActionError_sendsDismissedError() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_ERROR);
@@ -110,7 +111,7 @@
@Test
public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
initializeContainer(
- Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL);
+ Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
mAuthContainer.mBiometricCallback.onAction(
AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
@@ -125,7 +126,7 @@
@Test
public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
initializeContainer(
- Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL);
+ Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
mAuthContainer.mBiometricView = mock(AuthBiometricView.class);
mAuthContainer.animateToCredentialUI();
@@ -134,7 +135,7 @@
@Test
public void testShowBiometricUI() {
- initializeContainer(Authenticator.TYPE_BIOMETRIC);
+ initializeContainer(Authenticators.BIOMETRIC_WEAK);
assertNotEquals(null, mAuthContainer.mBiometricView);
@@ -146,7 +147,7 @@
@Test
public void testShowCredentialUI_doesNotInflateBiometricUI() {
- initializeContainer(Authenticator.TYPE_CREDENTIAL);
+ initializeContainer(Authenticators.DEVICE_CREDENTIAL);
mAuthContainer.onAttachedToWindowInternal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index f6375fc..c0e92e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
import static junit.framework.TestCase.assertNotNull;
@@ -38,7 +40,6 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -109,28 +110,28 @@
@Test
public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
}
@Test
public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
}
@Test
public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
}
@Test
public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
verify(mReceiver).onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
@@ -138,14 +139,14 @@
@Test
public void testSendsReasonError_whenDismissedByError() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
}
@Test
public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
}
@@ -153,7 +154,7 @@
@Test
public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated()
throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
}
@@ -163,20 +164,20 @@
@Test
public void testShowInvoked_whenSystemRequested()
throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
}
@Test
public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onBiometricAuthenticated();
verify(mDialog1).onAuthenticationSucceeded();
}
@Test
public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
0 /* vendorCode */);
@@ -189,7 +190,7 @@
@Test
public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
final int vendorCode = 0;
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
@@ -202,7 +203,7 @@
@Test
public void testOnHelpInvoked_whenSystemRequested() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final String helpMessage = "help";
mAuthController.onBiometricHelp(helpMessage);
@@ -214,7 +215,7 @@
@Test
public void testOnErrorInvoked_whenSystemRequested() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = 1;
final int vendorCode = 0;
mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
@@ -227,7 +228,7 @@
@Test
public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final int vendorCode = 0;
@@ -240,7 +241,7 @@
@Test
public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final int vendorCode = 0;
@@ -253,7 +254,7 @@
@Test
public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
final int vendorCode = 0;
@@ -266,7 +267,7 @@
@Test
public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
final int vendorCode = 0;
@@ -278,30 +279,24 @@
}
@Test
- public void testDismissWithoutCallbackInvoked_whenSystemRequested() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
- mAuthController.hideAuthenticationDialog();
- verify(mDialog1).dismissFromSystemServer();
- }
-
- @Test
- public void testClientNotified_whenDismissedBySystemServer() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ public void testHideAuthenticationDialog_invokesDismissFromSystemServer() {
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.hideAuthenticationDialog();
verify(mDialog1).dismissFromSystemServer();
- assertNotNull(mAuthController.mCurrentDialog);
- assertNotNull(mAuthController.mReceiver);
+ // In this case, BiometricService sends the error to the client immediately, without
+ // doing a round trip to SystemUI.
+ assertNull(mAuthController.mCurrentDialog);
}
// Corner case tests
@Test
public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
// First dialog should be dismissed without animation
verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
@@ -312,7 +307,7 @@
@Test
public void testConfigurationPersists_whenOnConfigurationChanged() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
// Return that the UI is in "showing" state
@@ -342,7 +337,7 @@
@Test
public void testConfigurationPersists_whenBiometricFallbackToCredential() {
- showDialog(Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC,
+ showDialog(Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
BiometricPrompt.TYPE_FACE);
verify(mDialog1).show(any(), any());
@@ -361,14 +356,14 @@
// Check that the new dialog was initialized to the credential UI.
ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
verify(mDialog2).show(any(), captor.capture());
- assertEquals(Authenticator.TYPE_CREDENTIAL,
+ assertEquals(Authenticators.DEVICE_CREDENTIAL,
mAuthController.mLastBiometricPromptBundle
.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
}
@Test
public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -388,21 +383,21 @@
@Test
public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
mAuthController.onTryAgainPressed();
}
@Test
public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
mAuthController.onDeviceCredentialPressed();
}
@Test
public void testActionCloseSystemDialogs_dismissesDialogIfShowing() throws Exception {
- showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+ showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mAuthController.mBroadcastReceiver.onReceive(mContext, intent);
waitForIdleSync();
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 2242c1a..42fbf59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -93,7 +93,7 @@
// These should be valid filters
`when`(intentFilter.countActions()).thenReturn(1)
`when`(intentFilterOther.countActions()).thenReturn(1)
- `when`(mockContext.user).thenReturn(user0)
+ setUserMock(mockContext, user0)
}
@Test
@@ -140,6 +140,18 @@
verify(mockUBRUser1, never()).unregisterReceiver(broadcastReceiver)
}
+ @Test
+ fun testRegisterCurrentAsActualUser() {
+ setUserMock(mockContext, user1)
+ broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler,
+ UserHandle.CURRENT)
+
+ testableLooper.processAllMessages()
+
+ verify(mockUBRUser1).registerReceiver(capture(argumentCaptor))
+ assertSame(broadcastReceiver, argumentCaptor.value.receiver)
+ }
+
@Test(expected = IllegalArgumentException::class)
fun testFilterMustContainActions() {
val testFilter = IntentFilter()
@@ -186,6 +198,11 @@
broadcastDispatcher.registerReceiver(broadcastReceiver, testFilter)
}
+ private fun setUserMock(mockContext: Context, user: UserHandle) {
+ `when`(mockContext.user).thenReturn(user)
+ `when`(mockContext.userId).thenReturn(user.identifier)
+ }
+
private class TestBroadcastDispatcher(
context: Context,
mainHandler: Handler,
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 ae43aa2..e0b4b81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -45,9 +45,7 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.drawable.Icon;
import android.hardware.face.FaceManager;
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
@@ -57,7 +55,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -94,8 +91,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -153,7 +148,7 @@
@Mock
private Resources mResources;
@Mock
- private Lazy<ShadeController> mShadeController;
+ private ShadeController mShadeController;
@Mock
private RemoteInputUriController mRemoteInputUriController;
@@ -263,7 +258,7 @@
@Test
public void testRemoveBubble_withDismissedNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -306,7 +301,7 @@
assertFalse(mBubbleController.isStackExpanded());
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -336,8 +331,8 @@
@Test
public void testCollapseAfterChangingExpandedBubble() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
@@ -379,7 +374,7 @@
@Test
public void testExpansionRemovesShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -405,7 +400,7 @@
@Test
public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// We should have bubbles & their notifs should not be suppressed
@@ -441,8 +436,8 @@
@Test
public void testRemoveLastExpandedCollapses() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.updateBubble(mRow2.getEntry());
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -479,13 +474,13 @@
}
@Test
- public void testAutoExpand_FailsNotForeground() {
+ public void testAutoExpand_fails_noFlag() {
assertFalse(mBubbleController.isStackExpanded());
setMetadataFlags(mRow.getEntry(),
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Expansion shouldn't change
@@ -498,12 +493,12 @@
}
@Test
- public void testAutoExpand_SucceedsForeground() {
+ public void testAutoExpand_succeeds_withFlag() {
setMetadataFlags(mRow.getEntry(),
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Expansion should change
@@ -516,43 +511,57 @@
}
@Test
- public void testSuppressNotif_FailsNotForeground() {
- setMetadataFlags(mRow.getEntry(),
- Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, false /* enableFlag */);
-
- // Add the suppress notif bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
- mBubbleController.updateBubble(mRow.getEntry());
-
- // Should not be suppressed because we weren't forground
- assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
- mRow.getEntry().getKey()));
- // # of bubbles should change
- verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
- }
-
- @Test
- public void testSuppressNotif_SucceedsForeground() {
+ public void testSuppressNotif_onInitialNotif() {
setMetadataFlags(mRow.getEntry(),
Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
// Add the suppress notif bubble
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Notif should be suppressed because we were foreground
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow.getEntry().getKey()));
+ // Dot + flyout is hidden because notif is suppressed
+ assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+ assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout());
// # of bubbles should change
verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
}
@Test
+ public void testSuppressNotif_onUpdateNotif() {
+ mBubbleController.updateBubble(mRow.getEntry());
+
+ // Should not be suppressed
+ assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+ mRow.getEntry().getKey()));
+ // Should show dot
+ assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+
+ // Update to suppress notif
+ setMetadataFlags(mRow.getEntry(),
+ Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
+ mBubbleController.updateBubble(mRow.getEntry());
+
+ // Notif should be suppressed
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+ mRow.getEntry().getKey()));
+ // Dot + flyout is hidden because notif is suppressed
+ assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
+ assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout());
+
+ // # of bubbles should change
+ verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
+ }
+
+
+ @Test
public void testExpandStackAndSelectBubble_removedFirst() {
final String key = mRow.getEntry().getKey();
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
// Simulate notification cancellation.
@@ -564,7 +573,7 @@
@Test
public void testMarkNewNotificationAsShowInShade() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow.getEntry().getKey()));
@@ -574,7 +583,7 @@
@Test
public void testAddNotif_notBubble() {
- mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
+ mEntryListener.onNotificationAdded(mNonBubbleNotifRow.getEntry());
mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
@@ -619,7 +628,7 @@
@Test
public void testRemoveBubble_succeeds_appCancel() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -634,7 +643,7 @@
@Test
public void removeBubble_fails_clearAll() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -657,7 +666,7 @@
@Test
public void removeBubble_fails_userDismissNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -680,7 +689,7 @@
@Test
public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
- mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ mEntryListener.onNotificationAdded(mRow.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
assertTrue(mBubbleController.hasBubbles());
@@ -705,7 +714,7 @@
TestableBubbleController(Context context,
StatusBarWindowController statusBarWindowController,
StatusBarStateController statusBarStateController,
- Lazy<ShadeController> shadeController,
+ ShadeController shadeController,
BubbleData data,
ConfigurationController configurationController,
NotificationInterruptionStateProvider interruptionStateProvider,
@@ -719,6 +728,7 @@
data, Runnable::run, configurationController, interruptionStateProvider,
zenModeController, lockscreenUserManager, groupManager, entryManager,
remoteInputUriController);
+ setInflateSynchronously(true);
}
}
@@ -734,17 +744,6 @@
}
/**
- * @return basic {@link android.app.Notification.BubbleMetadata.Builder}
- */
- private Notification.BubbleMetadata.Builder getBuilder() {
- Intent target = new Intent(mContext, BubblesTestActivity.class);
- PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
- return new Notification.BubbleMetadata.Builder()
- .setIntent(bubbleIntent)
- .setIcon(Icon.createWithResource(mContext, R.drawable.android));
- }
-
- /**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
* go through that path so we set them explicitly when testing.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 95c7af3..c4ae409 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -39,9 +39,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleData.TimeSource;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationTestHelper;
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.google.common.collect.ImmutableList;
@@ -84,6 +85,8 @@
private Bubble mBubbleB2;
private Bubble mBubbleB3;
private Bubble mBubbleC1;
+ private Bubble mBubbleInterruptive;
+ private Bubble mBubbleDismissed;
private BubbleData mBubbleData;
@@ -118,16 +121,20 @@
modifyRanking(mEntryInterruptive)
.setVisuallyInterruptive(true)
.build();
+ mBubbleInterruptive = new Bubble(mEntryInterruptive);
+ ExpandableNotificationRow row = mNotificationTestHelper.createBubble();
mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d");
+ mEntryDismissed.setRow(row);
+ mBubbleDismissed = new Bubble(mEntryDismissed);
- mBubbleA1 = new Bubble(mContext, mEntryA1);
- mBubbleA2 = new Bubble(mContext, mEntryA2);
- mBubbleA3 = new Bubble(mContext, mEntryA3);
- mBubbleB1 = new Bubble(mContext, mEntryB1);
- mBubbleB2 = new Bubble(mContext, mEntryB2);
- mBubbleB3 = new Bubble(mContext, mEntryB3);
- mBubbleC1 = new Bubble(mContext, mEntryC1);
+ mBubbleA1 = new Bubble(mEntryA1);
+ mBubbleA2 = new Bubble(mEntryA2);
+ mBubbleA3 = new Bubble(mEntryA3);
+ mBubbleB1 = new Bubble(mEntryB1);
+ mBubbleB2 = new Bubble(mEntryB2);
+ mBubbleB3 = new Bubble(mEntryB3);
+ mBubbleC1 = new Bubble(mEntryC1);
mBubbleData = new BubbleData(getContext());
@@ -177,7 +184,7 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryUpdated(mEntryC1, /* suppressFlyout */ true, /* showInShade */
+ mBubbleData.notificationEntryUpdated(mBubbleC1, /* suppressFlyout */ true, /* showInShade */
true);
// Verify
@@ -192,9 +199,8 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryUpdated(mEntryInterruptive, /* suppressFlyout */
- false, /* showInShade */
- true);
+ mBubbleData.notificationEntryUpdated(mBubbleInterruptive,
+ false /* suppressFlyout */, true /* showInShade */);
// Verify
verifyUpdateReceived();
@@ -208,12 +214,12 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryUpdated(mEntryC1, /* suppressFlyout */ false, /* showInShade */
- true);
+ mBubbleData.notificationEntryUpdated(mBubbleC1, false /* suppressFlyout */,
+ true /* showInShade */);
verifyUpdateReceived();
- mBubbleData.notificationEntryUpdated(mEntryC1, /* suppressFlyout */ false, /* showInShade */
- true);
+ mBubbleData.notificationEntryUpdated(mBubbleC1, false /* suppressFlyout */,
+ true /* showInShade */);
verifyUpdateReceived();
// Verify
@@ -225,16 +231,18 @@
public void sameUpdate_NotInShade_showFlyout() {
// Setup
mBubbleData.setListener(mListener);
- setMetadataFlags(mEntryDismissed,
- Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag */ true);
// Test
- mBubbleData.notificationEntryUpdated(mEntryDismissed, /* suppressFlyout */ false,
- /* showInShade */ false);
+ mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */,
+ true /* showInShade */);
verifyUpdateReceived();
- mBubbleData.notificationEntryUpdated(mEntryDismissed, /* suppressFlyout */
- false, /* showInShade */ false);
+ // Make it look like user swiped away row
+ mEntryDismissed.getRow().dismiss(false /* refocusOnDismiss */);
+ assertThat(mBubbleData.getBubbleWithKey(mBubbleDismissed.getKey()).showInShade()).isFalse();
+
+ mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */,
+ true /* showInShade */);
verifyUpdateReceived();
// Verify
@@ -936,23 +944,6 @@
}
/**
- * Sets the bubble metadata flags for this entry. These flags are normally set by
- * NotificationManagerService when the notification is sent, however, these tests do not
- * go through that path so we set them explicitly when testing.
- */
- private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
- Notification.BubbleMetadata bubbleMetadata =
- entry.getSbn().getNotification().getBubbleMetadata();
- int flags = bubbleMetadata.getFlags();
- if (enableFlag) {
- flags |= flag;
- } else {
- flags &= ~flag;
- }
- bubbleMetadata.setFlags(flags);
- }
-
- /**
* No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
* required for BubbleData functionality and verification. NotificationTestHelper is used only
* as a convenience to create a Notification w/BubbleMetadata.
@@ -987,7 +978,10 @@
private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
setPostTime(entry, postTime);
- mBubbleData.notificationEntryUpdated(entry, false /* suppressFlyout*/,
+ // BubbleController calls this:
+ Bubble b = mBubbleData.getOrCreateBubble(entry);
+ // And then this
+ mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
true /* showInShade */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
index 5757861..3c42fd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -28,8 +28,8 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -58,7 +58,7 @@
mEntry = new NotificationEntryBuilder()
.setNotification(mNotif)
.build();
- mBubble = new Bubble(mContext, mEntry);
+ mBubble = new Bubble(mEntry);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
index 3561e34..a19d1df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
@@ -34,7 +34,9 @@
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.sensors.ProximitySensor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -55,6 +57,7 @@
private FalsingManagerProxy mProxy;
private DeviceConfigProxy mDeviceConfig;
private TestableLooper mTestableLooper;
+ private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() {
@@ -77,7 +80,7 @@
@Test
public void test_brightLineFalsingManagerDisabled() {
mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
- mDeviceConfig);
+ mDeviceConfig, mUiBgExecutor);
assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
}
@@ -87,14 +90,14 @@
BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
mTestableLooper.processAllMessages();
mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
- mDeviceConfig);
+ mDeviceConfig, mUiBgExecutor);
assertThat(mProxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
}
@Test
public void test_brightLineFalsingManagerToggled() throws InterruptedException {
mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
- mDeviceConfig);
+ mDeviceConfig, mUiBgExecutor);
assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index f2665ef..775acdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -33,6 +33,7 @@
import android.app.AlarmManager;
import android.database.ContentObserver;
+import android.hardware.Sensor;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -80,6 +81,8 @@
private TriggerSensor mTriggerSensor;
@Mock
private DozeLog mDozeLog;
+ @Mock
+ private Sensor mProximitySensor;
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
private DozeSensors mDozeSensors;
@@ -90,6 +93,7 @@
mTestableLooper = TestableLooper.get(this);
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProximitySensor);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -98,6 +102,14 @@
}
@Test
+ public void testRegisterProx() {
+ // We should not register with the sensor manager initially.
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
+ mDozeSensors.setProxListening(true);
+ verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt());
+ }
+
+ @Test
public void testSensorDebounce() {
mDozeSensors.setListening(true);
@@ -116,6 +128,7 @@
@Test
public void testSetListening_firstTrue_registerSettingsObserver() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
verify(mTriggerSensor).registerSettingsObserver(any(ContentObserver.class));
@@ -123,6 +136,7 @@
@Test
public void testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
mDozeSensors.setListening(true);
@@ -131,6 +145,7 @@
@Test
public void testSetPaused_doesntPause_sensors() {
+ verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
mDozeSensors.setListening(true);
verify(mTriggerSensor).setListening(eq(true));
@@ -147,8 +162,7 @@
TestableDozeSensors() {
super(getContext(), mAlarmManager, mSensorManager, mDozeParameters,
- mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback,
- mAlwaysOnDisplayPolicy, mDozeLog);
+ mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog);
for (TriggerSensor sensor : mSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
index b4a60d6..a5722e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
@@ -52,7 +52,7 @@
@Test
public void testInit_finish() {
- mEglHelper.init(mSurfaceHolder);
+ mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */);
mEglHelper.finish();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageRevealHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageRevealHelperTest.java
new file mode 100644
index 0000000..c827ac7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageRevealHelperTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.glwallpaper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ImageRevealHelperTest extends SysuiTestCase {
+
+ static final int ANIMATION_DURATION = 500;
+ ImageRevealHelper mImageRevealHelper;
+ ImageRevealHelper.RevealStateListener mRevealStateListener;
+
+ @Before
+ public void setUp() throws Exception {
+ mRevealStateListener = new ImageRevealHelper.RevealStateListener() {
+ @Override
+ public void onRevealStateChanged() {
+ // no-op
+ }
+
+ @Override
+ public void onRevealStart(boolean animate) {
+ // no-op
+ }
+
+ @Override
+ public void onRevealEnd() {
+ // no-op
+ }
+ };
+ mImageRevealHelper = new ImageRevealHelper(mRevealStateListener);
+ }
+
+ @Test
+ public void testBiometricAuthUnlockAnimateImageRevealState_shouldNotBlackoutScreen() {
+ assertThat(mImageRevealHelper.getReveal()).isEqualTo(0f);
+
+ mImageRevealHelper.updateAwake(true /* awake */, ANIMATION_DURATION);
+ assertThat(mImageRevealHelper.getReveal()).isEqualTo(0f);
+
+ // When device unlock through Biometric, should not show reveal transition
+ mImageRevealHelper.updateAwake(false /* awake */, 0);
+ assertThat(mImageRevealHelper.getReveal()).isEqualTo(1f);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
index 7fa1dbe..a00cabc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
@@ -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 com.android.systemui.keyguard;
@@ -24,6 +24,8 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -41,10 +43,11 @@
private DismissCallbackRegistry mDismissCallbackRegistry;
private @Mock IKeyguardDismissCallback mMockCallback;
private @Mock IKeyguardDismissCallback mMockCallback2;
+ private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setUp() throws Exception {
- mDismissCallbackRegistry = new DismissCallbackRegistry();
+ mDismissCallbackRegistry = new DismissCallbackRegistry(mUiBgExecutor);
MockitoAnnotations.initMocks(this);
}
@@ -52,7 +55,7 @@
public void testCancelled() throws Exception {
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.notifyDismissCancelled();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mMockCallback).onDismissCancelled();
}
@@ -61,7 +64,7 @@
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.addCallback(mMockCallback2);
mDismissCallbackRegistry.notifyDismissCancelled();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mMockCallback).onDismissCancelled();
verify(mMockCallback2).onDismissCancelled();
}
@@ -70,7 +73,7 @@
public void testSucceeded() throws Exception {
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.notifyDismissSucceeded();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mMockCallback).onDismissSucceeded();
}
@@ -79,7 +82,7 @@
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.addCallback(mMockCallback2);
mDismissCallbackRegistry.notifyDismissSucceeded();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mMockCallback).onDismissSucceeded();
verify(mMockCallback2).onDismissSucceeded();
}
@@ -89,7 +92,7 @@
mDismissCallbackRegistry.addCallback(mMockCallback);
mDismissCallbackRegistry.notifyDismissSucceeded();
mDismissCallbackRegistry.notifyDismissSucceeded();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mMockCallback, times(1)).onDismissSucceeded();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index cbfcfdd..64fbc1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -38,6 +38,8 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -58,6 +60,7 @@
private @Mock StatusBarWindowController mStatusBarWindowController;
private @Mock BroadcastDispatcher mBroadcastDispatcher;
private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
+ private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private FalsingManagerFake mFalsingManager;
@@ -68,15 +71,14 @@
mDependency.injectTestDependency(FalsingManager.class, mFalsingManager);
mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mUpdateMonitor);
- mDependency.injectTestDependency(StatusBarWindowController.class,
- mStatusBarWindowController);
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
TestableLooper.get(this).runWithLooper(() -> {
mViewMediator = new KeyguardViewMediator(
mContext, mFalsingManager, mLockPatternUtils, mBroadcastDispatcher,
- () -> mStatusBarKeyguardViewManager, mDismissCallbackRegistry);
+ mStatusBarWindowController, () -> mStatusBarKeyguardViewManager,
+ mDismissCallbackRegistry, mUiBgExecutor);
});
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
index 2f90641..4a90bb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
@@ -57,7 +57,7 @@
class TestableRichEvent extends RichEvent {
TestableRichEvent(int logLevel, int type, String reason) {
- super(logLevel, type, reason);
+ init(logLevel, type, reason);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
index 378bba1..e7b317e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
@@ -35,11 +35,12 @@
@RunWith(AndroidTestingRunner.class)
public class SysuiLogTest extends SysuiTestCase {
private static final String TEST_ID = "TestLogger";
+ private static final String TEST_MSG = "msg";
private static final int MAX_LOGS = 5;
@Mock
private DumpController mDumpController;
- private SysuiLog mSysuiLog;
+ private SysuiLog<Event> mSysuiLog;
@Before
public void setup() {
@@ -48,35 +49,63 @@
@Test
public void testLogDisabled_noLogsWritten() {
- mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, false);
- assertEquals(mSysuiLog.mTimeline, null);
+ mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, false);
+ assertEquals(null, mSysuiLog.mTimeline);
- mSysuiLog.log(new Event("msg"));
- assertEquals(mSysuiLog.mTimeline, null);
+ mSysuiLog.log(createEvent(TEST_MSG));
+ assertEquals(null, mSysuiLog.mTimeline);
}
@Test
public void testLogEnabled_logWritten() {
- mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
- assertEquals(mSysuiLog.mTimeline.size(), 0);
+ mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
+ assertEquals(0, mSysuiLog.mTimeline.size());
- mSysuiLog.log(new Event("msg"));
- assertEquals(mSysuiLog.mTimeline.size(), 1);
+ mSysuiLog.log(createEvent(TEST_MSG));
+ assertEquals(1, mSysuiLog.mTimeline.size());
}
@Test
public void testMaxLogs() {
- mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
+ mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
assertEquals(mSysuiLog.mTimeline.size(), 0);
- final String msg = "msg";
for (int i = 0; i < MAX_LOGS + 1; i++) {
- mSysuiLog.log(new Event(msg + i));
+ mSysuiLog.log(createEvent(TEST_MSG + i));
}
- assertEquals(mSysuiLog.mTimeline.size(), MAX_LOGS);
+ assertEquals(MAX_LOGS, mSysuiLog.mTimeline.size());
- // check the first message (msg0) is deleted:
- assertEquals(mSysuiLog.mTimeline.getFirst().getMessage(), msg + "1");
+ // check the first message (msg0) was replaced with msg1:
+ assertEquals(TEST_MSG + "1", mSysuiLog.mTimeline.getFirst().getMessage());
+ }
+
+ @Test
+ public void testRecycleLogs() {
+ // GIVEN a SysuiLog with one log
+ mSysuiLog = new TestSysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
+ Event e = createEvent(TEST_MSG); // msg
+ mSysuiLog.log(e); // Logs: [msg]
+
+ Event recycledEvent = null;
+ // WHEN we add MAX_LOGS after the first log
+ for (int i = 0; i < MAX_LOGS; i++) {
+ recycledEvent = mSysuiLog.log(createEvent(TEST_MSG + i));
+ }
+ // Logs: [msg1, msg2, msg3, msg4]
+
+ // THEN we see the recycledEvent is e
+ assertEquals(e, recycledEvent);
+ }
+
+ private Event createEvent(String msg) {
+ return new Event().init(msg);
+ }
+
+ public class TestSysuiLog extends SysuiLog<Event> {
+ protected TestSysuiLog(DumpController dumpController, String id, int maxLogs,
+ boolean enabled) {
+ super(dumpController, id, maxLogs, enabled, false);
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 8004562..b7e9f07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -31,6 +31,7 @@
import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
+import androidx.test.filters.Suppress;
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.CarrierText;
@@ -64,7 +65,7 @@
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
-@Ignore
+@Suppress
public class QSFragmentTest extends SysuiBaseFragmentTest {
private MetricsLogger mMockMetricsLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 39ce8c1..8a9a7a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -120,7 +120,6 @@
).when(mQSTileHost).createTile(anyString());
FakeSystemClock clock = new FakeSystemClock();
- clock.setAutoIncrement(false);
mMainExecutor = new FakeExecutor(clock);
mBgExecutor = new FakeExecutor(clock);
mTileQueryHelper = new TileQueryHelper(mContext, mMainExecutor, mBgExecutor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 192d8f8..6bd24b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -47,6 +47,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
@@ -83,6 +84,7 @@
String spec = "spec";
mTestableLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mDependency.injectMockDependency(ActivityStarter.class);
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mStatusBarStateController =
mDependency.injectMockDependency(StatusBarStateController.class);
@@ -188,6 +190,16 @@
verify(mTile).handleSetListening(eq(false));
}
+ @Test
+ public void testHandleDestroyClearsHandlerQueue() {
+ when(mTile.getStaleTimeout()).thenReturn(0L);
+ mTile.handleRefreshState(null); // this will add a delayed H.STALE message
+ mTile.handleDestroy();
+
+ mTestableLooper.processAllMessages();
+ verify(mTile, never()).handleStale();
+ }
+
private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
private final int mCategory;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 6d85d37..2c7cee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.screenshot;
-import static android.content.Context.NOTIFICATION_SERVICE;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -29,7 +27,6 @@
import static org.mockito.Mockito.when;
import android.app.Notification;
-import android.app.NotificationManager;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -42,7 +39,6 @@
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.NotificationChannels;
import org.junit.Assert;
import org.junit.Before;
@@ -84,7 +80,7 @@
eq(false))).thenThrow(
RuntimeException.class);
CompletableFuture<List<Notification.Action>> smartActionsFuture =
- GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
smartActionsProvider, true, false);
Assert.assertNotNull(smartActionsFuture);
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
@@ -101,7 +97,7 @@
int timeoutMs = 1000;
when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
RuntimeException.class);
- List<Notification.Action> actions = GlobalScreenshot.getSmartActions(
+ List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
"", smartActionsFuture, timeoutMs, mSmartActionsProvider);
Assert.assertEquals(Collections.emptyList(), actions);
}
@@ -112,7 +108,7 @@
throws Exception {
doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(),
anyLong());
- GlobalScreenshot.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
+ ScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
}
// Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
@@ -123,7 +119,7 @@
Bitmap bitmap = mock(Bitmap.class);
when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
CompletableFuture<List<Notification.Action>> smartActionsFuture =
- GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
mSmartActionsProvider, true, true);
verify(mSmartActionsProvider, never()).getActions(any(), any(), any(),
eq(false));
@@ -137,7 +133,7 @@
public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
Bitmap bitmap = mock(Bitmap.class);
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
- GlobalScreenshot.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
+ ScreenshotSmartActions.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
true, true);
verify(mSmartActionsProvider, times(1))
.getActions(any(), any(), any(), eq(true));
@@ -153,7 +149,7 @@
SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
mContext, null, mHandler);
CompletableFuture<List<Notification.Action>> smartActionsFuture =
- GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
actionsProvider,
true, true);
Assert.assertNotNull(smartActionsFuture);
@@ -167,31 +163,23 @@
if (Looper.myLooper() == null) {
Looper.prepare();
}
- NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
+
GlobalScreenshot.SaveImageInBackgroundData
data = new GlobalScreenshot.SaveImageInBackgroundData();
- data.context = mContext;
data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
- data.iconSize = 10;
data.finisher = null;
data.mActionsReadyListener = null;
- data.previewWidth = 10;
- data.previewheight = 10;
- SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data,
- notificationManager);
- Notification.Builder notificationBuilder = new Notification.Builder(mContext,
- NotificationChannels.SCREENSHOTS_HEADSUP);
- task.populateNotificationActions(mContext, mContext.getResources(),
+ SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+ List<Notification.Action> actions = task.populateNotificationActions(
+ mContext, mContext.getResources(),
Uri.parse("Screenshot_123.png"),
- CompletableFuture.completedFuture(Collections.emptyList()), notificationBuilder);
+ CompletableFuture.completedFuture(Collections.emptyList()));
- Notification notification = notificationBuilder.build();
- Assert.assertEquals(notification.actions.length, 3);
+ Assert.assertEquals(actions.size(), 3);
boolean isShareFound = false;
boolean isEditFound = false;
boolean isDeleteFound = false;
- for (Notification.Action action : notification.actions) {
+ for (Notification.Action action : actions) {
Intent intent = action.actionIntent.getIntent();
Assert.assertNotNull(intent);
Bundle bundle = intent.getExtras();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index c6dd232..a5395e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
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 org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
index 9b860c9..677a6fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 8e6f4d7..d580234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -27,7 +27,8 @@
import android.app.NotificationManager;
import android.os.Handler;
import android.os.UserHandle;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -35,8 +36,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
import org.junit.Before;
import org.junit.Test;
@@ -51,52 +51,39 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
- @Mock private NotificationListenerService.RankingMap mRanking;
-
- // Dependency mocks:
- @Mock private NotificationEntryManager mEntryManager;
+ @Mock private NotifServiceListener mServiceListener;
@Mock private NotificationManager mNotificationManager;
- @Mock private NotificationGroupManager mNotificationGroupManager;
private NotificationListener mListener;
private StatusBarNotification mSbn;
+ private RankingMap mRanking = new RankingMap(new Ranking[0]);
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
- mListener = new NotificationListener(mContext,
- new Handler(TestableLooper.get(this).getLooper()), mEntryManager,
- mNotificationGroupManager);
+ mListener = new NotificationListener(
+ mContext,
+ mNotificationManager,
+ new Handler(TestableLooper.get(this).getLooper()));
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
+
+ mListener.addNotificationListener(mServiceListener);
}
@Test
public void testNotificationAddCallsAddNotification() {
mListener.onNotificationPosted(mSbn, mRanking);
TestableLooper.get(this).processAllMessages();
- verify(mEntryManager).addNotification(mSbn, mRanking);
- }
-
- @Test
- public void testNotificationUpdateCallsUpdateNotification() {
- when(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()))
- .thenReturn(
- new NotificationEntryBuilder()
- .setSbn(mSbn)
- .build());
- mListener.onNotificationPosted(mSbn, mRanking);
- TestableLooper.get(this).processAllMessages();
- verify(mEntryManager).updateNotification(mSbn, mRanking);
+ verify(mServiceListener).onNotificationPosted(mSbn, mRanking);
}
@Test
public void testNotificationRemovalCallsRemoveNotification() {
mListener.onNotificationRemoved(mSbn, mRanking);
TestableLooper.get(this).processAllMessages();
- verify(mEntryManager).removeNotification(eq(mSbn.getKey()), eq(mRanking), anyInt());
+ verify(mServiceListener).onNotificationRemoved(eq(mSbn), eq(mRanking), anyInt());
}
@Test
@@ -104,7 +91,7 @@
mListener.onNotificationRankingUpdate(mRanking);
TestableLooper.get(this).processAllMessages();
// RankingMap may be modified by plugins.
- verify(mEntryManager).updateNotificationRanking(any());
+ verify(mServiceListener).onNotificationRankingUpdate(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index ba7b2e2..3a6acce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -26,12 +26,14 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.KeyguardManager;
+import android.app.Notification;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -54,6 +56,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -198,11 +201,13 @@
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
+ final Notification notification = mock(Notification.class);
+ when(notification.isForegroundService()).thenReturn(true);
NotificationEntry entry = new NotificationEntryBuilder()
.setImportance(IMPORTANCE_LOW)
+ .setNotification(notification)
.build();
entry.setBucket(BUCKET_SILENT);
- entry.setIsHighPriority(true);
assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 8aac189..64b10c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager.SmartReplyHistoryExtender;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
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.phone.StatusBar;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
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 88546b9..c7810be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -44,6 +44,7 @@
import com.android.systemui.bubbles.BubblesTestActivity;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
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.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.NotificationContentInflaterTest;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 46a8dad..1b05216 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -47,13 +47,13 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
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.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
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.util.Assert;
import com.google.android.collect.Lists;
@@ -79,7 +79,6 @@
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationGroupManager mGroupManager;
@Mock private VisualStabilityManager mVisualStabilityManager;
- @Mock private ShadeController mShadeController;
private TestableLooper mTestableLooper;
private Handler mHandler;
@@ -99,14 +98,12 @@
mLockscreenUserManager);
mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
- mDependency.injectTestDependency(ShadeController.class, mShadeController);
mHelper = new NotificationTestHelper(mContext, mDependency);
mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
mock(StatusBarStateControllerImpl.class), mEntryManager,
- () -> mShadeController,
mock(KeyguardBypassController.class),
mock(BubbleController.class),
mock(DynamicPrivacyController.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index d003b99..5310dd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -76,6 +76,7 @@
mSmartActions = copyList(ranking.getSmartActions());
mSmartReplies = copyList(ranking.getSmartReplies());
mCanBubble = ranking.canBubble();
+ mIsVisuallyInterruptive = ranking.visuallyInterruptive();
}
public Ranking build() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 3f62412..22dc080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
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 e0dfe7e..7f5105e 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
@@ -66,7 +66,6 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -80,6 +79,7 @@
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
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;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 7fabb0f..5aed61b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -41,11 +41,11 @@
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
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.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 133d52b4..7343e5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -30,9 +30,9 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index 6a4ddc7..9079223 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -31,9 +31,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationPresenter;
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 org.junit.Before;
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
new file mode 100644
index 0000000..a06d6c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index e1beb34..0dcd253 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
@@ -48,7 +48,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
import com.android.systemui.statusbar.RankingBuilder;
@@ -99,7 +98,7 @@
// Capture the listener object that the collection registers with the listener service so
// we can simulate listener service events in tests below
- verify(mListenerService).setDownstreamListener(mListenerCaptor.capture());
+ verify(mListenerService).addNotificationListener(mListenerCaptor.capture());
mServiceListener = checkNotNull(mListenerCaptor.getValue());
mNoMan = new NoManSimulator(mServiceListener);
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/NotifListBuilderImplTest.java
index a25af84..6e9c2c8 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/NotifListBuilderImplTest.java
@@ -28,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.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -38,7 +39,6 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
@@ -47,6 +47,7 @@
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.logging.NotifLog;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.FakeSystemClock;
@@ -76,6 +77,7 @@
private NotifListBuilderImpl mListBuilder;
private FakeSystemClock mSystemClock = new FakeSystemClock();
+ @Mock private NotifLog mNotifLog;
@Mock private NotifCollection mNotifCollection;
@Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener;
@Spy private OnBeforeSortListener mOnBeforeSortListener;
@@ -97,7 +99,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mListBuilder = new NotifListBuilderImpl(mSystemClock);
+ mListBuilder = new NotifListBuilderImpl(mSystemClock, mNotifLog);
mListBuilder.setOnRenderListListener(mOnRenderListListener);
mListBuilder.attach(mNotifCollection);
@@ -334,7 +336,7 @@
// GIVEN a notification that is initially added to the list
PackageFilter filter = new PackageFilter(PACKAGE_2);
filter.setEnabled(false);
- mListBuilder.addFilter(filter);
+ mListBuilder.addPreGroupFilter(filter);
addNotif(0, PACKAGE_1);
addNotif(1, PACKAGE_2);
@@ -371,24 +373,54 @@
notif(2)
);
- // THEN the list of newly visible entries doesn't contain the summary or the group
- assertEquals(
- Arrays.asList(
- mEntrySet.get(0),
- mEntrySet.get(2)),
- listener.newlyVisibleEntries
- );
-
// THEN the summary has a null parent and an unset firstAddedIteration
assertNull(mEntrySet.get(1).getParent());
assertEquals(-1, mEntrySet.get(1).mFirstAddedIteration);
}
@Test
- public void testNotifsAreFiltered() {
+ public void testPreGroupNotifsAreFiltered() {
+ // GIVEN a PreGroupNotifFilter and PreRenderFilter that filters out the same package
+ NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_2));
+ NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_2));
+ mListBuilder.addPreGroupFilter(preGroupFilter);
+ mListBuilder.addPreRenderFilter(preRenderFilter);
+
+ // WHEN the pipeline is kicked off on a list of notifs
+ addNotif(0, PACKAGE_1);
+ addNotif(1, PACKAGE_2);
+ addNotif(2, PACKAGE_3);
+ addNotif(3, PACKAGE_2);
+ dispatchBuild();
+
+ // THEN the preGroupFilter is called on each notif in the original set
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+ verify(preGroupFilter).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+ // THEN the preRenderFilter is only called on the notifications not already filtered out
+ verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(0)), anyLong());
+ verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(1)), anyLong());
+ verify(preRenderFilter).shouldFilterOut(eq(mEntrySet.get(2)), anyLong());
+ verify(preRenderFilter, never()).shouldFilterOut(eq(mEntrySet.get(3)), anyLong());
+
+ // THEN the final list doesn't contain any filtered-out notifs
+ verifyBuiltList(
+ notif(0),
+ notif(2)
+ );
+
+ // THEN each filtered notif records the NotifFilter that did it
+ assertEquals(preGroupFilter, mEntrySet.get(1).mExcludingFilter);
+ assertEquals(preGroupFilter, mEntrySet.get(3).mExcludingFilter);
+ }
+
+ @Test
+ public void testPreRenderNotifsAreFiltered() {
// GIVEN a NotifFilter that filters out a specific package
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
- mListBuilder.addFilter(filter1);
+ mListBuilder.addPreRenderFilter(filter1);
// WHEN the pipeline is kicked off on a list of notifs
addNotif(0, PACKAGE_1);
@@ -419,8 +451,8 @@
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
- mListBuilder.addFilter(filter1);
- mListBuilder.addFilter(filter2);
+ mListBuilder.addPreGroupFilter(filter1);
+ mListBuilder.addPreGroupFilter(filter2);
// WHEN the pipeline is kicked off on a list of notifs
addNotif(0, PACKAGE_1);
@@ -520,7 +552,7 @@
public void testNotifsAreSectioned() {
// GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
// notifs based on package name
- mListBuilder.addFilter(new PackageFilter(PACKAGE_4));
+ mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
final SectionsProvider sectionsProvider = spy(new PackageSectioner());
mListBuilder.setSectionsProvider(sectionsProvider);
@@ -594,17 +626,19 @@
@Test
public void testListenersAndPluggablesAreFiredInOrder() {
// GIVEN a bunch of registered listeners and pluggables
- NotifFilter filter = spy(new PackageFilter(PACKAGE_1));
+ NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
NotifPromoter promoter = spy(new IdPromoter(3));
PackageSectioner sectioner = spy(new PackageSectioner());
NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
- mListBuilder.addFilter(filter);
+ NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
+ mListBuilder.addPreGroupFilter(preGroupFilter);
mListBuilder.addOnBeforeTransformGroupsListener(mOnBeforeTransformGroupsListener);
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
mListBuilder.setComparators(Collections.singletonList(comparator));
mListBuilder.setSectionsProvider(sectioner);
mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
+ mListBuilder.addPreRenderFilter(preRenderFilter);
// WHEN a few new notifs are added
addNotif(0, PACKAGE_1);
@@ -618,25 +652,28 @@
// THEN the pluggables and listeners are called in order
InOrder inOrder = inOrder(
- filter,
+ preGroupFilter,
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
sectioner,
comparator,
+ preRenderFilter,
mOnBeforeRenderListListener,
mOnRenderListListener);
- inOrder.verify(filter, atLeastOnce())
+ inOrder.verify(preGroupFilter, atLeastOnce())
.shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeTransformGroupsListener)
- .onBeforeTransformGroups(anyList(), anyList());
+ .onBeforeTransformGroups(anyList());
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
+ inOrder.verify(preRenderFilter, atLeastOnce())
+ .shouldFilterOut(any(NotificationEntry.class), anyLong());
inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList());
inOrder.verify(mOnRenderListListener).onRenderList(anyList());
}
@@ -649,7 +686,7 @@
SectionsProvider sectionsProvider = new PackageSectioner();
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
- mListBuilder.addFilter(packageFilter);
+ mListBuilder.addPreGroupFilter(packageFilter);
mListBuilder.addPromoter(idPromoter);
mListBuilder.setSectionsProvider(sectionsProvider);
mListBuilder.setComparators(Collections.singletonList(hypeComparator));
@@ -685,13 +722,12 @@
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_5));
NotifFilter filter2 = spy(new PackageFilter(PACKAGE_5));
NotifFilter filter3 = spy(new PackageFilter(PACKAGE_5));
- mListBuilder.addFilter(filter1);
- mListBuilder.addFilter(filter2);
- mListBuilder.addFilter(filter3);
+ mListBuilder.addPreGroupFilter(filter1);
+ mListBuilder.addPreGroupFilter(filter2);
+ mListBuilder.addPreGroupFilter(filter3);
// GIVEN the SystemClock is set to a particular time:
- mSystemClock.setAutoIncrement(true);
- mSystemClock.setUptimeMillis(47);
+ mSystemClock.setUptimeMillis(10047);
// WHEN the pipeline is kicked off on a list of notifs
addNotif(0, PACKAGE_1);
@@ -699,22 +735,22 @@
dispatchBuild();
// THEN the value of `now` is the same for all calls to shouldFilterOut
- verify(filter1).shouldFilterOut(mEntrySet.get(0), 47);
- verify(filter2).shouldFilterOut(mEntrySet.get(0), 47);
- verify(filter3).shouldFilterOut(mEntrySet.get(0), 47);
- verify(filter1).shouldFilterOut(mEntrySet.get(1), 47);
- verify(filter2).shouldFilterOut(mEntrySet.get(1), 47);
- verify(filter3).shouldFilterOut(mEntrySet.get(1), 47);
+ verify(filter1).shouldFilterOut(mEntrySet.get(0), 10047);
+ verify(filter2).shouldFilterOut(mEntrySet.get(0), 10047);
+ verify(filter3).shouldFilterOut(mEntrySet.get(0), 10047);
+ verify(filter1).shouldFilterOut(mEntrySet.get(1), 10047);
+ verify(filter2).shouldFilterOut(mEntrySet.get(1), 10047);
+ verify(filter3).shouldFilterOut(mEntrySet.get(1), 10047);
}
@Test
- public void testNewlyAddedEntries() {
+ public void testGroupTransformEntries() {
// GIVEN a registered OnBeforeTransformGroupsListener
RecordingOnBeforeTransformGroupsListener listener =
spy(new RecordingOnBeforeTransformGroupsListener());
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // Given some new notifs
+ // GIVEN some new notifs
addNotif(0, PACKAGE_1);
addGroupChild(1, PACKAGE_2, GROUP_1);
addGroupSummary(2, PACKAGE_2, GROUP_1);
@@ -742,27 +778,18 @@
mEntrySet.get(0),
mBuiltList.get(1),
mEntrySet.get(4)
- ),
- Arrays.asList(
- mEntrySet.get(0),
- mEntrySet.get(1),
- mBuiltList.get(1),
- mEntrySet.get(2),
- mEntrySet.get(3),
- mEntrySet.get(4),
- mEntrySet.get(5)
)
);
}
@Test
- public void testNewlyAddedEntriesOnSecondRun() {
+ public void testGroupTransformEntriesOnSecondRun() {
// GIVEN a registered OnBeforeTransformGroupsListener
RecordingOnBeforeTransformGroupsListener listener =
spy(new RecordingOnBeforeTransformGroupsListener());
mListBuilder.addOnBeforeTransformGroupsListener(listener);
- // Given some notifs that have already been added (two of which are in malformed groups)
+ // GIVEN some notifs that have already been added (two of which are in malformed groups)
addNotif(0, PACKAGE_1);
addGroupChild(1, PACKAGE_2, GROUP_1);
addGroupChild(2, PACKAGE_3, GROUP_2);
@@ -798,13 +825,6 @@
mEntrySet.get(1),
mBuiltList.get(2),
mEntrySet.get(7)
- ),
- Arrays.asList(
- mBuiltList.get(2),
- mEntrySet.get(4),
- mEntrySet.get(5),
- mEntrySet.get(6),
- mEntrySet.get(7)
)
);
}
@@ -841,19 +861,18 @@
}
@Test(expected = IllegalStateException.class)
- public void testOutOfOrderFilterInvalidationThrows() {
- // GIVEN a NotifFilter that gets invalidated during the grouping stage
+ public void testOutOfOrderPreGroupFilterInvalidationThrows() {
+ // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeTransformGroupsListener listener =
- (list, newlyVisibleEntries) -> filter.invalidateList();
- mListBuilder.addFilter(filter);
+ OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
// WHEN we try to run the pipeline and the filter is invalidated
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
}
@Test(expected = IllegalStateException.class)
@@ -869,7 +888,7 @@
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
}
@Test(expected = IllegalStateException.class)
@@ -885,7 +904,37 @@
addNotif(0, PACKAGE_1);
dispatchBuild();
- // Then an exception is thrown
+ // THEN an exception is thrown
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOutOfOrderPreRenderFilterInvalidationThrows() {
+ // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
+ NotifFilter filter = new PackageFilter(PACKAGE_5);
+ OnBeforeRenderListListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreRenderFilter(filter);
+ mListBuilder.addOnBeforeRenderListListener(listener);
+
+ // WHEN we try to run the pipeline and the PreRenderFilter is invalidated
+ addNotif(0, PACKAGE_1);
+ dispatchBuild();
+
+ // THEN an exception is thrown
+ }
+
+ @Test
+ public void testInOrderPreRenderFilter() {
+ // GIVEN a PreRenderFilter that gets invalidated during the grouping stage
+ NotifFilter filter = new PackageFilter(PACKAGE_5);
+ OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+ mListBuilder.addPreRenderFilter(filter);
+ mListBuilder.addOnBeforeTransformGroupsListener(listener);
+
+ // WHEN we try to run the pipeline and the filter is invalidated
+ addNotif(0, PACKAGE_1);
+ dispatchBuild();
+
+ // THEN no exception thrown
}
/**
@@ -1177,13 +1226,9 @@
private static class RecordingOnBeforeTransformGroupsListener
implements OnBeforeTransformGroupsListener {
- public List<ListEntry> newlyVisibleEntries;
@Override
- public void onBeforeTransformGroups(List<ListEntry> list,
- List<ListEntry> newlyVisibleEntries) {
- this.newlyVisibleEntries = newlyVisibleEntries;
- }
+ public void onBeforeTransformGroups(List<ListEntry> list) { }
}
private static final String PACKAGE_1 = "com.test1";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index d18b16b..e6a61d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -14,18 +14,19 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification.collection;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager.Importance;
+import android.app.NotificationManager;
import android.content.Context;
import android.os.UserHandle;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.SbnBuilder;
import java.util.ArrayList;
@@ -34,6 +35,8 @@
* and Ranking. Is largely a proxy for the SBN and Ranking builders, but does a little extra magic
* to make sure the keys match between the two, etc.
*
+ * Has the ability to set ListEntry properties as well.
+ *
* Only for use in tests.
*/
public class NotificationEntryBuilder {
@@ -41,10 +44,35 @@
private final RankingBuilder mRankingBuilder = new RankingBuilder();
private StatusBarNotification mSbn = null;
+ /* ListEntry properties */
+ private GroupEntry mParent;
+ private int mSection;
+
public NotificationEntry build() {
StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build();
mRankingBuilder.setKey(sbn.getKey());
- return new NotificationEntry(sbn, mRankingBuilder.build());
+ final NotificationEntry entry = new NotificationEntry(sbn, mRankingBuilder.build());
+
+ /* ListEntry properties */
+ entry.setParent(mParent);
+ entry.setSection(mSection);
+ return entry;
+ }
+
+ /**
+ * Sets the parent.
+ */
+ public NotificationEntryBuilder setParent(@Nullable GroupEntry parent) {
+ mParent = parent;
+ return this;
+ }
+
+ /**
+ * Sets the section.
+ */
+ public NotificationEntryBuilder setSection(int section) {
+ mSection = section;
+ return this;
}
/**
@@ -176,7 +204,7 @@
return this;
}
- public NotificationEntryBuilder setImportance(@Importance int importance) {
+ public NotificationEntryBuilder setImportance(@NotificationManager.Importance int importance) {
mRankingBuilder.setImportance(importance);
return this;
}
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 536aeb4..39ae68a 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,6 +21,8 @@
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;
@@ -50,7 +52,6 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
@@ -91,6 +92,44 @@
}
@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 cda1538e..10450fa 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,19 +17,13 @@
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
import android.service.notification.NotificationListenerService.RankingMap
-import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
-
-import org.junit.runner.RunWith
-
import androidx.test.filters.SmallTest
-
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.NotificationEntryBuilder
import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationFilter
@@ -42,12 +36,9 @@
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Lazy
import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
-
import org.junit.Before
import org.junit.Test
-import org.mockito.Mockito.`when`
+import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@SmallTest
@@ -76,106 +67,34 @@
}
@Test
- fun testPeopleNotification_isHighPriority() {
- val notification = Notification.Builder(mContext, "test")
- .build()
-
- val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
- notification, mContext.user, "", 0)
-
- `when`(personNotificationIdentifier.isPeopleNotification(sbn)).thenReturn(true)
-
- val e = NotificationEntryBuilder()
- .setNotification(notification)
- .setSbn(sbn)
- .build()
-
- assertTrue(rankingManager.isHighPriority2(e))
- }
-
- @Test
- fun lowForegroundHighPriority() {
- val notification = mock(Notification::class.java)
- `when`(notification.isForegroundService).thenReturn(true)
-
- val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
- notification, mContext.user, "", 0)
-
- val e = NotificationEntryBuilder()
- .setNotification(notification)
- .setSbn(sbn)
- .build()
-
- modifyRanking(e)
- .setImportance(IMPORTANCE_LOW)
- .build()
-
- assertTrue(rankingManager.isHighPriority2(e))
- }
-
- @Test
- fun userChangeTrumpsHighPriorityCharacteristics() {
- val notification = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
- .build()
-
- val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
- notification, mContext.user, "", 0)
-
- `when`(personNotificationIdentifier.isPeopleNotification(sbn)).thenReturn(true)
-
- val channel = NotificationChannel("a", "a", IMPORTANCE_LOW)
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE)
-
- val e = NotificationEntryBuilder()
- .setSbn(sbn)
- .setChannel(channel)
- .build()
-
- assertFalse(rankingManager.isHighPriority2(e))
- }
-
- @Test
fun testSort_highPriorityTrumpsNMSRank() {
// NMS rank says A and then B. But A is not high priority and B is, so B should sort in
// front
- val aN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
val a = NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_LOW) // low priority
+ .setRank(1) // NMS says rank first
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
- .setNotification(aN)
+ .setNotification(
+ Notification.Builder(mContext, "test")
+ .build())
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
- a.setIsHighPriority(false)
-
- modifyRanking(a)
- .setImportance(IMPORTANCE_LOW)
- .setRank(1)
- .build()
-
- val bN = Notification.Builder(mContext, "test")
- .setStyle(Notification.MessagingStyle(""))
- .build()
val b = NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH) // high priority
+ .setRank(2) // NMS says rank second
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
- .setNotification(bN)
+ .setNotification(
+ Notification.Builder(mContext, "test")
+ .build())
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
- b.setIsHighPriority(true)
-
- modifyRanking(b)
- .setImportance(IMPORTANCE_LOW)
- .setRank(2)
- .build()
assertEquals(
listOf(b, a),
@@ -189,6 +108,8 @@
.setStyle(Notification.MessagingStyle(""))
.build()
val a = NotificationEntryBuilder()
+ .setRank(1)
+ .setImportance(IMPORTANCE_HIGH)
.setPkg("pkg")
.setOpPkg("pkg")
.setTag("tag")
@@ -196,17 +117,13 @@
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
- a.setIsHighPriority(false)
-
- modifyRanking(a)
- .setImportance(IMPORTANCE_LOW)
- .setRank(1)
- .build()
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
.build()
val b = NotificationEntryBuilder()
+ .setRank(2)
+ .setImportance(IMPORTANCE_HIGH)
.setPkg("pkg2")
.setOpPkg("pkg2")
.setTag("tag")
@@ -214,12 +131,6 @@
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
- b.setIsHighPriority(false)
-
- modifyRanking(b)
- .setImportance(IMPORTANCE_LOW)
- .setRank(2)
- .build()
assertEquals(
listOf(a, b),
@@ -239,7 +150,7 @@
.setOverrideGroupKey("")
.build()
- modifyRanking(e).setImportance(IMPORTANCE_DEFAULT) .build()
+ modifyRanking(e).setImportance(IMPORTANCE_DEFAULT).build()
rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
assertEquals(e.bucket, BUCKET_ALERTING)
@@ -281,11 +192,6 @@
sectionsFeatureManager,
peopleNotificationIdentifier
) {
-
- fun isHighPriority2(e: NotificationEntry): Boolean {
- return isHighPriority(e)
- }
-
fun applyTestRankingMap(r: RankingMap) {
rankingMap = r
}
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
new file mode 100644
index 0000000..ea6c70a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.coordinator;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.app.Notification;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+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 org.mockito.MockitoSession;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DeviceProvisionedCoordinatorTest extends SysuiTestCase {
+ private static final int NOTIF_UID = 0;
+
+ private static final String SHOW_WHEN_UNPROVISIONED_FLAG =
+ Notification.EXTRA_ALLOW_DURING_SETUP;
+ private static final String SETUP_NOTIF_PERMISSION =
+ Manifest.permission.NOTIFICATION_DURING_SETUP;
+
+ private MockitoSession mMockitoSession;
+
+ @Mock private ActivityManagerInternal mActivityMangerInternal;
+ @Mock private IPackageManager mIPackageManager;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
+ @Mock private NotifListBuilderImpl mNotifListBuilder;
+ private Notification mNotification;
+ private NotificationEntry mEntry;
+ private DeviceProvisionedCoordinator mDeviceProvisionedCoordinator;
+ private NotifFilter mDeviceProvisionedFilter;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mDeviceProvisionedCoordinator = new DeviceProvisionedCoordinator(
+ mDeviceProvisionedController, mIPackageManager);
+
+ mNotification = new Notification();
+ mEntry = new NotificationEntryBuilder()
+ .setNotification(mNotification)
+ .setUid(NOTIF_UID)
+ .build();
+
+ ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+ mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder);
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+ mDeviceProvisionedFilter = filterCaptor.getValue();
+ }
+
+ @Test
+ public void deviceProvisioned() {
+ // GIVEN device is provisioned
+ when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
+
+ // THEN don't filter out the notification
+ assertFalse(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void deviceUnprovisioned() {
+ // GIVEN device is unprovisioned
+ when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
+
+ // THEN filter out the notification
+ assertTrue(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void deviceUnprovisionedCanBypass() throws RemoteException {
+ // GIVEN device is unprovisioned
+ when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
+
+ // GIVEN notification has a flag to allow the notification during setup
+ Bundle extras = new Bundle();
+ extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
+ mNotification.extras = extras;
+
+ // GIVEN notification has the permission to display during setup
+ when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ // THEN don't filter out the notification
+ assertFalse(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void deviceUnprovisionedTryBypassWithoutPermission() throws RemoteException {
+ // GIVEN device is unprovisioned
+ when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
+
+ // GIVEN notification has a flag to allow the notification during setup
+ Bundle extras = new Bundle();
+ extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
+ mNotification.extras = extras;
+
+ // GIVEN notification does NOT have permission to display during setup
+ when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
+ // THEN filter out the notification
+ assertTrue(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ private RankingBuilder getRankingForUnfilteredNotif() {
+ return new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setSuppressedVisualEffects(0)
+ .setSuspended(false);
+ }
+}
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
new file mode 100644
index 0000000..01bca0d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+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.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+
+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 ForegroundCoordinatorTest extends SysuiTestCase {
+ private static final String TEST_PKG = "test_pkg";
+ private static final int NOTIF_USER_ID = 0;
+
+ @Mock private Handler mMainHandler;
+ @Mock private ForegroundServiceController mForegroundServiceController;
+ @Mock private AppOpsController mAppOpsController;
+ @Mock private NotifListBuilderImpl mNotifListBuilder;
+ @Mock private NotifCollection mNotifCollection;
+
+ private NotificationEntry mEntry;
+ private Notification mNotification;
+ private ForegroundCoordinator mForegroundCoordinator;
+ private NotifFilter mForegroundFilter;
+ private NotifLifetimeExtender mForegroundNotifLifetimeExtender;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mForegroundCoordinator = new ForegroundCoordinator(
+ mForegroundServiceController, mAppOpsController, mMainHandler);
+
+ mNotification = new Notification();
+ mEntry = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setNotification(mNotification)
+ .build();
+
+ ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+ ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor =
+ ArgumentCaptor.forClass(NotifLifetimeExtender.class);
+
+ mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder);
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+ verify(mNotifCollection, times(1)).addNotificationLifetimeExtender(
+ lifetimeExtenderCaptor.capture());
+
+ mForegroundFilter = filterCaptor.getValue();
+ mForegroundNotifLifetimeExtender = lifetimeExtenderCaptor.getValue();
+ }
+
+ @Test
+ public void filterTest_disclosureUnnecessary() {
+ StatusBarNotification sbn = mEntry.getSbn();
+
+ // GIVEN the notification is a disclosure notification
+ when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
+
+ // GIVEN the disclosure isn't needed for this user
+ when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
+ .thenReturn(false);
+
+ // THEN filter out the notification
+ assertTrue(mForegroundFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void filterTest_systemAlertNotificationUnnecessary() {
+ StatusBarNotification sbn = mEntry.getSbn();
+
+ // GIVEN the notification is a system alert notification + not a disclosure notification
+ when(mForegroundServiceController.isSystemAlertNotification(sbn)).thenReturn(true);
+ when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(false);
+
+ // GIVEN the alert notification isn't needed for this user
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS,
+ new String[]{TEST_PKG});
+ mNotification.extras = extras;
+ when(mForegroundServiceController.isSystemAlertWarningNeeded(sbn.getUserId(), TEST_PKG))
+ .thenReturn(false);
+
+ // THEN filter out the notification
+ assertTrue(mForegroundFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void filterTest_doNotFilter() {
+ StatusBarNotification sbn = mEntry.getSbn();
+
+ // GIVEN the notification isn't a system alert notification nor a disclosure notification
+ when(mForegroundServiceController.isSystemAlertNotification(sbn)).thenReturn(false);
+ when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(false);
+
+ // THEN don't filter out the notification
+ assertFalse(mForegroundFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void extendLifetimeText_notForeground() {
+ // GIVEN the notification doesn't represent a foreground service
+ mNotification.flags = 0;
+
+ // THEN don't extend the lifetime
+ assertFalse(mForegroundNotifLifetimeExtender
+ .shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
+ }
+
+ @Test
+ public void extendLifetimeText_foregroundNotifRecentlyPosted() {
+ // GIVEN the notification represents a foreground service that was just posted
+ mNotification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ mEntry = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setSbn(new StatusBarNotification(TEST_PKG, TEST_PKG, NOTIF_USER_ID, "",
+ NOTIF_USER_ID, NOTIF_USER_ID, mNotification,
+ new UserHandle(NOTIF_USER_ID), "", System.currentTimeMillis()))
+ .setNotification(mNotification)
+ .build();
+
+ // THEN extend the lifetime
+ assertTrue(mForegroundNotifLifetimeExtender
+ .shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
+ }
+
+ @Test
+ public void extendLifetimeText_foregroundNotifOld() {
+ // GIVEN the notification represents a foreground service that was posted 10 seconds ago
+ mNotification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ mEntry = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setSbn(new StatusBarNotification(TEST_PKG, TEST_PKG, NOTIF_USER_ID, "",
+ NOTIF_USER_ID, NOTIF_USER_ID, mNotification,
+ new UserHandle(NOTIF_USER_ID), "",
+ System.currentTimeMillis() - 10000))
+ .setNotification(mNotification)
+ .build();
+
+ // THEN don't extend the lifetime because the extended time exceeds
+ // ForegroundCoordinator.MIN_FGS_TIME_MS
+ assertFalse(mForegroundNotifLifetimeExtender
+ .shouldExtendLifetime(mEntry, NotificationListenerService.REASON_CLICK));
+ }
+}
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 87b3783d..979b8a9 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
@@ -24,6 +24,8 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Handler;
@@ -36,16 +38,19 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
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.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.policy.KeyguardStateController;
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;
@@ -61,14 +66,16 @@
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private NotifListBuilderImpl mNotifListBuilder;
private NotificationEntry mEntry;
- private KeyguardCoordinator mKeyguardNotificationCoordinator;
+ private KeyguardCoordinator mKeyguardCoordinator;
+ private NotifFilter mKeyguardFilter;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mKeyguardNotificationCoordinator = new KeyguardCoordinator(
+ mKeyguardCoordinator = new KeyguardCoordinator(
mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
mBroadcastDispatcher, mStatusBarStateController,
mKeyguardUpdateMonitor);
@@ -76,65 +83,70 @@
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());
+ mKeyguardFilter = filterCaptor.getValue();
}
@Test
public void unfilteredState() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// THEN don't filter out the entry
- assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void notificationNotForCurrentProfile() {
// GIVEN the notification isn't for the given user
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(false);
// THEN filter out the entry
- assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void keyguardNotShowing() {
// GIVEN the lockscreen isn't showing
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
when(mKeyguardStateController.isShowing()).thenReturn(false);
// THEN don't filter out the entry
- assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertFalse(mKeyguardFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void doNotShowLockscreenNotifications() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN we shouldn't show any lockscreen notifications
when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false);
// THEN filter out the entry
- assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void lockdown() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN the notification's user is in lockdown:
when(mKeyguardUpdateMonitor.isUserInLockdown(NOTIF_USER_ID)).thenReturn(true);
// THEN filter out the entry
- assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void publicMode_settingsDisallow() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN the notification's user is in public mode and settings are configured to disallow
// notifications in public mode
@@ -143,13 +155,13 @@
.thenReturn(false);
// THEN filter out the entry
- assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void publicMode_notifDisallowed() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN the notification's user is in public mode and settings are configured to disallow
// notifications in public mode
@@ -159,13 +171,13 @@
.setVisibilityOverride(VISIBILITY_SECRET).build());
// THEN filter out the entry
- assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void doesNotExceedThresholdToShow() {
// GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ setupUnfilteredState(mEntry);
// WHEN the notification doesn't exceed the threshold to show on the lockscreen
mEntry.setRanking(new RankingBuilder()
@@ -174,39 +186,47 @@
.build());
// THEN filter out the entry
- assertTrue(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertTrue(mKeyguardFilter.shouldFilterOut(mEntry, 0));
}
@Test
public void summaryExceedsThresholdToShow() {
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState();
+ // GIVEN the notification doesn't exceed the threshold to show on the lockscreen
+ // but it's part of a group (has a parent)
+ final GroupEntry parent = new GroupEntry("test_group_key");
+ final NotificationEntry entryWithParent = new NotificationEntryBuilder()
+ .setParent(parent)
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .build();
- // WHEN the notification doesn't exceed the threshold to show on the lockscreen
- // but its summary does
- mEntry.setRanking(new RankingBuilder()
- .setKey(mEntry.getKey())
+ setupUnfilteredState(entryWithParent);
+ entryWithParent.setRanking(new RankingBuilder()
+ .setKey(entryWithParent.getKey())
.setImportance(IMPORTANCE_MIN)
.build());
- final NotificationEntry summary = new NotificationEntryBuilder().build();
- summary.setRanking(new RankingBuilder()
- .setKey(summary.getKey())
+ // WHEN its parent has a summary that exceeds threshold to show on lockscreen
+ parent.setSummary(new NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.build());
- final GroupEntry group = new GroupEntry(mEntry.getSbn().getGroupKey());
- group.setSummary(summary);
- mEntry.setParent(group);
// THEN don't filter out the entry
- assertFalse(mKeyguardNotificationCoordinator.mNotifFilter.shouldFilterOut(mEntry, 0));
+ assertFalse(mKeyguardFilter.shouldFilterOut(entryWithParent, 0));
+
+ // WHEN its parent has a summary that doesn't exceed threshold to show on lockscreen
+ parent.setSummary(new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_MIN)
+ .build());
+
+ // THEN filter out the entry
+ assertTrue(mKeyguardFilter.shouldFilterOut(entryWithParent, 0));
}
/**
* setup a state where the notification will not be filtered by the
* KeyguardNotificationCoordinator when the keyguard is showing.
*/
- private void setupUnfilteredState() {
+ private void setupUnfilteredState(NotificationEntry entry) {
// notification is for current profile
when(mLockscreenUserManager.isCurrentProfile(NOTIF_USER_ID)).thenReturn(true);
@@ -227,7 +247,7 @@
// entry's ranking - should show on all lockscreens
// + priority of the notification exceeds the threshold to be shown on the lockscreen
- mEntry.setRanking(new RankingBuilder()
+ entry.setRanking(new RankingBuilder()
.setKey(mEntry.getKey())
.setVisibilityOverride(VISIBILITY_PUBLIC)
.setImportance(IMPORTANCE_HIGH)
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
new file mode 100644
index 0000000..d3b16c3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.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.statusbar.notification.collection.coordinator;
+
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+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.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+
+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 RankingCoordinatorTest extends SysuiTestCase {
+
+ @Mock private StatusBarStateController mStatusBarStateController;
+ @Mock private NotifListBuilderImpl mNotifListBuilder;
+ private NotificationEntry mEntry;
+ private RankingCoordinator mRankingCoordinator;
+ private NotifFilter mRankingFilter;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mRankingCoordinator = new RankingCoordinator(mStatusBarStateController);
+ mEntry = new NotificationEntryBuilder().build();
+
+ ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
+ mRankingCoordinator.attach(null, mNotifListBuilder);
+ verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+ mRankingFilter = filterCaptor.getValue();
+ }
+
+ @Test
+ public void testUnfilteredState() {
+ // GIVEN no suppressed visual effects + app not suspended
+ mEntry.setRanking(getRankingForUnfilteredNotif().build());
+
+ // THEN don't filter out the notification
+ assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void filterSuspended() {
+ // GIVEN the notification's app is suspended
+ mEntry.setRanking(getRankingForUnfilteredNotif()
+ .setSuspended(true)
+ .build());
+
+ // THEN filter out the notification
+ assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void filterDozingSuppressAmbient() {
+ // GIVEN should suppress ambient
+ mEntry.setRanking(getRankingForUnfilteredNotif()
+ .setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT)
+ .build());
+
+ // WHEN it's dozing (on ambient display)
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+ // THEN filter out the notification
+ assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0));
+
+ // WHEN it's not dozing (showing the notification list)
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+ // THEN don't filter out the notification
+ assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ @Test
+ public void filterDozingSuppressNotificationList() {
+ // GIVEN should suppress from the notification list
+ mEntry.setRanking(getRankingForUnfilteredNotif()
+ .setSuppressedVisualEffects(SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+ .build());
+
+ // WHEN it's dozing (on ambient display)
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+ // THEN don't filter out the notification
+ assertFalse(mRankingFilter.shouldFilterOut(mEntry, 0));
+
+ // WHEN it's not dozing (showing the notification list)
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+ // THEN filter out the notification
+ assertTrue(mRankingFilter.shouldFilterOut(mEntry, 0));
+ }
+
+ private RankingBuilder getRankingForUnfilteredNotif() {
+ return new RankingBuilder()
+ .setKey(mEntry.getKey())
+ .setSuppressedVisualEffects(0)
+ .setSuspended(false);
+ }
+}
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
new file mode 100644
index 0000000..6fa1a89
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
index 4f1ffbe..2662c80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
@@ -29,10 +29,10 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -52,12 +52,12 @@
private NotificationLogger.ExpansionStateLogger mLogger;
@Mock
private IStatusBarService mBarService;
+ private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mLogger = new NotificationLogger.ExpansionStateLogger(
- Dependency.get(UiOffloadThread.class));
+ mLogger = new NotificationLogger.ExpansionStateLogger(mUiBgExecutor);
mLogger.mBarService = mBarService;
}
@@ -66,7 +66,7 @@
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
@@ -76,7 +76,7 @@
public void testExpanded() throws RemoteException {
mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
@@ -89,7 +89,7 @@
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
@@ -102,7 +102,7 @@
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService).onNotificationExpansionChanged(
NOTIFICATION_KEY, true, true,
@@ -117,7 +117,7 @@
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true,
NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)),
Collections.emptyList());
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService).onNotificationExpansionChanged(
NOTIFICATION_KEY, false, true,
@@ -133,7 +133,7 @@
Collections.emptyList());
mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService).onNotificationExpansionChanged(
NOTIFICATION_KEY, false, true,
@@ -150,7 +150,7 @@
NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService).onNotificationExpansionChanged(
NOTIFICATION_KEY, false, true,
@@ -164,7 +164,7 @@
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService).onNotificationExpansionChanged(
NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
@@ -172,7 +172,7 @@
mLogger.onVisibilityChanged(
Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
Collections.emptyList());
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
// onNotificationExpansionChanged is called the second time.
verify(mBarService, times(2)).onNotificationExpansionChanged(
NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index d139866..d826ce1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -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 com.android.systemui.statusbar.notification.logging;
@@ -36,17 +36,17 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.UiOffloadThread;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
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 com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.google.android.collect.Lists;
@@ -60,6 +60,7 @@
import org.mockito.MockitoAnnotations;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -82,6 +83,7 @@
private TestableNotificationLogger mLogger;
private NotificationEntryListener mNotificationEntryListener;
private ConcurrentLinkedQueue<AssertionError> mErrorQueue = new ConcurrentLinkedQueue<>();
+ private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setUp() {
@@ -98,7 +100,7 @@
.build();
mEntry.setRow(mRow);
- mLogger = new TestableNotificationLogger(mListener, Dependency.get(UiOffloadThread.class),
+ mLogger = new TestableNotificationLogger(mListener, mUiBgExecutor,
mEntryManager, mock(StatusBarStateControllerImpl.class), mBarService,
mExpansionStateLogger);
mLogger.setUpWithContainer(mListContainer);
@@ -130,7 +132,7 @@
when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
TestableLooper.get(this).processAllMessages();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
if(!mErrorQueue.isEmpty()) {
throw mErrorQueue.poll();
@@ -140,7 +142,7 @@
Mockito.reset(mBarService);
mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
TestableLooper.get(this).processAllMessages();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService, never()).onNotificationVisibilityChanged(any(), any());
}
@@ -152,11 +154,11 @@
when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
TestableLooper.get(this).processAllMessages();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
Mockito.reset(mBarService);
mLogger.stopNotificationLogging();
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
// The visibility objects are recycled by NotificationLogger, so we can't use specific
// matchers here.
verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
@@ -165,12 +167,12 @@
private class TestableNotificationLogger extends NotificationLogger {
TestableNotificationLogger(NotificationListener notificationListener,
- UiOffloadThread uiOffloadThread,
+ Executor uiBgExecutor,
NotificationEntryManager entryManager,
StatusBarStateControllerImpl statusBarStateController,
IStatusBarService barService,
ExpansionStateLogger expansionStateLogger) {
- super(notificationListener, uiOffloadThread, entryManager, statusBarStateController,
+ super(notificationListener, uiBgExecutor, entryManager, statusBarStateController,
expansionStateLogger);
mBarService = barService;
// Make this on the current thread so we can wait for it during tests.
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 f5d6f22..0764d0c 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
@@ -89,8 +89,17 @@
return mockSubscription
}
}
+ val fakeSettingDataSource = object : DataSource<Boolean> {
+ override fun registerListener(listener: DataListener<Boolean>): Subscription {
+ listener.onDataChanged(true)
+ return mockSubscription
+ }
+ }
val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
- mockActivityStarter, fakeModelDataSource)
+ mockActivityStarter,
+ fakeModelDataSource,
+ fakeSettingDataSource
+ )
val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
val mockClickView = mock(View::class.java)
@@ -112,6 +121,41 @@
same(mockClickView)
)
}
+
+ @Test
+ fun testViewModelDataSource_notVisibleIfSettingDisabled() {
+ val fakeClickIntent = PendingIntent.getActivity(context, 0, Intent("action"), 0)
+ val fakePerson = fakePersonModel("id", "name", fakeClickIntent)
+ val fakeModel = PeopleHubModel(listOf(fakePerson))
+ val mockSubscription = mock(Subscription::class.java)
+ val fakeModelDataSource = object : DataSource<PeopleHubModel> {
+ override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
+ listener.onDataChanged(fakeModel)
+ return mockSubscription
+ }
+ }
+ val fakeSettingDataSource = object : DataSource<Boolean> {
+ override fun registerListener(listener: DataListener<Boolean>): Subscription {
+ listener.onDataChanged(false)
+ return mockSubscription
+ }
+ }
+ val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
+ mockActivityStarter,
+ fakeModelDataSource,
+ fakeSettingDataSource
+ )
+ val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
+ val mockClickView = mock(View::class.java)
+
+ factoryDataSource.registerListener(fakeListener)
+
+ val viewModel = (fakeListener.lastSeen as Maybe.Just).value
+ .createWithAssociatedClickView(mockClickView)
+ assertThat(viewModel.isVisible).isFalse()
+ val people = viewModel.people.toList()
+ assertThat(people.size).isEqualTo(0)
+ }
}
/** Works around Mockito matchers returning `null` and breaking non-nullable Kotlin code. */
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 27e3a66..c7e59ef 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
@@ -42,6 +42,7 @@
import android.widget.RemoteViews;
import androidx.test.filters.SmallTest;
+import androidx.test.filters.Suppress;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.InflationTask;
@@ -64,7 +65,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
-@Ignore
+@Suppress
public class NotificationContentInflaterTest extends SysuiTestCase {
private NotificationContentInflater mNotificationInflater;
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 43d39a2..ccc9496 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
@@ -20,6 +20,7 @@
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -383,15 +384,14 @@
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
row.setBlockingHelperShowing(true);
- modifyRanking(row.getEntry())
+ final NotificationEntry entry = row.getEntry();
+ modifyRanking(entry)
.setUserSentiment(USER_SENTIMENT_NEGATIVE)
- .setImportance(IMPORTANCE_DEFAULT)
+ .setImportance(IMPORTANCE_HIGH)
.build();
- row.getEntry().setIsHighPriority(true);
- when(row.getIsNonblockable()).thenReturn(false);
- StatusBarNotification statusBarNotification = row.getEntry().getSbn();
- NotificationEntry entry = row.getEntry();
+ when(row.getIsNonblockable()).thenReturn(false);
+ StatusBarNotification statusBarNotification = entry.getSbn();
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
verify(notificationInfoView).bindNotification(
@@ -408,7 +408,7 @@
eq(false),
eq(false),
eq(true) /* isForBlockingHelper */,
- eq(IMPORTANCE_DEFAULT),
+ eq(IMPORTANCE_HIGH),
eq(true) /* wasShownHighPriority */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index bdca7ef..f513c2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -78,10 +78,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubblesTestActivity;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
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.After;
import org.junit.Before;
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 9eba4eb..f48c40c 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
@@ -38,8 +38,8 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.After;
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 d2b4a20..deca51f 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
@@ -55,7 +55,6 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.EmptyShadeView;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -71,6 +70,7 @@
import com.android.systemui.statusbar.notification.TestableNotificationEntryManager;
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.NotificationRankingManager;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 4451fa4..5907a0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -76,6 +77,8 @@
@Mock
private StatusBar mStatusBar;
@Mock
+ private ShadeController mShadeController;
+ @Mock
private KeyguardStateController mKeyguardStateController;
@Mock
private Handler mHandler;
@@ -98,13 +101,12 @@
when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
mContext.addMockSystemService(PowerManager.class, mPowerManager);
mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
- mDependency.injectTestDependency(StatusBarWindowController.class,
- mStatusBarWindowController);
res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
- mKeyguardViewMediator, mScrimController, mStatusBar, mKeyguardStateController,
- mHandler, mUpdateMonitor, res.getResources(), mKeyguardBypassController,
- mDozeParameters, mMetricsLogger, mDumpController);
+ mKeyguardViewMediator, mScrimController, mStatusBar, mShadeController,
+ mStatusBarWindowController, mKeyguardStateController, mHandler, mUpdateMonitor,
+ res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger,
+ mDumpController);
mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
}
@@ -113,7 +115,8 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT);
verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
- verify(mStatusBarKeyguardViewManager).animateCollapsePanels(anyFloat());
+ verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
+ anyFloat());
}
@Test
@@ -136,7 +139,8 @@
BiometricSourceType.FINGERPRINT);
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager).animateCollapsePanels(anyFloat());
+ verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
+ anyFloat());
}
@Test
@@ -155,7 +159,8 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FACE);
- verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+ verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
+ anyBoolean(), anyFloat());
verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
}
@@ -168,7 +173,8 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FACE);
- verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+ verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
+ anyBoolean(), anyFloat());
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
}
@@ -201,7 +207,8 @@
BiometricSourceType.FACE);
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+ verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
+ anyBoolean(), anyFloat());
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_NONE);
}
@@ -253,7 +260,8 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FACE);
- verify(mStatusBarKeyguardViewManager, never()).animateCollapsePanels(anyFloat());
+ verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
+ anyBoolean(), anyFloat());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index dfd9941..7fa6901 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -33,9 +33,9 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
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.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
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 0df2ebc8..39afbe0 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
@@ -254,6 +254,7 @@
mDivider,
Optional.of(mRecents),
() -> mock(StatusBar.class),
+ mock(ShadeController.class),
mHandler);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index 3ad1e39..54dc728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -26,8 +26,8 @@
import android.os.UserHandle;
import com.android.systemui.R;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4d6ff1f..008a349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -760,6 +760,17 @@
}
@Test
+ public void testWillHideDockedWallpaper() {
+ mAlwaysOnEnabled = false;
+ when(mDockManager.isDocked()).thenReturn(true);
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+
+ mScrimController.transitionTo(ScrimState.AOD);
+
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+ }
+
+ @Test
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.setPanelExpansion(0.5f);
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 d7c00cf..86b2a44 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
@@ -72,6 +72,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -111,6 +113,8 @@
private Handler mHandler;
@Mock
private BubbleController mBubbleController;
+ @Mock
+ private ShadeControllerImpl mShadeController;
@Mock
private ActivityIntentHelper mActivityIntentHelper;
@@ -124,6 +128,7 @@
private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
@Mock
private NotificationPanelView mNotificationPanelView;
+ private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private NotificationTestHelper mNotificationTestHelper;
private ExpandableNotificationRow mNotificationRow;
@@ -176,8 +181,9 @@
mock(NotificationLockscreenUserManager.class),
mKeyguardStateController,
mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
- mock(LockPatternUtils.class), mHandler, mHandler, mActivityIntentHelper,
- mBubbleController, mSuperStatusBarViewFactory))
+ mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
+ mActivityIntentHelper, mBubbleController, mShadeController,
+ mSuperStatusBarViewFactory))
.setStatusBar(mStatusBar)
.setNotificationPresenter(mock(NotificationPresenter.class))
.setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
@@ -194,7 +200,7 @@
// set up addPostCollapseAction to synchronously invoke the Runnable arg
doAnswer(answerVoid(Runnable::run))
- .when(mStatusBar).addPostCollapseAction(any(Runnable.class));
+ .when(mShadeController).addPostCollapseAction(any(Runnable.class));
// set up Handler to synchronously invoke the Runnable arg
doAnswer(answerVoid(Runnable::run))
@@ -219,7 +225,7 @@
mNotificationActivityStarter.onNotificationClicked(sbn, mNotificationRow);
// Then
- verify(mStatusBar, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapsePanel();
verify(mContentIntent).sendAndReturnResult(
any(Context.class),
@@ -254,7 +260,7 @@
verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
// This is called regardless, and simply short circuits when there is nothing to do.
- verify(mStatusBar, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapsePanel();
verify(mAssistManager).hideAssist();
@@ -284,7 +290,7 @@
// Then
verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
- verify(mStatusBar, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapsePanel();
verify(mAssistManager).hideAssist();
@@ -314,7 +320,7 @@
// Then
verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
- verify(mStatusBar, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapsePanel();
verify(mAssistManager).hideAssist();
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 fb6e168..f6ed4e6 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
@@ -40,7 +40,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -54,6 +53,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.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -115,7 +115,7 @@
mock(NotificationAlertingManager.class),
mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class),
mock(KeyguardIndicationController.class),
- mStatusBar, mCommandQueue);
+ mStatusBar, mock(ShadeControllerImpl.class), mCommandQueue);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 6dfd082..cd2c349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -73,7 +73,7 @@
mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
- mActivityStarter, () -> mShadeController, new CommandQueue(mContext)));
+ mActivityStarter, mShadeController, new CommandQueue(mContext)));
mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
}
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 be68097..ca229c9 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
@@ -63,6 +63,7 @@
import android.util.SparseArray;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
@@ -77,7 +78,6 @@
import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.UiOffloadThread;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
@@ -97,7 +97,6 @@
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -119,6 +118,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
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.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -132,6 +132,8 @@
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.volume.VolumeComponent;
import org.junit.Before;
@@ -243,6 +245,8 @@
@Mock private LockscreenLockIconController mLockscreenLockIconController;
@Mock private StatusBarNotificationActivityStarter.Builder
mStatusBarNotificationActivityStarterBuilder;
+ private ShadeController mShadeController;
+ private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setup() throws Exception {
@@ -264,7 +268,7 @@
mMetricsLogger = new FakeMetricsLogger();
NotificationLogger notificationLogger = new NotificationLogger(mNotificationListener,
- Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController,
+ mUiBgExecutor, mEntryManager, mStatusBarStateController,
mExpansionStateLogger);
notificationLogger.setVisibilityReporter(mock(Runnable.class));
@@ -310,6 +314,11 @@
when(mStatusBarComponent.getStatusBarWindowViewController()).thenReturn(
mStatusBarWindowViewController);
+ mShadeController = new ShadeControllerImpl(mCommandQueue,
+ mStatusBarStateController, mStatusBarWindowController,
+ mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
+ () -> mStatusBar, () -> mAssistManager, () -> mBubbleController);
+
mStatusBar = new StatusBar(
mContext,
mFeatureFlags,
@@ -342,7 +351,7 @@
mNotificationAlertingManager,
new DisplayMetrics(),
mMetricsLogger,
- Dependency.get(UiOffloadThread.class),
+ mUiBgExecutor,
mNotificationMediaManager,
mLockscreenUserManager,
mRemoteInputManager,
@@ -382,6 +391,7 @@
Optional.of(mDivider),
mLightsOutNotifController,
mStatusBarNotificationActivityStarterBuilder,
+ mShadeController,
mSuperStatusBarViewFactory,
mStatusBarKeyguardViewManager,
mViewMediatorCallback,
@@ -633,7 +643,7 @@
public void testLogHidden() {
try {
mStatusBar.handleVisibleToUserChanged(false);
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService, times(1)).onPanelHidden();
verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
} catch (RemoteException e) {
@@ -651,7 +661,7 @@
try {
mStatusBar.handleVisibleToUserChanged(true);
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService, never()).onPanelHidden();
verify(mBarService, times(1)).onPanelRevealed(false, 1);
} catch (RemoteException e) {
@@ -670,7 +680,7 @@
try {
mStatusBar.handleVisibleToUserChanged(true);
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService, never()).onPanelHidden();
verify(mBarService, times(1)).onPanelRevealed(true, 5);
} catch (RemoteException e) {
@@ -688,7 +698,7 @@
try {
mStatusBar.handleVisibleToUserChanged(true);
- waitForUiOffloadThread();
+ mUiBgExecutor.runAllReady();
verify(mBarService, never()).onPanelHidden();
verify(mBarService, times(1)).onPanelRevealed(false, 5);
} catch (RemoteException e) {
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 00ea187..9f899ee 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
@@ -102,7 +102,7 @@
mDozeLog,
mDozeParameters,
new CommandQueue(mContext),
- () -> mShadeController,
+ mShadeController,
mDockManager,
mView);
mController.setupExpandedStatusBar();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index e629a4f..fbbfa96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -38,6 +38,7 @@
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
@@ -204,4 +205,28 @@
assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
}
+
+ @Test
+ public void testOnActiveDeviceChanged_updatesAudioActive() {
+ assertFalse(mBluetoothControllerImpl.isBluetoothAudioActive());
+ assertFalse(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
+
+ CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+ mDevices.add(device);
+ when(device.isActiveDevice(BluetoothProfile.HEADSET)).thenReturn(true);
+
+ List<LocalBluetoothProfile> profiles = new ArrayList<>();
+ LocalBluetoothProfile profile = mock(LocalBluetoothProfile.class);
+ profiles.add(profile);
+ when(profile.getProfileId()).thenReturn(BluetoothProfile.HEADSET);
+ when(device.getProfiles()).thenReturn(profiles);
+ when(device.isConnectedProfile(profile)).thenReturn(true);
+
+ mBluetoothControllerImpl.onAclConnectionStateChanged(device,
+ BluetoothProfile.STATE_CONNECTED);
+ mBluetoothControllerImpl.onActiveDeviceChanged(device, BluetoothProfile.HEADSET);
+
+ assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive());
+ assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index c1f376b..53d8e58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
@@ -42,9 +42,9 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions;
import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index b5e4cb9..f1a6e67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -51,10 +51,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.NotificationEntryBuilder;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.phone.ShadeController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 426aba0..260ff2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -69,8 +69,11 @@
}
for (Pair<Executor, OnPropertiesChangedListener> listener : mListeners) {
- listener.first.execute(() -> listener.second.onPropertiesChanged(
- new Properties(namespace, mProperties.get(namespace))));
+ Properties.Builder propBuilder = new Properties.Builder(namespace);
+ for (String key : mProperties.get(namespace).keySet()) {
+ propBuilder.setString(key, mProperties.get(namespace).get(key));
+ }
+ listener.first.execute(() -> listener.second.onPropertiesChanged(propBuilder.build()));
}
return true;
}
@@ -88,10 +91,12 @@
private Properties propsForNamespaceAndName(String namespace, String name) {
if (mProperties.containsKey(namespace) && mProperties.get(namespace).containsKey(name)) {
- return new Properties(namespace, mProperties.get(namespace));
+ return new Properties.Builder(namespace)
+ .setString(name, mProperties.get(namespace).get(name)).build();
}
if (mDefaultProperties.containsKey(namespace)) {
- return new Properties(namespace, mDefaultProperties.get(namespace));
+ return new Properties.Builder(namespace)
+ .setString(name, mDefaultProperties.get(namespace).get(name)).build();
}
return null;
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
new file mode 100644
index 0000000..709a1a8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -0,0 +1,441 @@
+package com.android.systemui.util.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.util.ArrayMap
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringForce
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.animation.PhysicsAnimator.EndListener
+import com.android.systemui.util.animation.PhysicsAnimator.UpdateListener
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PhysicsAnimatorTest : SysuiTestCase() {
+ private lateinit var viewGroup: ViewGroup
+ private lateinit var testView: View
+ private lateinit var testView2: View
+
+ private lateinit var animator: PhysicsAnimator<View>
+
+ private val springConfig = PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ private val flingConfig = PhysicsAnimator.FlingConfig(2f)
+
+ private lateinit var mockUpdateListener: UpdateListener<View>
+ private lateinit var mockEndListener: EndListener<View>
+ private lateinit var mockEndAction: Runnable
+
+ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ mockUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+ mockEndListener = mock(EndListener::class.java) as EndListener<View>
+ mockEndAction = mock(Runnable::class.java)
+
+ viewGroup = FrameLayout(context)
+ testView = View(context)
+ testView2 = View(context)
+ viewGroup.addView(testView)
+ viewGroup.addView(testView2)
+
+ PhysicsAnimatorTestUtils.prepareForTest()
+
+ // Most of our tests involve checking the end state of animations, so we want calls that
+ // start animations to block the test thread until the animations have ended.
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+ animator = PhysicsAnimator.getInstance(testView)
+ }
+
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ }
+
+ @Test
+ fun testOneAnimatorPerView() {
+ assertEquals(animator, PhysicsAnimator.getInstance(testView))
+ assertEquals(PhysicsAnimator.getInstance(testView), PhysicsAnimator.getInstance(testView))
+ assertNotEquals(animator, PhysicsAnimator.getInstance(testView2))
+ }
+
+ @Test
+ fun testSpringOneProperty() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 50f, springConfig)
+ .start()
+
+ assertEquals(testView.translationX, 50f, 1f)
+ }
+
+ @Test
+ fun testSpringMultipleProperties() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+ .spring(DynamicAnimation.SCALE_Y, 1.1f, springConfig)
+ .start()
+
+ assertEquals(10f, testView.translationX, 1f)
+ assertEquals(50f, testView.translationY, 1f)
+ assertEquals(1.1f, testView.scaleY, 0.01f)
+ }
+
+ @Test
+ fun testFling() {
+ val startTime = System.currentTimeMillis()
+
+ animator
+ .fling(DynamicAnimation.TRANSLATION_X, 1000f /* startVelocity */, flingConfig)
+ .fling(DynamicAnimation.TRANSLATION_Y, 500f, flingConfig)
+ .start()
+
+ val elapsedTimeSeconds = (System.currentTimeMillis() - startTime) / 1000f
+
+ // If the fling worked, the view should be somewhere between its starting position and the
+ // and the theoretical no-friction maximum of startVelocity (in pixels per second)
+ // multiplied by elapsedTimeSeconds. We can't calculate an exact expected location for a
+ // fling, so this is close enough.
+ assertTrue(testView.translationX > 0f)
+ assertTrue(testView.translationX < 1000f * elapsedTimeSeconds)
+ assertTrue(testView.translationY > 0f)
+ assertTrue(testView.translationY < 500f * elapsedTimeSeconds)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testEndListenersAndActions() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 500f, springConfig)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // Once TRANSLATION_X is done, the view should be at x = 10...
+ assertEquals(10f, testView.translationX, 1f)
+
+ // / ...TRANSLATION_Y should still be running...
+ assertTrue(animator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+
+ // ...and our end listener should have been called with x = 10, velocity = 0, and allEnded =
+ // false since TRANSLATION_Y is still running.
+ verify(mockEndListener).onAnimationEnd(
+ testView,
+ DynamicAnimation.TRANSLATION_X,
+ canceled = false,
+ finalValue = 10f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = false)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The end action should not have been run yet.
+ verify(mockEndAction, times(0)).run()
+
+ // Block until TRANSLATION_Y finishes.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+ // The view should have been moved.
+ assertEquals(10f, testView.translationX, 1f)
+ assertEquals(500f, testView.translationY, 1f)
+
+ // The end listener should have been called, this time with TRANSLATION_Y, y = 50, and
+ // allEnded = true.
+ verify(mockEndListener).onAnimationEnd(
+ testView,
+ DynamicAnimation.TRANSLATION_Y,
+ canceled = false,
+ finalValue = 500f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // Now that all properties are done animating, the end action should have been called.
+ verify(mockEndAction, times(1)).run()
+ }
+
+ @Test
+ fun testUpdateListeners() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+ .addUpdateListener(object : UpdateListener<View> {
+ override fun onAnimationUpdateForProperty(
+ target: View,
+ values: UpdateMap<View>
+ ) {
+ mockUpdateListener.onAnimationUpdateForProperty(target, values)
+ }
+ })
+ .start()
+
+ verifyUpdateListenerCalls(animator, mockUpdateListener)
+ }
+
+ @Test
+ fun testListenersNotCalledOnSubsequentAnimations() {
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ verifyUpdateListenerCalls(animator, mockUpdateListener)
+ verify(mockEndListener, times(1)).onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
+ eq(true))
+ verify(mockEndAction, times(1)).run()
+
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 0f, springConfig)
+ .start()
+
+ // We didn't pass any of the listeners/actions to the subsequent animation, so they should
+ // never have been called.
+ verifyNoMoreInteractions(mockUpdateListener)
+ verifyNoMoreInteractions(mockEndListener)
+ verifyNoMoreInteractions(mockEndAction)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testAnimationsUpdatedWhileInMotion() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Spring towards x = 100f.
+ animator
+ .spring(
+ DynamicAnimation.TRANSLATION_X,
+ 100f,
+ springConfig)
+ .start()
+
+ // Block until it reaches x = 50f.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { view -> view.translationX > 50f }
+
+ // Translation X value at the time of reversing the animation to spring to x = 0f.
+ val reversalTranslationX = testView.translationX
+
+ // Spring back towards 0f.
+ animator
+ .spring(
+ DynamicAnimation.TRANSLATION_X,
+ 0f,
+ // Lower the stiffness to ensure the update listener receives at least one
+ // update frame where the view has continued to move to the right.
+ springConfig.apply { stiffness = SpringForce.STIFFNESS_LOW })
+ .start()
+
+ // Wait for TRANSLATION_X.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // Verify that the animation continued past the X value at the time of reversal, before
+ // springing back. This ensures the change in direction was not abrupt.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > reversalTranslationX },
+ { u -> u.value < reversalTranslationX })
+
+ // Verify that the view is where it should be.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testAnimationsUpdatedWhileInMotion_originalListenersStillCalled() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Spring TRANSLATION_X to 100f, with an update and end listener provided.
+ animator
+ .spring(
+ DynamicAnimation.TRANSLATION_X,
+ 100f,
+ // Use very low stiffness to ensure that all of the keyframes we're testing
+ // for are reported to the update listener.
+ springConfig.apply { stiffness = SpringForce.STIFFNESS_VERY_LOW })
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Wait until the animation is halfway there.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { view -> view.translationX > 50f }
+
+ // The end listener shouldn't have been called since the animation hasn't ended.
+ verifyNoMoreInteractions(mockEndListener)
+
+ // Make sure we called the update listener with appropriate values.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > 0f },
+ { u -> u.value >= 50f })
+
+ // Mock a second end listener.
+ val secondEndListener = mock(EndListener::class.java) as EndListener<View>
+ val secondUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+
+ // Start a new animation that springs both TRANSLATION_X and TRANSLATION_Y, and provide it
+ // the second end listener. This new end listener should be called for the end of
+ // TRANSLATION_X and TRANSLATION_Y, with allEnded = true when both have ended.
+ animator
+ .spring(DynamicAnimation.TRANSLATION_X, 200f, springConfig)
+ .spring(DynamicAnimation.TRANSLATION_Y, 4000f, springConfig)
+ .addUpdateListener(secondUpdateListener)
+ .addEndListener(secondEndListener)
+ .start()
+
+ // Wait for TRANSLATION_X to end.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+ // The update listener provided to the initial animation call (the one that only animated
+ // TRANSLATION_X) should have been called with values on the way to x = 200f. This is
+ // because the second animation call updated the original TRANSLATION_X animation.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value > 100f }, { u -> u.value >= 200f })
+
+ // 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)
+ 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)
+ verifyNoMoreInteractions(secondEndListener)
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+ // The original end listener shouldn't receive any callbacks because it was not provided to
+ // an animator that animated TRANSLATION_Y.
+ verifyNoMoreInteractions(mockEndListener)
+
+ verify(secondEndListener, times(1))
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+ verifyNoMoreInteractions(secondEndListener)
+ }
+
+ @Test
+ fun testFlingRespectsMinMax() {
+ animator
+ .fling(DynamicAnimation.TRANSLATION_X,
+ startVelocity = 1000f,
+ friction = 1.1f,
+ max = 10f)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Ensure that the view stopped at x = 10f, and the end listener was called once with that
+ // value.
+ assertEquals(10f, testView.translationX, 1f)
+ verify(mockEndListener, times(1))
+ .onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
+ anyFloat(), eq(true))
+
+ animator
+ .fling(
+ DynamicAnimation.TRANSLATION_X,
+ startVelocity = -1000f,
+ friction = 1.1f,
+ min = -5f)
+ .addEndListener(mockEndListener)
+ .start()
+
+ // Ensure that the view stopped at x = -5f, and the end listener was called once with that
+ // value.
+ assertEquals(-5f, testView.translationX, 1f)
+ verify(mockEndListener, times(1))
+ .onAnimationEnd(
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
+ anyFloat(), eq(true))
+ }
+
+ @Test
+ fun testExtensionProperty() {
+ testView
+ .physicsAnimator
+ .spring(DynamicAnimation.TRANSLATION_X, 200f)
+ .start()
+
+ assertEquals(200f, 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.
+ */
+ private fun <T : Any> verifyUpdateListenerCalls(
+ animator: PhysicsAnimator<T>,
+ mockUpdateListener: UpdateListener<T>
+ ) {
+ val updates = getAnimationUpdateFrames(animator)
+
+ for (invocation in Mockito.mockingDetails(mockUpdateListener).invocations) {
+
+ // Grab the update map of Property -> AnimationUpdate that was passed to the mock update
+ // listener.
+ val updateMap = invocation.arguments[1]
+ as ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+ //
+ for ((property, update) in updateMap) {
+ val updatesForProperty = updates[property]!!
+
+ // This update should be the next one in the list for this property.
+ if (update != updatesForProperty[0]) {
+ Assert.fail("The update listener was called with an unexpected value: $update.")
+ }
+
+ updatesForProperty.remove(update)
+ }
+
+ // Mark this invocation verified.
+ verify(mockUpdateListener).onAnimationUpdateForProperty(animator.target, updateMap)
+ }
+
+ verifyNoMoreInteractions(mockUpdateListener)
+
+ // Since we were removing values as matching invocations were found, there should no longer
+ // be any values remaining. If there are, it means the update listener wasn't notified when
+ // it should have been.
+ assertEquals(0,
+ updates.values.fold(0, { count, propertyUpdates -> count + propertyUpdates.size }))
+
+ clearAnimationUpdateFrames(animator)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
index f3c0530..7c7ad53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
@@ -17,7 +17,6 @@
package com.android.systemui.util.concurrency;
import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.FakeSystemClock.ClockTickListener;
import java.util.Collections;
import java.util.PriorityQueue;
@@ -29,15 +28,6 @@
private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>();
private boolean mIgnoreClockUpdates;
- private ClockTickListener mClockTickListener = new ClockTickListener() {
- @Override
- public void onUptimeMillis(long uptimeMillis) {
- if (!mIgnoreClockUpdates) {
- runAllReady();
- }
- }
- };
-
/**
* Initializes a fake executor.
*
@@ -47,7 +37,11 @@
*/
public FakeExecutor(FakeSystemClock clock) {
mClock = clock;
- mClock.addListener(mClockTickListener);
+ mClock.addListener(() -> {
+ if (!mIgnoreClockUpdates) {
+ runAllReady();
+ }
+ });
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
index b1716ff..abc283f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
@@ -49,32 +49,31 @@
@Test
public void testNoDelay() {
FakeSystemClock clock = new FakeSystemClock();
- clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(0, runnable.mRunCount);
// Execute two runnables. They should not run and should be left pending.
fakeExecutor.execute(runnable);
assertEquals(0, runnable.mRunCount);
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(1, fakeExecutor.numPending());
fakeExecutor.execute(runnable);
assertEquals(0, runnable.mRunCount);
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(2, fakeExecutor.numPending());
// Run one pending runnable.
assertTrue(fakeExecutor.runNextReady());
assertEquals(1, runnable.mRunCount);
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(1, fakeExecutor.numPending());
// Run a second pending runnable.
assertTrue(fakeExecutor.runNextReady());
assertEquals(2, runnable.mRunCount);
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(0, fakeExecutor.numPending());
// No more runnables to run.
@@ -84,12 +83,12 @@
fakeExecutor.execute(runnable);
fakeExecutor.execute(runnable);
assertEquals(2, runnable.mRunCount);
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(2, fakeExecutor.numPending());
// Execute all pending runnables in batch.
assertEquals(2, fakeExecutor.runAllReady());
assertEquals(4, runnable.mRunCount);
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(0, fakeExecutor.runAllReady());
}
@@ -99,7 +98,6 @@
@Test
public void testDelayed() {
FakeSystemClock clock = new FakeSystemClock();
- clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
@@ -108,7 +106,7 @@
fakeExecutor.executeDelayed(runnable, 50);
fakeExecutor.executeDelayed(runnable, 100);
assertEquals(0, runnable.mRunCount);
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(3, fakeExecutor.numPending());
// Delayed runnables should not advance the clock and therefore should not run.
assertFalse(fakeExecutor.runNextReady());
@@ -134,7 +132,6 @@
@Test
public void testDelayed_AdvanceAndRun() {
FakeSystemClock clock = new FakeSystemClock();
- clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
@@ -143,7 +140,7 @@
fakeExecutor.executeDelayed(runnable, 50);
fakeExecutor.executeDelayed(runnable, 100);
assertEquals(0, runnable.mRunCount);
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
assertEquals(3, fakeExecutor.numPending());
// Delayed runnables should not advance the clock and therefore should not run.
assertFalse(fakeExecutor.runNextReady());
@@ -153,24 +150,24 @@
// Advance the clock to the next runnable. Check that it is run.
assertEquals(1, fakeExecutor.advanceClockToNext());
assertEquals(1, fakeExecutor.runAllReady());
- assertEquals(1, clock.uptimeMillis());
+ assertEquals(10001, clock.uptimeMillis());
assertEquals(2, fakeExecutor.numPending());
assertEquals(1, runnable.mRunCount);
assertEquals(49, fakeExecutor.advanceClockToNext());
assertEquals(1, fakeExecutor.runAllReady());
- assertEquals(50, clock.uptimeMillis());
+ assertEquals(10050, clock.uptimeMillis());
assertEquals(1, fakeExecutor.numPending());
assertEquals(2, runnable.mRunCount);
assertEquals(50, fakeExecutor.advanceClockToNext());
assertEquals(1, fakeExecutor.runAllReady());
- assertEquals(100, clock.uptimeMillis());
+ assertEquals(10100, clock.uptimeMillis());
assertEquals(0, fakeExecutor.numPending());
assertEquals(3, runnable.mRunCount);
// Nothing left to do
assertEquals(0, fakeExecutor.advanceClockToNext());
assertEquals(0, fakeExecutor.runAllReady());
- assertEquals(100, clock.uptimeMillis());
+ assertEquals(10100, clock.uptimeMillis());
assertEquals(0, fakeExecutor.numPending());
assertEquals(3, runnable.mRunCount);
}
@@ -181,7 +178,6 @@
@Test
public void testExecutionOrder() {
FakeSystemClock clock = new FakeSystemClock();
- clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnableA = new RunnableImpl();
RunnableImpl runnableB = new RunnableImpl();
@@ -197,7 +193,7 @@
return null;
};
- assertEquals(0, clock.uptimeMillis());
+ assertEquals(10000, clock.uptimeMillis());
checkRunCounts.invoke(0, 0, 0, 0);
fakeExecutor.execute(runnableA);
@@ -231,8 +227,8 @@
fakeExecutor.execute(runnableA);
fakeExecutor.executeAtTime(runnableB, 0); // this is in the past!
- fakeExecutor.executeAtTime(runnableC, 1000);
- fakeExecutor.executeAtTime(runnableD, 500);
+ fakeExecutor.executeAtTime(runnableC, 11000);
+ fakeExecutor.executeAtTime(runnableD, 10500);
fakeExecutor.advanceClockToNext();
fakeExecutor.runAllReady();
@@ -251,7 +247,6 @@
@Test
public void testRemoval_single() {
FakeSystemClock clock = new FakeSystemClock();
- clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
RunnableImpl runnable = new RunnableImpl();
Runnable removeFunction;
@@ -291,7 +286,6 @@
@Test
public void testRemoval_multi() {
FakeSystemClock clock = new FakeSystemClock();
- clock.setAutoIncrement(false);
FakeExecutor fakeExecutor = new FakeExecutor(clock);
List<Runnable> removeFunctions = new ArrayList<>();
RunnableImpl runnable = new RunnableImpl();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
index 65e5902..601f88e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
@@ -16,122 +16,73 @@
package com.android.systemui.util.time;
+import com.android.systemui.util.concurrency.FakeExecutor;
+
import java.util.ArrayList;
import java.util.List;
+/**
+ * A fake {@link SystemClock} for use with {@link FakeExecutor}.
+ *
+ * Attempts to simulate the behavior of a real system clock. Time can be moved forward but not
+ * backwards. uptimeMillis, elapsedRealtime, and currentThreadTimeMillis are all kept in sync.
+ *
+ * Unless otherwise specified, uptimeMillis and elapsedRealtime will advance the same amount with
+ * every call to {@link #advanceTime(long)}. Thread time always lags by 50% of the uptime
+ * advancement to simulate time loss due to scheduling.
+ */
public class FakeSystemClock implements SystemClock {
- private boolean mAutoIncrement = true;
+ private long mUptimeMillis = 10000;
+ private long mElapsedRealtime = 10000;
+ private long mCurrentThreadTimeMillis = 10000;
- private long mUptimeMillis;
- private long mElapsedRealtime;
- private long mElapsedRealtimeNanos;
- private long mCurrentThreadTimeMillis;
- private long mCurrentThreadTimeMicro;
- private long mCurrentTimeMicro;
-
- List<ClockTickListener> mListeners = new ArrayList<>();
+ private final List<ClockTickListener> mListeners = new ArrayList<>();
@Override
public long uptimeMillis() {
- long value = mUptimeMillis;
- if (mAutoIncrement) {
- setUptimeMillis(mUptimeMillis + 1);
- }
- return value;
+ return mUptimeMillis;
}
@Override
public long elapsedRealtime() {
- long value = mElapsedRealtime;
- if (mAutoIncrement) {
- setElapsedRealtime(mElapsedRealtime + 1);
- }
- return value;
+ return mElapsedRealtime;
}
@Override
public long elapsedRealtimeNanos() {
- long value = mElapsedRealtimeNanos;
- if (mAutoIncrement) {
- setElapsedRealtimeNanos(mElapsedRealtimeNanos + 1);
- }
- return value;
+ return mElapsedRealtime * 1000000 + 447;
}
@Override
public long currentThreadTimeMillis() {
- long value = mCurrentThreadTimeMillis;
- if (mAutoIncrement) {
- setCurrentThreadTimeMillis(mCurrentThreadTimeMillis + 1);
- }
- return value;
+ return mCurrentThreadTimeMillis;
}
- @Override
- public long currentThreadTimeMicro() {
- long value = mCurrentThreadTimeMicro;
- if (mAutoIncrement) {
- setCurrentThreadTimeMicro(mCurrentThreadTimeMicro + 1);
- }
- return value;
+ public void setUptimeMillis(long uptime) {
+ advanceTime(uptime - mUptimeMillis);
}
- @Override
- public long currentTimeMicro() {
- long value = mCurrentTimeMicro;
- if (mAutoIncrement) {
- setCurrentTimeMicro(mCurrentTimeMicro + 1);
- }
- return value;
+ public void advanceTime(long uptime) {
+ advanceTime(uptime, 0);
}
- public void setUptimeMillis(long uptimeMillis) {
- mUptimeMillis = uptimeMillis;
- for (ClockTickListener listener : mListeners) {
- listener.onUptimeMillis(mUptimeMillis);
+ public void advanceTime(long uptime, long sleepTime) {
+ if (uptime < 0 || sleepTime < 0) {
+ throw new IllegalArgumentException("Time cannot go backwards");
}
- }
- public void setElapsedRealtime(long elapsedRealtime) {
- mElapsedRealtime = elapsedRealtime;
- for (ClockTickListener listener : mListeners) {
- listener.onElapsedRealtime(mElapsedRealtime);
+ if (uptime > 0 || sleepTime > 0) {
+ mUptimeMillis += uptime;
+ mElapsedRealtime += uptime + sleepTime;
+
+ mCurrentThreadTimeMillis += Math.ceil(uptime * 0.5);
+
+ for (ClockTickListener listener : mListeners) {
+ listener.onClockTick();
+ }
}
}
- public void setElapsedRealtimeNanos(long elapsedRealtimeNanos) {
- mElapsedRealtimeNanos = elapsedRealtimeNanos;
- for (ClockTickListener listener : mListeners) {
- listener.onElapsedRealtimeNanos(mElapsedRealtimeNanos);
- }
- }
-
- public void setCurrentThreadTimeMillis(long currentThreadTimeMillis) {
- mCurrentThreadTimeMillis = currentThreadTimeMillis;
- for (ClockTickListener listener : mListeners) {
- listener.onCurrentThreadTimeMillis(mCurrentThreadTimeMillis);
- }
- }
-
- public void setCurrentThreadTimeMicro(long currentThreadTimeMicro) {
- mCurrentThreadTimeMicro = currentThreadTimeMicro;
- for (ClockTickListener listener : mListeners) {
- listener.onCurrentThreadTimeMicro(mCurrentThreadTimeMicro);
- }
- }
-
- public void setCurrentTimeMicro(long currentTimeMicro) {
- mCurrentTimeMicro = currentTimeMicro;
- for (ClockTickListener listener : mListeners) {
- listener.onCurrentTimeMicro(mCurrentTimeMicro);
- }
- }
-
- /** If true, each call to get____ will be one higher than the previous call to that method. */
- public void setAutoIncrement(boolean autoIncrement) {
- mAutoIncrement = autoIncrement;
- }
-
public void addListener(ClockTickListener listener) {
mListeners.add(listener);
}
@@ -140,30 +91,9 @@
mListeners.remove(listener);
}
- /** Alert all the listeners about the current time. */
- public void synchronizeListeners() {
- for (ClockTickListener listener : mListeners) {
- listener.onUptimeMillis(mUptimeMillis);
- listener.onElapsedRealtime(mElapsedRealtime);
- listener.onElapsedRealtimeNanos(mElapsedRealtimeNanos);
- listener.onCurrentThreadTimeMillis(mCurrentThreadTimeMillis);
- listener.onCurrentThreadTimeMicro(mCurrentThreadTimeMicro);
- listener.onCurrentTimeMicro(mCurrentTimeMicro);
- }
- }
-
-
public interface ClockTickListener {
- default void onUptimeMillis(long uptimeMillis) {}
-
- default void onElapsedRealtime(long elapsedRealtime) {}
-
- default void onElapsedRealtimeNanos(long elapsedRealtimeNanos) {}
-
- default void onCurrentThreadTimeMillis(long currentThreadTimeMillis) {}
-
- default void onCurrentThreadTimeMicro(long currentThreadTimeMicro) {}
-
- default void onCurrentTimeMicro(long currentTimeMicro) {}
+ void onClockTick();
}
+
+ private static final long START_TIME = 10000;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
index cac6bf7..6cbd175 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -57,6 +57,16 @@
}
@Override
+ public boolean isBluetoothAudioProfileOnly() {
+ return false;
+ }
+
+ @Override
+ public boolean isBluetoothAudioActive() {
+ return false;
+ }
+
+ @Override
public String getConnectedDeviceName() {
return null;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
new file mode 100644
index 0000000..701b2fa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/EventsTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.volume;
+
+import static org.junit.Assert.assertEquals;
+
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.metrics.LogMaker;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Queue;
+
+/**
+ * Parameterized unit test for Events.logEvent.
+ *
+ * This test captures a translation table between the Event class tags, the debugging logs,
+ * the event-buffer logs, and the statsd logs.
+ *
+ * This test works as a straight JUnit4 test, but is declared as a SysuiTestCase because
+ * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest requires all tests in SystemUiTest extend
+ * either SysuiTestCase or SysUiBaseFragmentTest.
+ *
+ */
+@RunWith(Parameterized.class)
+@SmallTest
+public class EventsTest extends SysuiTestCase {
+ private FakeMetricsLogger mLegacyLogger;
+ private UiEventLoggerFake mUiEventLogger;
+
+ @Before
+ public void setFakeLoggers() {
+ mLegacyLogger = new FakeMetricsLogger();
+ Events.sLegacyLogger = mLegacyLogger;
+ mUiEventLogger = new UiEventLoggerFake();
+ Events.sUiEventLogger = mUiEventLogger;
+ }
+
+ // Parameters for calling writeEvent with arbitrary args.
+ @Parameterized.Parameter
+ public int mTag;
+
+ @Parameterized.Parameter(1)
+ public Object[] mArgs;
+
+ // Expect returned string exactly matches.
+ @Parameterized.Parameter(2)
+ public String mExpectedMessage;
+
+ // Expect these MetricsLogger calls.
+
+ @Parameterized.Parameter(3)
+ public int[] mExpectedMetrics;
+
+ // Expect this UiEvent (use null if there isn't one).
+ @Parameterized.Parameter(4)
+ public UiEventLogger.UiEventEnum mUiEvent;
+
+ @Test
+ public void testLogEvent() {
+ String result = Events.logEvent(mTag, mArgs);
+ assertEquals("Show Dialog", mExpectedMessage, result);
+
+ Queue<LogMaker> logs = mLegacyLogger.getLogs();
+ if (mExpectedMetrics == null) {
+ assertEquals(0, logs.size());
+ } else {
+ assertEquals(mExpectedMetrics.length, logs.size());
+ if (mExpectedMetrics.length > 0) {
+ assertEquals(mExpectedMetrics[0], logs.remove().getCategory());
+ }
+ if (mExpectedMetrics.length > 1) {
+ assertEquals(mExpectedMetrics[1], logs.remove().getCategory());
+ }
+ }
+ Queue<UiEventLoggerFake.FakeUiEvent> events = mUiEventLogger.getLogs();
+ if (mUiEvent != null) {
+ assertEquals(mUiEvent.getId(), events.remove().eventId);
+ }
+ }
+
+ @Parameterized.Parameters(name = "{index}: {2}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {Events.EVENT_SETTINGS_CLICK, null,
+ "writeEvent settings_click",
+ new int[]{MetricsEvent.ACTION_VOLUME_SETTINGS},
+ Events.VolumeDialogEvent.VOLUME_DIALOG_SETTINGS_CLICK},
+ {Events.EVENT_SHOW_DIALOG, new Object[]{Events.SHOW_REASON_VOLUME_CHANGED, false},
+ "writeEvent show_dialog volume_changed keyguard=false",
+ new int[]{MetricsEvent.VOLUME_DIALOG,
+ MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM},
+ Events.VolumeDialogOpenEvent.VOLUME_DIALOG_SHOW_VOLUME_CHANGED},
+ {Events.EVENT_EXPAND, new Object[]{true},
+ "writeEvent expand true",
+ new int[]{MetricsEvent.VOLUME_DIALOG_DETAILS},
+ Events.VolumeDialogEvent.VOLUME_DIALOG_EXPAND_DETAILS},
+ {Events.EVENT_DISMISS_DIALOG,
+ new Object[]{Events.DISMISS_REASON_TOUCH_OUTSIDE, true},
+ "writeEvent dismiss_dialog touch_outside",
+ new int[]{MetricsEvent.VOLUME_DIALOG},
+ Events.VolumeDialogCloseEvent.VOLUME_DIALOG_DISMISS_TOUCH_OUTSIDE},
+ {Events.EVENT_ACTIVE_STREAM_CHANGED, new Object[]{AudioSystem.STREAM_ACCESSIBILITY},
+ "writeEvent active_stream_changed STREAM_ACCESSIBILITY",
+ new int[]{MetricsEvent.ACTION_VOLUME_STREAM},
+ Events.VolumeDialogEvent.VOLUME_DIALOG_ACTIVE_STREAM_CHANGED},
+ {Events.EVENT_ICON_CLICK,
+ new Object[]{AudioSystem.STREAM_MUSIC, Events.ICON_STATE_MUTE},
+ "writeEvent icon_click STREAM_MUSIC mute",
+ new int[]{MetricsEvent.ACTION_VOLUME_ICON},
+ Events.VolumeDialogEvent.VOLUME_DIALOG_MUTE_STREAM},
+ {Events.EVENT_TOUCH_LEVEL_DONE,
+ new Object[]{AudioSystem.STREAM_MUSIC, /* volume */ 0},
+ "writeEvent touch_level_done STREAM_MUSIC 0",
+ new int[]{MetricsEvent.ACTION_VOLUME_SLIDER},
+ Events.VolumeDialogEvent.VOLUME_DIALOG_SLIDER_TO_ZERO},
+ {Events.EVENT_TOUCH_LEVEL_DONE,
+ new Object[]{AudioSystem.STREAM_MUSIC, /* volume */ 1},
+ "writeEvent touch_level_done STREAM_MUSIC 1",
+ new int[]{MetricsEvent.ACTION_VOLUME_SLIDER},
+ Events.VolumeDialogEvent.VOLUME_DIALOG_SLIDER},
+ {Events.EVENT_TOUCH_LEVEL_CHANGED,
+ new Object[]{AudioSystem.STREAM_MUSIC, /* volume */ 0},
+ "writeEvent touch_level_changed STREAM_MUSIC 0",
+ null, null},
+ {Events.EVENT_LEVEL_CHANGED,
+ new Object[]{AudioSystem.STREAM_MUSIC, /* volume */ 0},
+ "writeEvent level_changed STREAM_MUSIC 0",
+ null, null},
+ {Events.EVENT_MUTE_CHANGED,
+ new Object[]{AudioSystem.STREAM_MUSIC, /* volume */ 0},
+ "writeEvent mute_changed STREAM_MUSIC 0",
+ null, null},
+ {Events.EVENT_KEY,
+ new Object[]{AudioSystem.STREAM_MUSIC, /* volume */ 0},
+ "writeEvent key STREAM_MUSIC 0",
+ new int[]{MetricsEvent.ACTION_VOLUME_KEY},
+ Events.VolumeDialogEvent.VOLUME_KEY_TO_ZERO},
+ {Events.EVENT_KEY,
+ new Object[]{AudioSystem.STREAM_MUSIC, /* volume */ 1},
+ "writeEvent key STREAM_MUSIC 1",
+ new int[]{MetricsEvent.ACTION_VOLUME_KEY},
+ Events.VolumeDialogEvent.VOLUME_KEY},
+ {Events.EVENT_RINGER_TOGGLE, new Object[]{AudioManager.RINGER_MODE_NORMAL},
+ "writeEvent ringer_toggle normal",
+ new int[]{MetricsEvent.ACTION_VOLUME_RINGER_TOGGLE},
+ Events.VolumeDialogEvent.RINGER_MODE_NORMAL},
+ {Events.EVENT_EXTERNAL_RINGER_MODE_CHANGED,
+ new Object[]{AudioManager.RINGER_MODE_NORMAL},
+ "writeEvent external_ringer_mode_changed normal",
+ new int[]{MetricsEvent.ACTION_RINGER_MODE},
+ null},
+ {Events.EVENT_INTERNAL_RINGER_MODE_CHANGED,
+ new Object[]{AudioManager.RINGER_MODE_NORMAL},
+ "writeEvent internal_ringer_mode_changed normal",
+ null, null},
+ {Events.EVENT_ZEN_MODE_CHANGED,
+ new Object[]{Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS},
+ "writeEvent zen_mode_changed important_interruptions",
+ null, Events.ZenModeEvent.ZEN_MODE_IMPORTANT_ONLY},
+ {Events.EVENT_ZEN_MODE_CHANGED,
+ new Object[]{Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS},
+ "writeEvent zen_mode_changed important_interruptions",
+ null, Events.ZenModeEvent.ZEN_MODE_IMPORTANT_ONLY},
+ {Events.EVENT_SUPPRESSOR_CHANGED,
+ new Object[]{"component", "name"},
+ "writeEvent suppressor_changed component name",
+ null, null},
+ {Events.EVENT_SHOW_USB_OVERHEAT_ALARM,
+ new Object[]{Events.SHOW_REASON_USB_OVERHEAD_ALARM_CHANGED, true},
+ "writeEvent show_usb_overheat_alarm usb_temperature_above_threshold "
+ + "keyguard=true",
+ new int[]{MetricsEvent.POWER_OVERHEAT_ALARM,
+ MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM},
+ Events.VolumeDialogEvent.USB_OVERHEAT_ALARM},
+ {Events.EVENT_DISMISS_USB_OVERHEAT_ALARM,
+ new Object[]{Events.DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED, true},
+ "writeEvent dismiss_usb_overheat_alarm usb_temperature_below_threshold "
+ + "keyguard=true",
+ new int[]{MetricsEvent.POWER_OVERHEAT_ALARM,
+ MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM},
+ Events.VolumeDialogEvent.USB_OVERHEAT_ALARM_DISMISSED},
+ });
+ }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wm/DisplayLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/wm/DisplayLayoutTest.java
new file mode 100644
index 0000000..9596a73
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wm/DisplayLayoutTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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 static android.content.res.Configuration.UI_MODE_TYPE_NORMAL;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+
+@SmallTest
+public class DisplayLayoutTest extends SysuiTestCase {
+
+ @Test
+ public void testInsets() {
+ Resources res = createResources(40, 50, false, 30, 40);
+ // Test empty display, no bars or anything
+ DisplayInfo info = createDisplayInfo(1000, 1500, 0, ROTATION_0);
+ DisplayLayout dl = new DisplayLayout(info, res, false, false);
+ assertEquals(new Rect(0, 0, 0, 0), dl.stableInsets());
+ assertEquals(new Rect(0, 0, 0, 0), dl.nonDecorInsets());
+
+ // Test with bars
+ dl = new DisplayLayout(info, res, true, true);
+ assertEquals(new Rect(0, 40, 0, 50), dl.stableInsets());
+ assertEquals(new Rect(0, 0, 0, 50), dl.nonDecorInsets());
+
+ // Test just cutout
+ info = createDisplayInfo(1000, 1500, 60, ROTATION_0);
+ dl = new DisplayLayout(info, res, false, false);
+ assertEquals(new Rect(0, 60, 0, 0), dl.stableInsets());
+ assertEquals(new Rect(0, 60, 0, 0), dl.nonDecorInsets());
+
+ // Test with bars and cutout
+ dl = new DisplayLayout(info, res, true, true);
+ assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets());
+ assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets());
+ }
+
+ @Test
+ public void testRotate() {
+ // Basic rotate utility
+ Rect testParent = new Rect(0, 0, 1000, 600);
+ Rect testInner = new Rect(40, 20, 120, 80);
+ Rect testResult = new Rect(testInner);
+ DisplayLayout.rotateBounds(testResult, testParent, 1);
+ assertEquals(new Rect(20, 880, 80, 960), testResult);
+ testResult.set(testInner);
+ DisplayLayout.rotateBounds(testResult, testParent, 2);
+ assertEquals(new Rect(880, 20, 960, 80), testResult);
+ testResult.set(testInner);
+ DisplayLayout.rotateBounds(testResult, testParent, 3);
+ assertEquals(new Rect(520, 40, 580, 120), testResult);
+
+ Resources res = createResources(40, 50, false, 30, 40);
+ DisplayInfo info = createDisplayInfo(1000, 1500, 60, ROTATION_0);
+ DisplayLayout dl = new DisplayLayout(info, res, true, true);
+ assertEquals(new Rect(0, 60, 0, 50), dl.stableInsets());
+ assertEquals(new Rect(0, 60, 0, 50), dl.nonDecorInsets());
+
+ // Rotate to 90
+ dl.rotateTo(res, ROTATION_90);
+ assertEquals(new Rect(60, 30, 0, 40), dl.stableInsets());
+ assertEquals(new Rect(60, 0, 0, 40), dl.nonDecorInsets());
+
+ // Rotate with moving navbar
+ res = createResources(40, 50, true, 30, 40);
+ dl = new DisplayLayout(info, res, true, true);
+ dl.rotateTo(res, ROTATION_270);
+ assertEquals(new Rect(40, 30, 60, 0), dl.stableInsets());
+ assertEquals(new Rect(40, 0, 60, 0), dl.nonDecorInsets());
+ }
+
+ private Resources createResources(
+ int navLand, int navPort, boolean navMoves, int statusLand, int statusPort) {
+ Configuration cfg = new Configuration();
+ cfg.uiMode = UI_MODE_TYPE_NORMAL;
+ Resources res = mock(Resources.class);
+ doReturn(navLand).when(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height_landscape_car_mode);
+ doReturn(navPort).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height_car_mode);
+ doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode);
+ doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height_landscape);
+ doReturn(navPort).when(res).getDimensionPixelSize(R.dimen.navigation_bar_height);
+ doReturn(navLand).when(res).getDimensionPixelSize(R.dimen.navigation_bar_width);
+ doReturn(navMoves).when(res).getBoolean(R.bool.config_navBarCanMove);
+ doReturn(statusLand).when(res).getDimensionPixelSize(R.dimen.status_bar_height_landscape);
+ doReturn(statusPort).when(res).getDimensionPixelSize(R.dimen.status_bar_height_portrait);
+ doReturn(cfg).when(res).getConfiguration();
+ return res;
+ }
+
+ private DisplayInfo createDisplayInfo(int width, int height, int cutoutHeight, int rotation) {
+ DisplayInfo info = new DisplayInfo();
+ info.logicalWidth = width;
+ info.logicalHeight = height;
+ info.rotation = rotation;
+ if (cutoutHeight > 0) {
+ info.displayCutout = new DisplayCutout(
+ Insets.of(0, cutoutHeight, 0, 0) /* safeInsets */, null /* boundLeft */,
+ new Rect(width / 2 - cutoutHeight, 0, width / 2 + cutoutHeight,
+ cutoutHeight) /* boundTop */, null /* boundRight */,
+ null /* boundBottom */);
+ } else {
+ info.displayCutout = DisplayCutout.NO_CUTOUT;
+ }
+ info.logicalDensityDpi = 300;
+ return info;
+ }
+}
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 7e8721d..08552cb 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -29,8 +29,11 @@
"netlink-client",
"networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.control-V1.0-java",
- "tethering-client",
],
+ libs: [
+ "framework-tethering",
+ ],
+
manifest: "AndroidManifestBase.xml",
}
@@ -89,6 +92,10 @@
resource_dirs: [
"res",
],
+ libs: [
+ "framework-tethering",
+ ],
+ jarjar_rules: "jarjar-rules.txt",
optimize: {
proguard_flags_files: ["proguard.flags"],
},
@@ -103,7 +110,6 @@
manifest: "AndroidManifest_InProcess.xml",
// InProcessTethering is a replacement for Tethering
overrides: ["Tethering"],
- // TODO: use PlatformNetworkPermissionConfig.
}
// Updatable tethering packaged as an application
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 1430ed0..87a8c3f 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -17,10 +17,25 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tethering"
+ package="com.android.networkstack.tethering"
android:sharedUserId="android.uid.networkstack">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+ <!-- Permissions must be defined here, and not in the base manifest, as the tethering
+ running in the system server process does not need any permission, and having
+ privileged permissions added would cause crashes on startup unless they are also
+ added to the privileged permissions whitelist for that package. -->
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <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_NETWORK_USAGE_HISTORY" />
+ <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
<application
android:process="com.android.networkstack.process"
android:extractNativeLibs="false"
diff --git a/packages/Tethering/AndroidManifestBase.xml b/packages/Tethering/AndroidManifestBase.xml
index dc013da..fa85f66 100644
--- a/packages/Tethering/AndroidManifestBase.xml
+++ b/packages/Tethering/AndroidManifestBase.xml
@@ -17,7 +17,7 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tethering"
+ package="com.android.networkstack.tethering"
android:versionCode="1"
android:versionName="R-initial">
<application
diff --git a/packages/Tethering/AndroidManifest_InProcess.xml b/packages/Tethering/AndroidManifest_InProcess.xml
index 28d405c..02ea551 100644
--- a/packages/Tethering/AndroidManifest_InProcess.xml
+++ b/packages/Tethering/AndroidManifest_InProcess.xml
@@ -17,16 +17,14 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tethering.inprocess"
+ package="com.android.networkstack.tethering.inprocess"
android:sharedUserId="android.uid.system"
android:process="system">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
<application>
- <!-- TODO: Using MAINLINE_NETWORK_STACK instead of NETWORK_STACK when tethering run in the
- same process with networkstack -->
<service android:name="com.android.server.connectivity.tethering.TetheringService"
android:process="system"
- android:permission="android.permission.NETWORK_STACK">
+ android:permission="android.permission.MAINLINE_NETWORK_STACK">
<intent-filter>
<action android:name="android.net.ITetheringConnector.InProcess"/>
</intent-filter>
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
index bca01ebd..94ef11c 100644
--- a/packages/Tethering/apex/Android.bp
+++ b/packages/Tethering/apex/Android.bp
@@ -15,21 +15,22 @@
//
apex {
- name: "com.android.tethering.apex",
+ name: "com.android.tethering",
+ java_libs: ["framework-tethering"],
apps: ["Tethering"],
manifest: "manifest.json",
- key: "com.android.tethering.apex.key",
+ key: "com.android.tethering.key",
androidManifest: "AndroidManifest.xml",
}
apex_key {
- name: "com.android.tethering.apex.key",
- public_key: "com.android.tethering.apex.avbpubkey",
- private_key: "com.android.tethering.apex.pem",
+ name: "com.android.tethering.key",
+ public_key: "com.android.tethering.avbpubkey",
+ private_key: "com.android.tethering.pem",
}
android_app_certificate {
- name: "com.android.tethering.apex.certificate",
- certificate: "com.android.tethering.apex",
+ name: "com.android.tethering.certificate",
+ certificate: "com.android.tethering",
}
diff --git a/packages/Tethering/apex/AndroidManifest.xml b/packages/Tethering/apex/AndroidManifest.xml
index 7769b79..5c35c51 100644
--- a/packages/Tethering/apex/AndroidManifest.xml
+++ b/packages/Tethering/apex/AndroidManifest.xml
@@ -15,7 +15,7 @@
* limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tethering.apex">
+ package="com.android.tethering">
<!-- APEX does not have classes.dex -->
<application android:hasCode="false" />
<!-- b/145383354: Current minSdk is locked to Q for development cycle, lock it to next version
diff --git a/packages/Tethering/apex/com.android.tethering.apex.avbpubkey b/packages/Tethering/apex/com.android.tethering.apex.avbpubkey
deleted file mode 100644
index 9c87111..0000000
--- a/packages/Tethering/apex/com.android.tethering.apex.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/packages/Tethering/apex/com.android.tethering.apex.pem b/packages/Tethering/apex/com.android.tethering.apex.pem
deleted file mode 100644
index a8cd12e..0000000
--- a/packages/Tethering/apex/com.android.tethering.apex.pem
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIJKQIBAAKCAgEAwloHpMmwszNBEgUVion141BTvF/oJ5g5DlQIYBtmht4tSpc3
-6elWXd+dhMzFxf/RkxSNRsU+dhD11cPKGp9nUYQQGrHEf3xEKwAHJKRMq26TkJ3o
-1TwOO70TaRKKA4ThNiM3VFDX2vy1ijArhZDIBTGVJCUl9HOHiO+ZJG5DKCx3KXbO
-QWz3c+Lbprr1L76dwIsl5kuoAFwgG0J+9BZhHEzIG1lVpGG7RRLxc8eDIxNN/oKT
-gPYBcOxFYqOECKGBBvElf6MxdRv6xG7gooALY2/HDMYUjAJSOosfwzeymugCzMhK
-e+6CSTAaEfUzuVZvMc2qnd1ly7zpLo9x+TOdH5LEVZpSwqmu2n5bqrUnSEAJUvMz
-SSw0YbsLWJZuTiTV7lecSITgqsmwuZyDexDmUkDQChzrTixsQV6S8vsh/FanjWoi
-zBlPneX8Q7/LME3hxHyLbrabxX0zWiyj8iM9h/8Y4mpO/MjEmmavglTAP4J8zrKD
-FBsntCoch9I49IpYBuO6NfKw1h7AUpLf8gARAjFjRxiJVcSgGY/Wt4/pBzJ57T5g
-xPvqxfpPQP0OA2CT8LqqzZIR8jXs8/TquvwLkkY2kRRPXx+azd5oU2A0uonrUY31
-Bc1obfmWPuEMz9bO/i06ETHuWPd4RiUNaB8qEmjYuKJfhv72YNcRwhrAYJECAwEA
-AQKCAgAaQn3b5yCH5fn5zFQPxvpBP35A6ph8mRXEeNg03B7rRCPMe0gjw9JWlrs6
-0Uw7p4gSnmlEUaxR2ZLN0kmBdV5JZlWitbg+HXU8diGA8u4lD6jCloN6JEYsDi0M
-OmQJe6/OV83HB7FStmh1BnMq9dgA06U6IAbT07RRbUY85OUQDYoAQTw3HNkGgHV7
-PrGYROIdvO9fAYPuoIP6Cu8KXee7Iii7gUOQFWBvQdL7+M4gNCCKrevuNc8WCeaK
-IFvbqq67WGPfrhYlo6UrW2vgqPpg8h5r/GuUS0/+9wNQpjrssUKHltxxiFV0PBqZ
-qI7XkPUvPoG6GMsDT0AWeW1F5ZJqEGPN67Xek0BCD0cpUli+nHD0yWGVHtkpHU2D
-qUOZdB2COfBuXRdW1LsYNPg8YjTCPsmGhISLTwiTNcZJeTxoK1y0CcVW9d7Af2aD
-lYzCegscQlXkSZiFj9s90Vd3KdD2XKrH/ADxzsOxQJ89ka004efdQa5/MKs9aChG
-/5XrwBEfN4O92OjY7KqXUAwB7CcVzNymOjD6r07LM24zbkRpwwXlkP0wmjsHBXkh
-8p0ISmY9QRdvhBgYmFmoPWZncM0zym9LI8atBs4CijQ7JjuOQ8HgHg+Se2eppWfe
-t8r6TVkDB8JeNAMxjX9q0G7icf3JjlIrgERZfyXLmpduR9NdkQKCAQEA5rp2fSKh
-RwihHNtJhNktFJuLR9OA++vyfjqhWnB8CrLPo3//LGWW/+WBr8EwXi/76hQpeKlf
-u8SmkTtxIHlTP2Brh2koh1Qf8HKzPHGjZeDFOoVPKHPqe3nV+cv3srd1mS0Eq3BA
-ZFQq+l61f2iiTZKxDroCahNEa8VMzirW6nKb5xhyMPHXgncCUdphHbwAGatas6be
-RUFg4ChH8BwX6jYw7leRUy2K6OqEl0fckT4Laitlb/ezKtwmD4PPE95q5hH0v3SO
-wetHWafiNrOXPn2wQqBrI2y+AfbTjNmQiaIPgcFKAQ7V3n+c3XfGZ9Xfv4L8m/wo
-RZ4ika1zur021QKCAQEA16OUBPA7BnWd+RJFri2kJBG5JZElaV9chO2ZHcXUbFR9
-HIPkWN19bJbki8Ca0w8FUQuS/M7JeeFjoZ194NlczbR899GVmb0X2AUKXilMacs3
-IONxIDczx3KFtsge8ewXRAjQvgE7M3NpmmJfPLPog7spMCbUIxbc3jzjiZgB/J1s
-WytlUTUY/Zy4V1wujkoydgK2KcHcEWG2oIy7EP0RwnL1NhTksXOtBH6+MoRMAT+H
-fcBK6yfJBNBRQzJ0PdkCCLdQPN1VtwRlWjPXZ3ey4fWvZ399wSLUkM2V1jB4GcOZ
-+DAgtwFKs9+HfOdV42GgFWFcjP+bkM3bcdrQFnmYzQKCAQAQnf1KpePXqddwrJpu
-5vVINquhUKpJeoTMcoyMZu2IF7i8nctS9z4Yz/63GcLSBcKu6STTe99ZNqCIdS+A
-lzxXpCoaZoh0tqpWNuyRvd12yOlrfY5l63NH0U6H3xjH1k6x6XwcnMkGcMlnnsqT
-koWd8KKv3NWvrhOPb3ZIou03lWmFC02uGLzcuJWCL6gu7AtVzfGKXspDUqIXgs8r
-i9ptE9oSUFw3EWCfxcQm4RYRn9ZSny1/EufkflZ/Z47Sb4Jjb4ehAlQFw1wwKNcx
-+V07MvIu2j7dHkfQ/GXgDwtJ3lIfljwuN1NP4wD5Mlcnw0+KC3UGBvMfkHQM6eEb
-4eTBAoIBAQDWfZsqHlpX3n431XkB+9wdFJP5ThrMaVJ51mxLNRBKgO/BgV+NFSNA
-9AZ5DCf0cCh1qPGYDYhSd2LGywT+trac1j7Hse0AcxpYgQsDBkk/oic/y3wm80HJ
-zZw7Z2uAb7nkrnATzt24G8CbE+ZvVvScs3oQr06raH5hgGdD4bN4No4lUVECKbKl
-8VFbdBHK7vqqb6AKgQ4JLAygPduE1nTn2bkXBklESS98HSXK0dVYGH0JFFBw/63v
-39Y05ObC7iwbx1tEb1RnKzQ1OQO1o1aHc/35ENNhXOfa8ONtneCYn/ty50xjPCG2
-MU1vbBv+hIjbO3D3vvhaXKk+4svAz0qxAoIBAQC84FJEjKHJHx17jLeoTuDfuxwX
-6bOQrI3nHbtnFRvPrMryWRDtHLv89Zma3o68/n4vTn5+AnvgYMZifOYlTlIPxinH
-tlE+qCD8KBXUlZdrc+5GGM18lp5tF3Ro4LireH+OhiOAWawaSzDIDYdiR6Kz9NU+
-SjcHKjDObeM6iMEukoaRsufMedpUSrnbzMraAJgBZGay1NZs/o8Icl3OySYPZWEK
-MJxVBMXU9QcUp2GEioYd/eNuP9rwyjq/EIUDJbP2vESAe6+FdGbIgvyYTV/gnKaH
-GcvyMNVZbCMp/wCYNonjlu+18m2w+pVs2uuZLqORkrKYhisK83TKxh4YOWJh
------END RSA PRIVATE KEY-----
diff --git a/packages/Tethering/apex/com.android.tethering.apex.pk8 b/packages/Tethering/apex/com.android.tethering.apex.pk8
deleted file mode 100644
index 5663246..0000000
--- a/packages/Tethering/apex/com.android.tethering.apex.pk8
+++ /dev/null
Binary files differ
diff --git a/packages/Tethering/apex/com.android.tethering.apex.x509.pem b/packages/Tethering/apex/com.android.tethering.apex.x509.pem
deleted file mode 100644
index a5e9401..0000000
--- a/packages/Tethering/apex/com.android.tethering.apex.x509.pem
+++ /dev/null
@@ -1,36 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIGMzCCBBugAwIBAgIUXVtoDaXanhs7ma8VIICambMkj5UwDQYJKoZIhvcNAQEL
-BQAwgacxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
-DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
-b2lkMSMwIQYDVQQDDBpjb20uYW5kcm9pZC50ZXRoZXJpbmcuYXBleDEiMCAGCSqG
-SIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAgFw0xOTExMjgwNjU4MTRaGA80
-NzU3MTAyNDA2NTgxNFowgacxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y
-bmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAw
-DgYDVQQLDAdBbmRyb2lkMSMwIQYDVQQDDBpjb20uYW5kcm9pZC50ZXRoZXJpbmcu
-YXBleDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJ
-KoZIhvcNAQEBBQADggIPADCCAgoCggIBANwzufMBdOj9XlNwiX+bXl/94G0DklWW
-nzob0jPlubCFfRqYkjCf2eOd28Mu/O1pOBcvobnrs9OTpGzcHkz2h58L5/0UMVTS
-tBugwCE49XF5FHawqVHNZE+s5tDmnp2cufhNc5HXHY4oZKh80/WVdbcKxiLjSY2T
-PgRAfB6E6XByKD3t1cSsc3liRVKADoJOVDvmF+xnyvSV/SN38bvTQk9aVs95mj0W
-yov6gzXBnqN7iQlvkhcijZBnFWxvoNbJ5KFy1abYOrm+ueXje4BcNhVOeRMb4E9N
-eo7+9k1GEI7TYG7laNNcp7UJ1IXCJzv/wBFKRg3f1HB3unKfx2rtKerDnVsr3o7V
-KProkgRNKNhhQ6opNguiH1YMzKpWMaC988n4AQPryPdIOmVIxIC5jJrixdxgzDXT
-qeiwFiXis291uyls08B03PQFlY9oWaY9P8s+4hIUjB6rLl+XZXsLDtDFxXeJ97NB
-8XZN1gBJoBoLknFs0C4LKpmJZB/EBao9tXV9dL/5lydRo6HzQDpjW8QX06CTUM6z
-Lr3LVelhqbsuZsV42yBKl+/LfrvNjBLEPdSevt2oMrlJW7m4iSNaMtDtJ2Oy8fA5
-WSIgLWuMbkaFDza3JzwiMzxbtbJHYiy6rY7aVywo3Vqwr1+KO3cq4eLQq62zUjRY
-e6KJwvgE2YmpAgMBAAGjUzBRMB0GA1UdDgQWBBQ8h1oF5JfKFmJCN8nfimbUK+IR
-wjAfBgNVHSMEGDAWgBQ8h1oF5JfKFmJCN8nfimbUK+IRwjAPBgNVHRMBAf8EBTAD
-AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAP5hIkAxSyt9hRafMKiFlmXcL277bgoxNd
-qGZYbdcCFjfvM2r0QQcM/K7x2ZslFe7mJSzcyMifpm4YQTEo2yYbzKItXQV+eV1K
-9RNksRGFG9umsdWaSHhfQmcmxtr2nu9rGAgxy5OQFtyXmJPUPEM2cb/YeILwYhuQ
-Ux3kaj/fxGltX1JBag7HnMzCTZK++fRo5nqFVOJQgJH8ZpuzGeM9kZvP1+b55046
-PhSnlqmZoKhG4i5POPvvZvaakh/lM3x/N7lIlSaQpCGf7jmldni4L0/GenULVKzH
-iN73aBfh4GEvE0HRcOoH3L7V6kc3WMMLve0chZBHpoVYbzUJEJOUL4yrmwEehqtf
-xm4vlYg3vqtcE3UnU/UGdMb16t77Nz88LlpBY5ierIt0jZMU0M81ppRhr1uiD2Lj
-091sEA0Bxcw/6Q8QNF2eR7SG7Qwipnms+lw6Vcxve+7DdTrdEA0k3XgpdXp8Ya+2
-PAp9SLVp1UHiGq3qD9Jvm34QmlUWAIUTHZs3DSgs1y3K5eyw/cnzTvUUOljc/n2y
-VF0FFZtJ1dVLrzQ80Ik7apEXpBqkgBGV04/L3QYk4C0/sP+1yk6zjeeeAvDtUcHS
-gLtjAfacQl/kwfVQWfrF7VByLcivApC6EUdvT3cURM5DfZRQ4RcKr1D61VYPnNRH
-+/NVbMObwQ==
------END CERTIFICATE-----
diff --git a/packages/Tethering/apex/com.android.tethering.avbpubkey b/packages/Tethering/apex/com.android.tethering.avbpubkey
new file mode 100644
index 0000000..9a2c017
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.avbpubkey
Binary files differ
diff --git a/packages/Tethering/apex/com.android.tethering.pem b/packages/Tethering/apex/com.android.tethering.pem
new file mode 100644
index 0000000..d4f39ab
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKgIBAAKCAgEA+AWTp03PBRMGt4mVNLt5PDoFFSfmFOVTM7jt5AJXnQMIDsAM
+1cyWGWRridGIpoHAaCALVgW5aRySgi8yV5xP4w0YHcKbfh9M6I9oz4RUo4GQBZfX
++lFIGaLjb6I3tEJxPuxps4sW26Io63ihwTnKeGyADHdHGWDUs9WU0Ml+QTvKrdjy
+qC03M0dehYXILGiA9m+UXwKoKxhWgfDUhWLhDBUtLJLPL4WeqKc9sG9h+zzVqE+8
+LzJsfrodKhTTrLpWOXi6YLRTk8dzsuPz/Nu98sJd1w3fHd20DrmkqsxVhgN1h+nk
+zcPpxyGYIP6qYVZCmIXCwZZNtPeb7y/tOs967VHoZ4Qj7p2tE0CAWFMZFGjA/pcZ
+7fi6CsIuMOYBbj4+wRlJwpG1g5zSJBCjzhv7dZp8S5oXmLShNYOMYEdsPfaZbm08
+3pVY+k8DVf7idcANXNw1lM+sPbE2hp5VuEuVpK+ca5x8hIMpTqJ84wDAjnC1kCwm
+X2xfNvYPKNF58SvqlNCPN8X7hQjoeaEb7w24vCdZMRqeGBmu1GNQvCyzbBO0huQm
+f5CQPrZjPcnoImlP879VPxY4YB6tAjsA/ZLiub9VdT108lCjb5r8criMzpMAA/AQ
+NqQLWFI3M43xPemGBTiIguTYgpRgGcdRZf7XuTgTY5qzQZZuZMVuwaqSD2cCAwEA
+AQKCAgEA0jMvw3BPTrakT7Lb8JgelKt7mUV6WyVMUZ6eh0pw5JIoJxAfEKfWYmjY
+NzKNRMjcv6LA2MP7MplTld/YI6ZHkl+Lm9VOISL39HVuV8mIThbFb+gT1INEvu1t
+IjRyT2SsQ67rmo377mLNmVtgg7mt3kfecjI44MpPGqad/CF4zmKVUKd4aI4BpYUM
+F8+dKf3bpoBEWA2RZwy2bGQmSXHW132vDoLR8y2knL04rCqJ+PrC/WWuULXEe9bS
+VtLV3yMBZq3qD4Fk/+7fILLPGvNFVdPi4htQiChYrM4rP9HzfaO63VieYMF0hR70
+pqoOznXj9Q4QVC9FZmUgFCQjQ1+KhqJw3OldIo0SnvpsLdTO/inKkhQWKC5HlPyh
+/rqvro2j3pTHWPAziuBr+oQPcdVCOlCBZ+B99L1tO7aGktVPEIVQG7G7jlFMBiJ1
+j/kRGk2RTX8RaPQJTnwUqp8mWUV2fwxHiXNadjejA5ZU3eQT2eAOhXl1w6Lv2jEl
+0wMOwPMJGcF77CcqnnWHON8fkxCbAfyy5Uo6Pm9g/Zzecn+ji2sabG7Ge5t0gzdL
+LKRcGoyakN2CrbQ8pxlCTgE4HX5oPY+VuqOf8L3AIWIJBsyLbXHVkL1mqQ/Ed2uz
+zaaSFYUZw81+m/5bl8JLPaIFNPyikZrXTD0YRer3V06XiyP/kYECggEBAP033xeF
+OhgRwkRTjd68hwRJpyHsZDWxHiUqQf6l6yFv5mEE355G2IGI7cZmR2+tUDjQdxLv
+tAZIszTK4PFCdVTeWfGVFbVF84eNWLB124pHDMM79GN/AMcuHnQPR756a8IO1hIy
+4KxIUE1a1PKN5b9IgE5Lu4TZM96HDpFcUAmCT5urdYDmg3++IWT9PYQlGS7Hhiar
+r+Hh646waM8Qx619CwXBqy+Y37+WHVbYqJClr6AcpVMrGA+6cgpskFpZAPLsoy7G
+RSsVfyV8pH2JKm/hzk7XCwIpczxeWQSfpJWZ+oOPFHu+zM60Cdj2UrQyKrNHwew8
++WYe9eCA+MiNBcECggEBAPq/F1vdqROiLv9uzhKb8ybgdL7CmREELiqwK+MvNE9t
+W7lQz7lcWzav+b2n0M+VJBxUWB3XClgoIvA/AllgTgsYXfKAxNakhKLSBoMmvKCW
+HtWcGr/D3RcmacK+DTMWlVS/LuueAFLuH6UmBIUFKc+qA5x7oQecAFALBFupE3G4
+LtAspLBI6P8gRtRav5p2whs9H8qjYcyf2f6liWpkmFITcXvPvAxFHicR6ZJdwZ/S
+PiX2LJQnOpT7L3+2PWnYwzFStb4MkMGlFKcscU9CvS53JcP/J4Asjk0I4zDB2gri
+xzFHPlVzCr2IVVGptKCQ3sdYiMIzQKzEXQHCU8h37ycCggEBAJu8aC48Fz3Edlm1
+ldS+2L9vWSaJEBzhoSu0cMBgZVu8SdGzwKDE69XHVI4oS5lI28UFmaaA3JTc07MN
+cAmSGT2oP2NQkPhbXGsrKLfm1K6YAiZ1Ulp7OwxFth8lYreo7Wt92nV46yuqkhDx
+Y3UGhp39xkPhWiRbvgYHxJLsVqFyjumsK2mq3IeNdVZ6VgJXGsTlnAFeqJ7hZxHs
+N5natSRjeosA0PtGJ57agZLvT8Ue0gREef3LzFGoFwmIOcQHZ4kAt2BGOzZDU17H
+6Rb4bKxBEbT1l2St/5zKXi90zDHicOvG7Q8qiyY6HrBc1wLSs+ZtpLxZx/3h3tFE
+IT6fVUECggEBAMSAQm8Ey76OJ+SXUjk1K50442SnHcs/Cmr7urkEQitImUwl71Pk
+87pst/uP6szypOTqmE9yOTIS6iZ6Sn3+QcriIqWrkhZfwW3Tx7S6A7KZUrq15iSH
++thsiw9JXxC9TvOmC8AsBzb2U6hZncsc28JZCxFztSNAduJDb/vhCVLiMxWDFuDr
+kmR1R+yc3XDQRpeQFDz6QudYEj9EPOc6xD/16sZLaqP2+oVFvVSt0tJLsdaQECle
+gMNGAdhE2eX8MCOUHMc+E6cdlozYAEhMFfO2/cqWR79jq3TlVR3dnOFRDScqHMhc
+KnuTvsELjHkUbvGsCSiff7yk+fop7vy4OJsCggEAPemJdItO2rhib8EofrZdY72I
+oifX1jhPZ1BWD2GKgcx+eVyJGbONBbJVexvvskTfZBvCcAegmgp+sngP6MO6yZkr
+cHMfAJeApYZnshsgXksHGMDtSB50/w1JLrc/nqpxdpy/aTazt0Eu1pLWpze1HFZ/
+Xyu4PcmrU+4P1vN7c396slHMktEvly6QqOn4nfBbGDJ17Ow6X1XFvGjAxQPIDTB+
+6loV14AHymwmqwMrGn84O72rzqyw+41GxW5+oXhOZ4MeXF3u89TBLWvXDpPy/YQU
+EiKpodN0YeEn6Ghzplan8rUha+7TP7AYnS5pCszsCHKd03Py0lMLkF+uAfVsDA==
+-----END RSA PRIVATE KEY-----
diff --git a/packages/Tethering/apex/com.android.tethering.pk8 b/packages/Tethering/apex/com.android.tethering.pk8
new file mode 100644
index 0000000..3b94405
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.pk8
Binary files differ
diff --git a/packages/Tethering/apex/com.android.tethering.x509.pem b/packages/Tethering/apex/com.android.tethering.x509.pem
new file mode 100644
index 0000000..a1786e3
--- /dev/null
+++ b/packages/Tethering/apex/com.android.tethering.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGKTCCBBGgAwIBAgIUNiSs5EMqxCZ31gWWCcRJVp9HffAwDQYJKoZIhvcNAQEL
+BQAwgaIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMR4wHAYDVQQDDBVjb20uYW5kcm9pZC50ZXRoZXJpbmcxIjAgBgkqhkiG9w0B
+CQEWE2FuZHJvaWRAYW5kcm9pZC5jb20wIBcNMTkxMjE4MDcwMDQ4WhgPNDc1NzEx
+MTMwNzAwNDhaMIGiMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEW
+MBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UE
+CwwHQW5kcm9pZDEeMBwGA1UEAwwVY29tLmFuZHJvaWQudGV0aGVyaW5nMSIwIAYJ
+KoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEAxvTUA4seblYjZLfTVNwZuJH914QVNFTj+vD94pWmt5Aq
+sH1DVTpBvpXXegc/P5HI2XF/71poSBib1WaQSuXG0fU5K75T18bOGL0qF+fhMtBO
+wUyvulcjO0h4XE/xf0txY54exUjAA4JS9ERGJOgb4GOwSbPyzekfmzIyCZ2Yawwu
++oGwD2ZNzZRaPOoWxjwohBWQ6mySuvF9RRRb300qmxxUGFM9Ki3aqrWlYlHEOwOC
+M+gIXxYFO7S+yUzf6/gMZLOz2YqfcTOup4hAxtExR7niutxJSsRLPBL237exAJoz
+OupoXjtWAlPK4ZwZ/Nl1jdTWauJ+Kv3WqzhHGEb2gn3ZpeO3IdOjJhDgFJ6m1OT/
+kjRbW1LCuKGrKaoqsEDT2X3a7Izfripn65hSNTfR5gNLtgELaI3/vXi8Fmzw1AfH
++qi6ulElZvSwx0qm+S0QiPyGFlxrsdnHoGJl1tzjJW8KdNZRvzRLUQtbphPp+VkL
+5i0bNKum+AwbfdUkLkNLfw9XdbujgBkZTZDQbZGsNjgrvyXcPO2KiJee0hVCZRs0
+rhDi5Pfm7BnN/I2vaTRz/W4mdct9H2RWMuqlSH90JvmKtWcND8ahmOJ3sggrvzfO
+QNs3k4JTRecamMzqIkylhlnEC4FjWc6Bx4wsEpwBMZOkF/tGGMZYf2C09a8tpP0C
+AwEAAaNTMFEwHQYDVR0OBBYEFNP5gIpNWmq0xa411M1GaRPbEijvMB8GA1UdIwQY
+MBaAFNP5gIpNWmq0xa411M1GaRPbEijvMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
+hvcNAQELBQADggIBADJGmU3QP4EGbt6eBhVPeo/efsqrHsuB2fvFzvIobJbfkSob
+cmvjbzIikOlPAgFWj8lT5SDcIWRorFf1u2JylClJ0nSDcqJMHVKmT7wseV/KtX//
+1yUyJFRQVzmjC89dp8OIc00GmItivKLer3NbJdkR3rTUjg7+bNUO27Qp3AFREmiJ
+P+M7ouvcQRvByUWbp/LOrJpMdJLysRBO562RwrtwTjltdvufyYswbBZOKEiUh1Jc
+Ged+3+SJdhwq3Wy+R3Uj7YE7mUMu1QNbANIMrwF8W93EA53eoL2+cKmuaVU6ZURL
+xgSJaY6TrunnSI9XTROLtjsFlJorYWy2tvG7Q5Hw3OkO2Xdz/mm85VTkiusg9DMB
+WWTv607YtsIO0FhKmcV4bp3q/EkRj3t/zLvL9uFJrWDGkuShZq6fQvqbCvaokOPY
++M0ZRIwgwa9UpEE0BMklVWqR6BGyap614gOgcOjYM70WRNl59Qne+g128ZN7g9nz
+61F70i7kUngV0ZUz1/Fu/NCG+6wGF85ZbFmQl60YHPDw1FtjVUuKyBblaDzdJunx
+yQr2t9RUokzFBFK0lGW3+yf0WDQ5fqTMs5h8bz1FCq8/HzWmpdOfqePLe4zsld3b
+1nFuSohaIfbn/HDdTNtTBGQPgz8ZswQ6ejJJqTLz9D/odbqn9LeIhDZXcQTf
+-----END CERTIFICATE-----
diff --git a/packages/Tethering/apex/manifest.json b/packages/Tethering/apex/manifest.json
index 078302a..538ffb3 100644
--- a/packages/Tethering/apex/manifest.json
+++ b/packages/Tethering/apex/manifest.json
@@ -1,4 +1,4 @@
{
- "name": "com.android.tethering.apex",
+ "name": "com.android.tethering",
"version": 300000000
}
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index adc5a72..5785707 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -12,7 +12,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-//
// AIDL interfaces between the core system and the tethering mainline module.
aidl_interface {
@@ -20,10 +19,7 @@
local_include_dir: "src",
include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
srcs: [
- "src/android/net/ITetherInternalCallback.aidl",
- "src/android/net/ITetheringConnector.aidl",
- "src/android/net/TetheringConfigurationParcel.aidl",
- "src/android/net/TetherStatesParcel.aidl",
+ "src/android/net/*.aidl",
],
backend: {
ndk: {
@@ -36,16 +32,32 @@
}
java_library {
- name: "tethering-client",
+ name: "framework-tethering",
sdk_version: "system_current",
+ srcs: [
+ "src/android/net/TetheringManager.java",
+ ":framework-tethering-annotations",
+ ],
static_libs: [
"tethering-aidl-interfaces-java",
],
+ jarjar_rules: "jarjar-rules.txt",
+ installable: true,
+
+ libs: [
+ "android_system_stubs_current",
+ ],
}
-// This is temporary file group which would be removed after TetheringManager is built
-// into tethering-client. Will be done by aosp/1156906.
filegroup {
- name: "tethering-manager",
- srcs: ["src/android/net/TetheringManager.java"],
+ name: "framework-tethering-srcs",
+ srcs: [
+ "src/android/net/TetheringManager.java",
+ "src/android/net/IIntResultListener.aidl",
+ "src/android/net/ITetheringEventCallback.aidl",
+ "src/android/net/ITetheringConnector.aidl",
+ "src/android/net/TetheringConfigurationParcel.aidl",
+ "src/android/net/TetherStatesParcel.aidl",
+ ],
+ path: "src"
}
diff --git a/packages/Tethering/common/TetheringLib/jarjar-rules.txt b/packages/Tethering/common/TetheringLib/jarjar-rules.txt
new file mode 100644
index 0000000..35e0f88
--- /dev/null
+++ b/packages/Tethering/common/TetheringLib/jarjar-rules.txt
@@ -0,0 +1 @@
+rule android.annotation.** com.android.networkstack.tethering.annotation.@1
diff --git a/core/java/android/net/ITetheringEventCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/IIntResultListener.aidl
similarity index 77%
rename from core/java/android/net/ITetheringEventCallback.aidl
rename to packages/Tethering/common/TetheringLib/src/android/net/IIntResultListener.aidl
index d502088..c3d66ee 100644
--- a/core/java/android/net/ITetheringEventCallback.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/IIntResultListener.aidl
@@ -16,13 +16,10 @@
package android.net;
-import android.net.Network;
-
/**
- * Callback class for receiving tethering changed events
- * @hide
+ * Listener interface allowing objects to listen to various module event.
+ * {@hide}
*/
-oneway interface ITetheringEventCallback
-{
- void onUpstreamChanged(in Network network);
+oneway interface IIntResultListener {
+ void onResult(int resultCode);
}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
index bfe502f..d30c399 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
@@ -15,23 +15,31 @@
*/
package android.net;
-import android.net.ITetherInternalCallback;
+import android.net.IIntResultListener;
+import android.net.ITetheringEventCallback;
import android.os.ResultReceiver;
/** @hide */
oneway interface ITetheringConnector {
- void tether(String iface);
+ void tether(String iface, String callerPkg, IIntResultListener receiver);
- void untether(String iface);
+ void untether(String iface, String callerPkg, IIntResultListener receiver);
- void setUsbTethering(boolean enable);
+ void setUsbTethering(boolean enable, String callerPkg, IIntResultListener receiver);
- void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+ void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi,
+ String callerPkg);
- void stopTethering(int type);
+ void stopTethering(int type, String callerPkg, IIntResultListener receiver);
void requestLatestTetheringEntitlementResult(int type, in ResultReceiver receiver,
- boolean showEntitlementUi);
+ boolean showEntitlementUi, String callerPkg);
- void registerTetherInternalCallback(ITetherInternalCallback callback);
+ void registerTetheringEventCallback(ITetheringEventCallback callback, String callerPkg);
+
+ void unregisterTetheringEventCallback(ITetheringEventCallback callback, String callerPkg);
+
+ void isTetheringSupported(String callerPkg, IIntResultListener receiver);
+
+ void stopAllTethering(String callerPkg, IIntResultListener receiver);
}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
similarity index 83%
rename from packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl
rename to packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index abb00e8..2836195 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/ITetherInternalCallback.aidl
+++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -21,14 +21,15 @@
import android.net.TetherStatesParcel;
/**
- * Callback class for receiving tethering changed events
+ * Callback class for receiving tethering changed events.
* @hide
*/
-oneway interface ITetherInternalCallback
+oneway interface ITetheringEventCallback
{
+ void onCallbackStarted(in Network network, in TetheringConfigurationParcel config,
+ in TetherStatesParcel states);
+ void onCallbackStopped(int errorCode);
void onUpstreamChanged(in Network network);
void onConfigurationChanged(in TetheringConfigurationParcel config);
void onTetherStatesChanged(in TetherStatesParcel states);
- void onCallbackCreated(in Network network, in TetheringConfigurationParcel config,
- in TetherStatesParcel states);
}
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index eb0d443..a49ab85 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -15,94 +15,142 @@
*/
package android.net;
-import static android.Manifest.permission.NETWORK_STACK;
import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.util.SharedLog;
+import android.content.Context;
+import android.net.ConnectivityManager.OnTetheringEventCallback;
import android.os.ConditionVariable;
import android.os.IBinder;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.util.Slog;
+import android.util.ArrayMap;
+import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.PrintWriter;
-import java.util.StringJoiner;
+import java.util.concurrent.Executor;
/**
- * Service used to communicate with the tethering, which is running in a separate module.
+ * This class provides the APIs to control the tethering service.
+ * <p> The primary responsibilities of this class are to provide the APIs for applications to
+ * start tethering, stop tethering, query configuration and query status.
+ *
* @hide
*/
+// TODO: make it @SystemApi
public class TetheringManager {
private static final String TAG = TetheringManager.class.getSimpleName();
+ private static final int DEFAULT_TIMEOUT_MS = 60_000;
private static TetheringManager sInstance;
- @Nullable
- private ITetheringConnector mConnector;
- private TetherInternalCallback mCallback;
- private Network mTetherUpstream;
+ private final ITetheringConnector mConnector;
+ private final TetheringCallbackInternal mCallback;
+ private final Context mContext;
+ private final ArrayMap<OnTetheringEventCallback, ITetheringEventCallback>
+ mTetheringEventCallbacks = new ArrayMap<>();
+
private TetheringConfigurationParcel mTetheringConfiguration;
private TetherStatesParcel mTetherStatesParcel;
- private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
- new RemoteCallbackList<>();
- @GuardedBy("mLog")
- private final SharedLog mLog = new SharedLog(TAG);
-
- private TetheringManager() { }
+ 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;
+ public static final int TETHER_ERROR_UNSUPPORTED = 3;
+ public static final int TETHER_ERROR_UNAVAIL_IFACE = 4;
+ public static final int TETHER_ERROR_MASTER_ERROR = 5;
+ public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
+ public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
+ public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8;
+ public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9;
+ public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
+ public static final int TETHER_ERROR_PROVISION_FAILED = 11;
+ public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12;
+ public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
+ public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14;
+ public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15;
/**
- * Get the TetheringManager singleton instance.
+ * Create a TetheringManager object for interacting with the tethering service.
*/
- public static synchronized TetheringManager getInstance() {
- if (sInstance == null) {
- sInstance = new TetheringManager();
- }
- return sInstance;
- }
+ public TetheringManager(@NonNull final Context context, @NonNull final IBinder service) {
+ mContext = context;
+ mConnector = ITetheringConnector.Stub.asInterface(service);
+ mCallback = new TetheringCallbackInternal();
- private class TetheringConnection implements
- ConnectivityModuleConnector.ModuleServiceCallback {
- @Override
- public void onModuleServiceConnected(@NonNull IBinder service) {
- logi("Tethering service connected");
- registerTetheringService(service);
- }
- }
-
- private void registerTetheringService(@NonNull IBinder service) {
- final ITetheringConnector connector = ITetheringConnector.Stub.asInterface(service);
-
- log("Tethering service registered");
-
- // Currently TetheringManager instance is only used by ConnectivityService and mConnector
- // only expect to assign once when system server start and bind tethering service.
- // STOPSHIP: Change mConnector to final before TetheringManager put into boot classpath.
- mConnector = connector;
- mCallback = new TetherInternalCallback();
+ final String pkgName = mContext.getOpPackageName();
+ Log.i(TAG, "registerTetheringEventCallback:" + pkgName);
try {
- mConnector.registerTetherInternalCallback(mCallback);
+ mConnector.registerTetheringEventCallback(mCallback, pkgName);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw new IllegalStateException(e);
}
}
- private class TetherInternalCallback extends ITetherInternalCallback.Stub {
- private final ConditionVariable mWaitForCallback = new ConditionVariable(false);
- private static final int EVENT_CALLBACK_TIMEOUT_MS = 60_000;
+ private interface RequestHelper {
+ void runRequest(IIntResultListener listener);
+ }
+
+ private class RequestDispatcher {
+ private final ConditionVariable mWaiting;
+ public int mRemoteResult;
+
+ private final IIntResultListener mListener = new IIntResultListener.Stub() {
+ @Override
+ public void onResult(final int resultCode) {
+ mRemoteResult = resultCode;
+ mWaiting.open();
+ }
+ };
+
+ RequestDispatcher() {
+ mWaiting = new ConditionVariable();
+ }
+
+ int waitForResult(final RequestHelper request) {
+ request.runRequest(mListener);
+ if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) {
+ throw new IllegalStateException("Callback timeout");
+ }
+
+ throwIfPermissionFailure(mRemoteResult);
+
+ return mRemoteResult;
+ }
+ }
+
+ private void throwIfPermissionFailure(final int errorCode) {
+ switch (errorCode) {
+ case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION:
+ throw new SecurityException("No android.permission.TETHER_PRIVILEGED"
+ + " or android.permission.WRITE_SETTINGS permission");
+ case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION:
+ throw new SecurityException(
+ "No android.permission.ACCESS_NETWORK_STATE permission");
+ }
+ }
+
+ private class TetheringCallbackInternal extends ITetheringEventCallback.Stub {
+ private int mError = TETHER_ERROR_NO_ERROR;
+ private final ConditionVariable mWaitForCallback = new ConditionVariable();
@Override
- public void onUpstreamChanged(Network network) {
- mTetherUpstream = network;
- reportUpstreamChanged(network);
+ public void onCallbackStarted(Network network, TetheringConfigurationParcel config,
+ TetherStatesParcel states) {
+ mTetheringConfiguration = config;
+ mTetherStatesParcel = states;
+ mWaitForCallback.open();
}
@Override
+ public void onCallbackStopped(int errorCode) {
+ mError = errorCode;
+ mWaitForCallback.open();
+ }
+
+ @Override
+ public void onUpstreamChanged(Network network) { }
+
+ @Override
public void onConfigurationChanged(TetheringConfigurationParcel config) {
mTetheringConfiguration = config;
}
@@ -112,49 +160,10 @@
mTetherStatesParcel = states;
}
- @Override
- public void onCallbackCreated(Network network, TetheringConfigurationParcel config,
- TetherStatesParcel states) {
- mTetherUpstream = network;
- mTetheringConfiguration = config;
- mTetherStatesParcel = states;
- mWaitForCallback.open();
+ public void waitForStarted() {
+ mWaitForCallback.block(DEFAULT_TIMEOUT_MS);
+ throwIfPermissionFailure(mError);
}
-
- boolean awaitCallbackCreation() {
- return mWaitForCallback.block(EVENT_CALLBACK_TIMEOUT_MS);
- }
- }
-
- private void reportUpstreamChanged(Network network) {
- final int length = mTetheringEventCallbacks.beginBroadcast();
- try {
- for (int i = 0; i < length; i++) {
- try {
- mTetheringEventCallbacks.getBroadcastItem(i).onUpstreamChanged(network);
- } catch (RemoteException e) {
- // Not really very much to do here.
- }
- }
- } finally {
- mTetheringEventCallbacks.finishBroadcast();
- }
- }
-
- /**
- * Start the tethering service. Should be called only once on device startup.
- *
- * <p>This method will start the tethering service either in the network stack process,
- * or inside the system server on devices that do not support the tethering module.
- *
- * {@hide}
- */
- public void start() {
- // Using MAINLINE_NETWORK_STACK permission after cutting off the dpendency of system server.
- ConnectivityModuleConnector.getInstance().startModuleService(
- ITetheringConnector.class.getName(), NETWORK_STACK,
- new TetheringConnection());
- log("Tethering service start requested");
}
/**
@@ -164,88 +173,110 @@
* IP network interface is available, dhcp will still run and traffic will be
* allowed between the tethered devices and this device, though upstream net
* access will of course fail until an upstream network interface becomes
- * active. Note: return value do not have any meaning. It is better to use
- * #getTetherableIfaces() to ensure corresponding interface is available for
- * tethering before calling #tether().
+ * active.
*
- * @deprecated The only usages should be in PanService and Wifi P2P which
- * need direct access.
+ * @deprecated The only usages is PanService. It uses this for legacy reasons
+ * and will migrate away as soon as possible.
*
- * {@hide}
+ * @param iface the interface name to tether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
*/
@Deprecated
- public int tether(@NonNull String iface) {
- try {
- mConnector.tether(iface);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return TETHER_ERROR_NO_ERROR;
+ public int tether(@NonNull final String iface) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "tether caller:" + callerPkg);
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+
+ return dispatcher.waitForResult(listener -> {
+ try {
+ mConnector.tether(iface, callerPkg, listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
}
/**
* Stop tethering the named interface.
*
- * @deprecated
- * {@hide}
+ * @deprecated The only usages is PanService. It uses this for legacy reasons
+ * and will migrate away as soon as possible.
*/
@Deprecated
- public int untether(@NonNull String iface) {
- try {
- mConnector.untether(iface);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return TETHER_ERROR_NO_ERROR;
+ public int untether(@NonNull final String iface) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "untether caller:" + callerPkg);
+
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+
+ return dispatcher.waitForResult(listener -> {
+ try {
+ mConnector.untether(iface, callerPkg, listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
}
/**
- * Attempt to both alter the mode of USB and Tethering of USB. WARNING: New client should not
- * use this API anymore. All clients should use #startTethering or #stopTethering which
- * encapsulate proper entitlement logic. If the API is used and an entitlement check is needed,
- * downstream USB tethering will be enabled but will not have any upstream.
+ * Attempt to both alter the mode of USB and Tethering of USB.
*
- * @deprecated
- * {@hide}
+ * @deprecated New client should not use this API anymore. All clients should use
+ * #startTethering or #stopTethering which encapsulate proper entitlement logic. If the API is
+ * used and an entitlement check is needed, downstream USB tethering will be enabled but will
+ * not have any upstream.
*/
@Deprecated
- public int setUsbTethering(boolean enable) {
- try {
- mConnector.setUsbTethering(enable);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- return TETHER_ERROR_NO_ERROR;
+ public int setUsbTethering(final boolean enable) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "setUsbTethering caller:" + callerPkg);
+
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+
+ return dispatcher.waitForResult(listener -> {
+ try {
+ mConnector.setUsbTethering(enable, callerPkg, listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
}
/**
* Starts tethering and runs tether provisioning for the given type if needed. If provisioning
* fails, stopTethering will be called automatically.
*
- * {@hide}
*/
// TODO: improve the usage of ResultReceiver, b/145096122
- public void startTethering(int type, @NonNull ResultReceiver receiver,
- boolean showProvisioningUi) {
+ public void startTethering(final int type, @NonNull final ResultReceiver receiver,
+ final boolean showProvisioningUi) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "startTethering caller:" + callerPkg);
+
try {
- mConnector.startTethering(type, receiver, showProvisioningUi);
+ mConnector.startTethering(type, receiver, showProvisioningUi, callerPkg);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw new IllegalStateException(e);
}
}
/**
* Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
* applicable.
- *
- * {@hide}
*/
- public void stopTethering(int type) {
- try {
- mConnector.stopTethering(type);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ public void stopTethering(final int type) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "stopTethering caller:" + callerPkg);
+
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+
+ dispatcher.waitForResult(listener -> {
+ try {
+ mConnector.stopTethering(type, callerPkg, listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
}
/**
@@ -256,43 +287,109 @@
* if it's really needed.
*/
// TODO: improve the usage of ResultReceiver, b/145096122
- public void requestLatestTetheringEntitlementResult(int type, @NonNull ResultReceiver receiver,
- boolean showEntitlementUi) {
+ public void requestLatestTetheringEntitlementResult(final int type,
+ @NonNull final ResultReceiver receiver, final boolean showEntitlementUi) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "getLatestTetheringEntitlementResult caller:" + callerPkg);
+
try {
- mConnector.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+ mConnector.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi,
+ callerPkg);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw new IllegalStateException(e);
}
}
/**
- * Register tethering event callback.
+ * Start listening to tethering change events. Any new added callback will receive the last
+ * tethering status right away. If callback is registered,
+ * {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called. If tethering
+ * has no upstream or disabled, the argument of callback will be null. The same callback object
+ * cannot be registered twice.
*
- * {@hide}
+ * @param executor the executor on which callback will be invoked.
+ * @param callback the callback to be called when tethering has change events.
*/
- public void registerTetheringEventCallback(@NonNull ITetheringEventCallback callback) {
- mTetheringEventCallbacks.register(callback);
+ public void registerTetheringEventCallback(@NonNull Executor executor,
+ @NonNull OnTetheringEventCallback callback) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg);
+
+ synchronized (mTetheringEventCallbacks) {
+ if (!mTetheringEventCallbacks.containsKey(callback)) {
+ throw new IllegalArgumentException("callback was already registered.");
+ }
+ final ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() {
+ @Override
+ public void onUpstreamChanged(Network network) throws RemoteException {
+ executor.execute(() -> {
+ callback.onUpstreamChanged(network);
+ });
+ }
+
+ @Override
+ public void onCallbackStarted(Network network, TetheringConfigurationParcel config,
+ TetherStatesParcel states) {
+ executor.execute(() -> {
+ callback.onUpstreamChanged(network);
+ });
+ }
+
+ @Override
+ public void onCallbackStopped(int errorCode) {
+ executor.execute(() -> {
+ throwIfPermissionFailure(errorCode);
+ });
+ }
+
+ @Override
+ public void onConfigurationChanged(TetheringConfigurationParcel config) { }
+
+ @Override
+ public void onTetherStatesChanged(TetherStatesParcel states) { }
+ };
+ try {
+ mConnector.registerTetheringEventCallback(remoteCallback, callerPkg);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ mTetheringEventCallbacks.put(callback, remoteCallback);
+ }
}
/**
- * Unregister tethering event callback.
+ * Remove tethering event callback previously registered with
+ * {@link #registerTetheringEventCallback}.
*
- * {@hide}
+ * @param callback previously registered callback.
*/
- public void unregisterTetheringEventCallback(@NonNull ITetheringEventCallback callback) {
- mTetheringEventCallbacks.unregister(callback);
+ public void unregisterTetheringEventCallback(@NonNull final OnTetheringEventCallback callback) {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg);
+
+ synchronized (mTetheringEventCallbacks) {
+ ITetheringEventCallback remoteCallback = mTetheringEventCallbacks.remove(callback);
+ if (remoteCallback == null) {
+ throw new IllegalArgumentException("callback was not registered.");
+ }
+ try {
+ mConnector.unregisterTetheringEventCallback(remoteCallback, callerPkg);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ }
}
/**
* Get a more detailed error code after a Tethering or Untethering
* request asynchronously failed.
*
- * {@hide}
+ * @param iface The name of the interface of interest
+ * @return error The error code of the last error tethering or untethering the named
+ * interface
*/
- public int getLastTetherError(@NonNull String iface) {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
+ public int getLastTetherError(@NonNull final String iface) {
+ mCallback.waitForStarted();
if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
int i = 0;
@@ -309,12 +406,11 @@
* USB network interfaces. If USB tethering is not supported by the
* device, this list should be empty.
*
- * {@hide}
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable usb interfaces.
*/
public @NonNull String[] getTetherableUsbRegexs() {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
+ mCallback.waitForStarted();
return mTetheringConfiguration.tetherableUsbRegexs;
}
@@ -323,12 +419,11 @@
* Wifi network interfaces. If Wifi tethering is not supported by the
* device, this list should be empty.
*
- * {@hide}
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable wifi interfaces.
*/
public @NonNull String[] getTetherableWifiRegexs() {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
+ mCallback.waitForStarted();
return mTetheringConfiguration.tetherableWifiRegexs;
}
@@ -337,12 +432,11 @@
* Bluetooth network interfaces. If Bluetooth tethering is not supported by the
* device, this list should be empty.
*
- * {@hide}
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable bluetooth interfaces.
*/
public @NonNull String[] getTetherableBluetoothRegexs() {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
+ mCallback.waitForStarted();
return mTetheringConfiguration.tetherableBluetoothRegexs;
}
@@ -350,40 +444,42 @@
* Get the set of tetherable, available interfaces. This list is limited by
* device configuration and current interface existence.
*
- * {@hide}
+ * @return an array of 0 or more Strings of tetherable interface names.
*/
public @NonNull String[] getTetherableIfaces() {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
+ mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
+
return mTetherStatesParcel.availableList;
}
/**
* Get the set of tethered interfaces.
*
- * {@hide}
+ * @return an array of 0 or more String of currently tethered interface names.
*/
public @NonNull String[] getTetheredIfaces() {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
+ mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
+
return mTetherStatesParcel.tetheredList;
}
/**
* Get the set of interface names which attempted to tether but
- * failed.
+ * 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
+ * information on the cause of the errors.
*
- * {@hide}
+ * @return an array of 0 or more String indicating the interface names
+ * which failed to tether.
*/
public @NonNull String[] getTetheringErroredIfaces() {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
+ mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
+
return mTetherStatesParcel.erroredIfaceList;
}
@@ -391,123 +487,49 @@
* Get the set of tethered dhcp ranges.
*
* @deprecated This API just return the default value which is not used in DhcpServer.
- * {@hide}
*/
@Deprecated
public @NonNull String[] getTetheredDhcpRanges() {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
+ mCallback.waitForStarted();
return mTetheringConfiguration.legacyDhcpRanges;
}
/**
- * Check if the device allows for tethering.
+ * Check if the device allows for tethering. It may be disabled via
+ * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
+ * due to device configuration.
*
- * {@hide}
+ * @return a boolean - {@code true} indicating Tethering is supported.
*/
- public boolean hasTetherableConfiguration() {
- if (!mCallback.awaitCallbackCreation()) {
- throw new NullPointerException("callback was not ready yet");
- }
- final boolean hasDownstreamConfiguration =
- (mTetheringConfiguration.tetherableUsbRegexs.length != 0)
- || (mTetheringConfiguration.tetherableWifiRegexs.length != 0)
- || (mTetheringConfiguration.tetherableBluetoothRegexs.length != 0);
- final boolean hasUpstreamConfiguration =
- (mTetheringConfiguration.preferredUpstreamIfaceTypes.length != 0)
- || mTetheringConfiguration.chooseUpstreamAutomatically;
+ public boolean isTetheringSupported() {
+ final String callerPkg = mContext.getOpPackageName();
- return hasDownstreamConfiguration && hasUpstreamConfiguration;
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+ final int ret = dispatcher.waitForResult(listener -> {
+ try {
+ mConnector.isTetheringSupported(callerPkg, listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
+
+ return ret == TETHER_ERROR_NO_ERROR;
}
/**
- * Log a message in the local log.
+ * Stop all active tethering.
*/
- private void log(@NonNull String message) {
- synchronized (mLog) {
- mLog.log(message);
- }
- }
+ public void stopAllTethering() {
+ final String callerPkg = mContext.getOpPackageName();
+ Log.i(TAG, "stopAllTethering caller:" + callerPkg);
- /**
- * Log a condition that should never happen.
- */
- private void logWtf(@NonNull String message, @Nullable Throwable e) {
- Slog.wtf(TAG, message);
- synchronized (mLog) {
- mLog.e(message, e);
- }
- }
-
- /**
- * Log a ERROR level message in the local and system logs.
- */
- private void loge(@NonNull String message, @Nullable Throwable e) {
- synchronized (mLog) {
- mLog.e(message, e);
- }
- }
-
- /**
- * Log a INFO level message in the local and system logs.
- */
- private void logi(@NonNull String message) {
- synchronized (mLog) {
- mLog.i(message);
- }
- }
-
- /**
- * Dump TetheringManager logs to the specified {@link PrintWriter}.
- */
- public void dump(@NonNull PrintWriter pw) {
- // dump is thread-safe on SharedLog
- mLog.dump(null, pw, null);
-
- pw.print("subId: ");
- pw.println(mTetheringConfiguration.subId);
-
- dumpStringArray(pw, "tetherableUsbRegexs",
- mTetheringConfiguration.tetherableUsbRegexs);
- dumpStringArray(pw, "tetherableWifiRegexs",
- mTetheringConfiguration.tetherableWifiRegexs);
- dumpStringArray(pw, "tetherableBluetoothRegexs",
- mTetheringConfiguration.tetherableBluetoothRegexs);
-
- pw.print("isDunRequired: ");
- pw.println(mTetheringConfiguration.isDunRequired);
-
- pw.print("chooseUpstreamAutomatically: ");
- pw.println(mTetheringConfiguration.chooseUpstreamAutomatically);
-
- dumpStringArray(pw, "legacyDhcpRanges", mTetheringConfiguration.legacyDhcpRanges);
- dumpStringArray(pw, "defaultIPv4DNS", mTetheringConfiguration.defaultIPv4DNS);
-
- dumpStringArray(pw, "provisioningApp", mTetheringConfiguration.provisioningApp);
- pw.print("provisioningAppNoUi: ");
- pw.println(mTetheringConfiguration.provisioningAppNoUi);
-
- pw.print("enableLegacyDhcpServer: ");
- pw.println(mTetheringConfiguration.enableLegacyDhcpServer);
-
- pw.println();
- }
-
- private static void dumpStringArray(@NonNull PrintWriter pw, @NonNull String label,
- @Nullable String[] values) {
- pw.print(label);
- pw.print(": ");
-
- if (values != null) {
- final StringJoiner sj = new StringJoiner(", ", "[", "]");
- for (String value : values) sj.add(value);
-
- pw.print(sj.toString());
- } else {
- pw.print("null");
- }
-
- pw.println();
+ final RequestDispatcher dispatcher = new RequestDispatcher();
+ dispatcher.waitForResult(listener -> {
+ try {
+ mConnector.stopAllTethering(callerPkg, listener);
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e);
+ }
+ });
}
}
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
new file mode 100644
index 0000000..dd9eab7
--- /dev/null
+++ b/packages/Tethering/jarjar-rules.txt
@@ -0,0 +1,15 @@
+# These must be kept in sync with the framework-tethering-shared-srcs filegroup.
+# If there are files in that filegroup that do not appear here, the classes in the
+# module will be overwritten by the ones in the framework.
+# Don't jar-jar the entire package because tethering still use some internal classes
+# (like TrafficStatsConstants in com.android.internal.util)
+# TODO: simply these when tethering is built as system_current.
+rule com.android.internal.util.BitUtils* com.android.networkstack.tethering.util.BitUtils@1
+rule com.android.internal.util.IndentingPrintWriter.java* com.android.networkstack.tethering.util.IndentingPrintWriter.java@1
+rule com.android.internal.util.IState.java* com.android.networkstack.tethering.util.IState.java@1
+rule com.android.internal.util.MessageUtils* com.android.networkstack.tethering.util.MessageUtils@1
+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 android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
diff --git a/packages/Tethering/proguard.flags b/packages/Tethering/proguard.flags
index 77fc024..1f83a66 100644
--- a/packages/Tethering/proguard.flags
+++ b/packages/Tethering/proguard.flags
@@ -1 +1,9 @@
-#TBD
+# Keep class's integer static field for MessageUtils to parsing their name.
+-keep class com.android.server.connectivity.tethering.Tethering$TetherMasterSM {
+ static final int CMD_*;
+ static final int EVENT_*;
+}
+
+-keepclassmembers class android.net.ip.IpServer {
+ static final int CMD_*;
+}
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index ff3d7bc..8fde520 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -30,7 +30,6 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
-import android.net.NetworkStackClient;
import android.net.RouteInfo;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -122,7 +121,7 @@
* @param state one of STATE_*
* @param lastError one of ConnectivityManager.TETHER_ERROR_*
*/
- public void updateInterfaceState(IpServer who, int state, int lastError) {}
+ public void updateInterfaceState(IpServer who, int state, int lastError) { }
/**
* Notify that |who| has new LinkProperties.
@@ -130,11 +129,11 @@
* @param who the calling instance of IpServer
* @param newLp the new LinkProperties to report
*/
- public void updateLinkProperties(IpServer who, LinkProperties newLp) {}
+ public void updateLinkProperties(IpServer who, LinkProperties newLp) { }
}
/** Capture IpServer dependencies, for injection. */
- public static class Dependencies {
+ public abstract static class Dependencies {
/** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/
public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
return new RouterAdvertisementDaemon(ifParams);
@@ -149,13 +148,9 @@
return NetdService.getInstance();
}
- /**
- * Create a DhcpServer instance to be used by IpServer.
- */
- public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
- DhcpServerCallbacks cb) {
- NetworkStackClient.getInstance().makeDhcpServer(ifName, params, cb);
- }
+ /** 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;
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 7c78ef8..a68b9b2 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.tethering;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
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;
@@ -63,7 +64,7 @@
import android.net.INetd;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
-import android.net.ITetherInternalCallback;
+import android.net.ITetheringEventCallback;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -89,6 +90,7 @@
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.UserHandle;
@@ -103,13 +105,12 @@
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.DumpUtils;
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.tethering.R;
+import com.android.networkstack.tethering.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -162,6 +163,8 @@
}
private final SharedLog mLog = new SharedLog(TAG);
+ private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
+ new RemoteCallbackList<>();
// used to synchronize public access to members
private final Object mPublicSync;
@@ -188,8 +191,8 @@
private final NetdCallback mNetdCallback;
private final UserRestrictionActionListener mTetheringRestriction;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
- // All the usage of mTetherInternalCallback should run in the same thread.
- private ITetherInternalCallback mTetherInternalCallback = null;
+ // All the usage of mTetheringEventCallback should run in the same thread.
+ private ITetheringEventCallback mTetheringEventCallback = null;
private volatile TetheringConfiguration mConfig;
private InterfaceSet mCurrentUpstreamIfaceSet;
@@ -456,7 +459,7 @@
mLog.e("setWifiTethering: failed to get WifiManager!");
return TETHER_ERROR_SERVICE_UNAVAIL;
}
- if ((enable && mgr.startSoftAp(null /* use existing wifi config */))
+ if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */))
|| (!enable && mgr.stopSoftAp())) {
mWifiTetherRequested = enable;
return TETHER_ERROR_NO_ERROR;
@@ -573,6 +576,11 @@
}
}
+ boolean isTetherProvisioningRequired() {
+ final TetheringConfiguration cfg = mConfig;
+ return mEntitlementMgr.isTetherProvisioningRequired(cfg);
+ }
+
// TODO: Figure out how to update for local hotspot mode interfaces.
private void sendTetherStateChangedBroadcast() {
if (!mDeps.isTetheringSupported()) return;
@@ -588,7 +596,7 @@
boolean bluetoothTethered = false;
final TetheringConfiguration cfg = mConfig;
- final TetherStatesParcel mTetherStatesParcel = new TetherStatesParcel();
+ mTetherStatesParcel = new TetherStatesParcel();
synchronized (mPublicSync) {
for (int i = 0; i < mTetherStates.size(); i++) {
@@ -1796,47 +1804,67 @@
}
/** Register tethering event callback */
- void registerTetherInternalCallback(ITetherInternalCallback callback) {
+ void registerTetheringEventCallback(ITetheringEventCallback callback) {
mHandler.post(() -> {
- mTetherInternalCallback = callback;
+ mTetheringEventCallbacks.register(callback);
try {
- mTetherInternalCallback.onCallbackCreated(mTetherUpstream,
- mConfig.toStableParcelable(), mTetherStatesParcel);
+ callback.onCallbackStarted(mTetherUpstream, mConfig.toStableParcelable(),
+ mTetherStatesParcel);
} catch (RemoteException e) {
// Not really very much to do here.
}
});
}
- private void reportUpstreamChanged(Network network) {
- // Don't need to synchronized mTetherInternalCallback because all the usage of this variable
- // should run at the same thread.
- if (mTetherInternalCallback == null) return;
+ /** Unregister tethering event callback */
+ void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
+ mHandler.post(() -> {
+ mTetheringEventCallbacks.unregister(callback);
+ });
+ }
+ private void reportUpstreamChanged(Network network) {
+ final int length = mTetheringEventCallbacks.beginBroadcast();
try {
- mTetherInternalCallback.onUpstreamChanged(network);
- } catch (RemoteException e) {
- // Not really very much to do here.
+ for (int i = 0; i < length; i++) {
+ try {
+ mTetheringEventCallbacks.getBroadcastItem(i).onUpstreamChanged(network);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
}
}
private void reportConfigurationChanged(TetheringConfigurationParcel config) {
- if (mTetherInternalCallback == null) return;
-
+ final int length = mTetheringEventCallbacks.beginBroadcast();
try {
- mTetherInternalCallback.onConfigurationChanged(config);
- } catch (RemoteException e) {
- // Not really very much to do here.
+ for (int i = 0; i < length; i++) {
+ try {
+ mTetheringEventCallbacks.getBroadcastItem(i).onConfigurationChanged(config);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
}
}
private void reportTetherStateChanged(TetherStatesParcel states) {
- if (mTetherInternalCallback == null) return;
-
+ final int length = mTetheringEventCallbacks.beginBroadcast();
try {
- mTetherInternalCallback.onTetherStatesChanged(states);
- } catch (RemoteException e) {
- // Not really very much to do here.
+ for (int i = 0; i < length; i++) {
+ try {
+ mTetheringEventCallbacks.getBroadcastItem(i).onTetherStatesChanged(states);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
}
}
@@ -1844,7 +1872,11 @@
// Binder.java closes the resource for us.
@SuppressWarnings("resource")
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump.");
+ return;
+ }
pw.println("Tethering:");
pw.increaseIndent();
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 0ba8412..b16b329 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -39,7 +39,7 @@
*
* @hide
*/
-public class TetheringDependencies {
+public abstract class TetheringDependencies {
/**
* Get a reference to the offload hardware interface to be used by tethering.
*/
@@ -66,9 +66,7 @@
/**
* Get dependencies to be used by IpServer.
*/
- public IpServer.Dependencies getIpServerDependencies() {
- return new IpServer.Dependencies();
- }
+ public abstract IpServer.Dependencies getIpServerDependencies();
/**
* Indicates whether tethering is supported on the device.
@@ -80,9 +78,7 @@
/**
* Get the NetworkRequest that should be fulfilled by the default network.
*/
- public NetworkRequest getDefaultNetworkRequest() {
- return null;
- }
+ public abstract NetworkRequest getDefaultNetworkRequest();
/**
* Get a reference to the EntitlementManager to be used by tethering.
@@ -138,14 +134,10 @@
/**
* Get tethering thread looper.
*/
- public Looper getTetheringLooper() {
- return null;
- }
+ public abstract Looper getTetheringLooper();
/**
* Get Context of TetheringSerice.
*/
- public Context getContext() {
- return null;
- }
+ public abstract Context getContext();
}
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 456f2f7..ba30845 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -16,20 +16,36 @@
package com.android.server.connectivity.tethering;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
+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 android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
-import android.net.ITetherInternalCallback;
+import android.net.IIntResultListener;
+import android.net.INetworkStackConnector;
import android.net.ITetheringConnector;
+import android.net.ITetheringEventCallback;
import android.net.NetworkRequest;
+import android.net.dhcp.DhcpServerCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.ip.IpServer;
import android.net.util.SharedLog;
+import android.os.Binder;
import android.os.HandlerThread;
import android.os.IBinder;
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;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -52,6 +68,7 @@
private Context mContext;
private TetheringDependencies mDeps;
private Tethering mTethering;
+ private UserManager mUserManager;
@Override
public void onCreate() {
@@ -59,6 +76,7 @@
mDeps = getTetheringDependencies();
mContext = mDeps.getContext();
mTethering = makeTethering(mDeps);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
/**
@@ -74,7 +92,7 @@
*/
private synchronized IBinder makeConnector() {
if (mConnector == null) {
- mConnector = new TetheringConnector(mTethering);
+ mConnector = new TetheringConnector(mTethering, TetheringService.this);
}
return mConnector;
}
@@ -87,55 +105,197 @@
}
private static class TetheringConnector extends ITetheringConnector.Stub {
- private final Tethering mService;
+ private final TetheringService mService;
+ private final Tethering mTethering;
- TetheringConnector(Tethering tether) {
- mService = tether;
+ TetheringConnector(Tethering tether, TetheringService service) {
+ mTethering = tether;
+ mService = service;
}
@Override
- public void tether(String iface) {
- mService.tether(iface);
+ public void tether(String iface, String callerPkg, IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, listener)) return;
+
+ try {
+ listener.onResult(mTethering.tether(iface));
+ } catch (RemoteException e) { }
}
@Override
- public void untether(String iface) {
- mService.untether(iface);
+ public void untether(String iface, String callerPkg, IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, listener)) return;
+
+ try {
+ listener.onResult(mTethering.untether(iface));
+ } catch (RemoteException e) { }
}
@Override
- public void setUsbTethering(boolean enable) {
- mService.setUsbTethering(enable);
+ public void setUsbTethering(boolean enable, String callerPkg, IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, listener)) return;
+
+ try {
+ listener.onResult(mTethering.setUsbTethering(enable));
+ } catch (RemoteException e) { }
}
@Override
- public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
- mService.startTethering(type, receiver, showProvisioningUi);
+ public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi,
+ String callerPkg) {
+ if (checkAndNotifyCommonError(callerPkg, receiver)) return;
+
+ mTethering.startTethering(type, receiver, showProvisioningUi);
}
@Override
- public void stopTethering(int type) {
- mService.stopTethering(type);
+ public void stopTethering(int type, String callerPkg, IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, listener)) return;
+
+ try {
+ mTethering.stopTethering(type);
+ listener.onResult(TETHER_ERROR_NO_ERROR);
+ } catch (RemoteException e) { }
}
@Override
public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
- boolean showEntitlementUi) {
- mService.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
+ boolean showEntitlementUi, String callerPkg) {
+ if (checkAndNotifyCommonError(callerPkg, receiver)) return;
+
+ mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
}
@Override
- public void registerTetherInternalCallback(ITetherInternalCallback callback) {
- mService.registerTetherInternalCallback(callback);
+ public void registerTetheringEventCallback(ITetheringEventCallback callback,
+ String callerPkg) {
+ try {
+ if (!mService.hasTetherAccessPermission()) {
+ callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
+ return;
+ }
+ mTethering.registerTetheringEventCallback(callback);
+ } catch (RemoteException e) { }
}
+
+ @Override
+ public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
+ String callerPkg) {
+ try {
+ if (!mService.hasTetherAccessPermission()) {
+ callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
+ return;
+ }
+ mTethering.unregisterTetheringEventCallback(callback);
+ } catch (RemoteException e) { }
+ }
+
+ @Override
+ public void stopAllTethering(String callerPkg, IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, listener)) return;
+
+ try {
+ mTethering.untetherAll();
+ listener.onResult(TETHER_ERROR_NO_ERROR);
+ } catch (RemoteException e) { }
+ }
+
+ @Override
+ public void isTetheringSupported(String callerPkg, IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, listener)) return;
+
+ try {
+ listener.onResult(TETHER_ERROR_NO_ERROR);
+ } catch (RemoteException e) { }
+ }
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+ @Nullable String[] args) {
+ mTethering.dump(fd, writer, args);
+ }
+
+ private boolean checkAndNotifyCommonError(String callerPkg, IIntResultListener listener) {
+ try {
+ if (!mService.hasTetherChangePermission(callerPkg)) {
+ listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+ return true;
+ }
+ if (!mService.isTetheringSupported()) {
+ listener.onResult(TETHER_ERROR_UNSUPPORTED);
+ return true;
+ }
+ } catch (RemoteException e) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean checkAndNotifyCommonError(String callerPkg, ResultReceiver receiver) {
+ if (!mService.hasTetherChangePermission(callerPkg)) {
+ receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
+ return true;
+ }
+ if (!mService.isTetheringSupported()) {
+ receiver.send(TETHER_ERROR_UNSUPPORTED, null);
+ return true;
+ }
+
+ return false;
+ }
+
}
- @Override
- protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
- @Nullable String[] args) {
- mTethering.dump(fd, writer, args);
+ // if ro.tether.denied = true we default to no tethering
+ // gservices could set the secure setting to 1 though to enable it on a build where it
+ // had previously been turned off.
+ private boolean isTetheringSupported() {
+ final int defaultVal =
+ SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1;
+ final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
+ final boolean tetherEnabledInSettings = tetherSupported
+ && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
+
+ return tetherEnabledInSettings && mTethering.hasTetherableConfiguration();
}
+ private boolean hasTetherChangePermission(String callerPkg) {
+ if (checkCallingOrSelfPermission(
+ android.Manifest.permission.TETHER_PRIVILEGED) == PERMISSION_GRANTED) {
+ return true;
+ }
+
+ if (mTethering.isTetherProvisioningRequired()) return false;
+
+
+ int uid = Binder.getCallingUid();
+ // If callerPkg's uid is not same as Binder.getCallingUid(),
+ // checkAndNoteWriteSettingsOperation will return false and the operation will be denied.
+ if (Settings.checkAndNoteWriteSettingsOperation(mContext, uid, callerPkg,
+ false /* throwException */)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean hasTetherAccessPermission() {
+ if (checkCallingOrSelfPermission(
+ android.Manifest.permission.TETHER_PRIVILEGED) == PERMISSION_GRANTED) {
+ return true;
+ }
+
+ if (checkCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NETWORK_STATE) == PERMISSION_GRANTED) {
+ return true;
+ }
+
+ return false;
+ }
+
+
/**
* An injection method for testing.
*/
@@ -159,20 +319,55 @@
@Override
public boolean isTetheringSupported() {
- int defaultVal =
- SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1;
- boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
- return tetherSupported;
+ return TetheringService.this.isTetheringSupported();
}
@Override
public Context getContext() {
return TetheringService.this;
}
+
+ @Override
+ public IpServer.Dependencies getIpServerDependencies() {
+ return new IpServer.Dependencies() {
+ @Override
+ public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
+ DhcpServerCallbacks cb) {
+ try {
+ final INetworkStackConnector service = getNetworkStackConnector();
+ if (service == null) return;
+
+ service.makeDhcpServer(ifName, params, cb);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ };
+ }
+
+ // TODO: replace this by NetworkStackClient#getRemoteConnector after refactoring
+ // networkStackClient.
+ static final int NETWORKSTACK_TIMEOUT_MS = 60_000;
+ private INetworkStackConnector getNetworkStackConnector() {
+ IBinder connector;
+ try {
+ final long before = System.currentTimeMillis();
+ while ((connector = ServiceManager.getService(
+ Context.NETWORK_STACK_SERVICE)) == null) {
+ if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
+ Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector");
+ return null;
+ }
+ Thread.sleep(200);
+ }
+ } catch (InterruptedException e) {
+ Log.wtf(TAG, "Interrupted, fail to get INetworkStackConnector");
+ return null;
+ }
+ return INetworkStackConnector.Stub.asInterface(connector);
+ }
};
}
-
return mDeps;
}
}
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 5b018df..81a0548 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -33,10 +33,12 @@
"android.test.runner",
"android.test.base",
"android.test.mock",
+ "framework-tethering",
],
jni_libs: [
// For mockito extended
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
],
+ jarjar_rules: "jarjar-rules.txt",
}
diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml
index 049ff6d..0a1cdd3 100644
--- a/packages/Tethering/tests/unit/AndroidManifest.xml
+++ b/packages/Tethering/tests/unit/AndroidManifest.xml
@@ -14,13 +14,13 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.tethering.tests.unit">
+ package="com.android.networkstack.tethering.tests.unit">
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.tethering.tests.unit"
+ android:targetPackage="com.android.networkstack.tethering.tests.unit"
android:label="Tethering service tests">
</instrumentation>
</manifest>
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/unit/jarjar-rules.txt
new file mode 100644
index 0000000..64fdebd
--- /dev/null
+++ b/packages/Tethering/tests/unit/jarjar-rules.txt
@@ -0,0 +1,11 @@
+# Don't jar-jar the entire package because this test use some
+# internal classes (like ArrayUtils in com.android.internal.util)
+rule com.android.internal.util.BitUtils* com.android.networkstack.tethering.util.BitUtils@1
+rule com.android.internal.util.IndentingPrintWriter.java* com.android.networkstack.tethering.util.IndentingPrintWriter.java@1
+rule com.android.internal.util.IState.java* com.android.networkstack.tethering.util.IState.java@1
+rule com.android.internal.util.MessageUtils* com.android.networkstack.tethering.util.MessageUtils@1
+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 android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
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 0273ed3..31ed823 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
@@ -72,7 +72,7 @@
import android.net.INetd;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
-import android.net.ITetherInternalCallback;
+import android.net.ITetheringEventCallback;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -81,6 +81,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.net.NetworkRequest;
import android.net.NetworkState;
import android.net.NetworkUtils;
import android.net.RouteInfo;
@@ -94,7 +95,7 @@
import android.net.util.InterfaceParams;
import android.net.util.NetworkConstants;
import android.net.util.SharedLog;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pInfo;
@@ -167,6 +168,7 @@
@Mock private IDhcpServer mDhcpServer;
@Mock private INetd mNetd;
@Mock private UserManager mUserManager;
+ @Mock private NetworkRequest mNetworkRequest;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -311,6 +313,11 @@
}
@Override
+ public NetworkRequest getDefaultNetworkRequest() {
+ return mNetworkRequest;
+ }
+
+ @Override
public boolean isTetheringSupported() {
mIsTetheringSupportedCalls++;
return true;
@@ -806,12 +813,12 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void failingWifiTetheringLegacyApBroadcast() throws Exception {
- when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(TETHERING_WIFI, null, false);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startSoftAp(null);
+ verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNMService);
@@ -833,12 +840,12 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
- when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(TETHERING_WIFI, null, false);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startSoftAp(null);
+ verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNMService);
@@ -907,13 +914,13 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void failureEnablingIpForwarding() throws Exception {
- when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(TETHERING_WIFI, null, false);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startSoftAp(null);
+ verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNMService);
@@ -1039,7 +1046,7 @@
expectedInteractionsWithShowNotification);
}
- private class TestTetherInternalCallback extends ITetherInternalCallback.Stub {
+ private class TestTetheringEventCallback extends ITetheringEventCallback.Stub {
private final ArrayList<Network> mActualUpstreams = new ArrayList<>();
private final ArrayList<TetheringConfigurationParcel> mTetheringConfigs =
new ArrayList<>();
@@ -1100,13 +1107,16 @@
}
@Override
- public void onCallbackCreated(Network network, TetheringConfigurationParcel config,
+ public void onCallbackStarted(Network network, TetheringConfigurationParcel config,
TetherStatesParcel states) {
mActualUpstreams.add(network);
mTetheringConfigs.add(config);
mTetherStates.add(states);
}
+ @Override
+ public void onCallbackStopped(int errorCode) { }
+
public void assertNoUpstreamChangeCallback() {
assertTrue(mActualUpstreams.isEmpty());
}
@@ -1115,10 +1125,20 @@
assertTrue(mTetheringConfigs.isEmpty());
}
+ public void assertNoStateChangeCallback() {
+ assertTrue(mTetherStates.isEmpty());
+ }
+
public void assertStateChangeCallback() {
assertFalse(mTetherStates.isEmpty());
}
+ public void assertNoCallback() {
+ assertNoUpstreamChangeCallback();
+ assertNoConfigChangeCallback();
+ assertNoStateChangeCallback();
+ }
+
private void assertTetherConfigParcelEqual(@NonNull TetheringConfigurationParcel actual,
@NonNull TetheringConfigurationParcel expect) {
assertEquals(actual.subId, expect.subId);
@@ -1139,23 +1159,24 @@
}
@Test
- public void testRegisterTetherInternalCallback() throws Exception {
- TestTetherInternalCallback callback = new TestTetherInternalCallback();
+ public void testRegisterTetheringEventCallback() throws Exception {
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
// 1. Register one callback before running any tethering.
- mTethering.registerTetherInternalCallback(callback);
+ mTethering.registerTetheringEventCallback(callback);
mLooper.dispatchAll();
callback.expectUpstreamChanged(new Network[] {null});
callback.expectConfigurationChanged(
mTethering.getTetheringConfiguration().toStableParcelable());
TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
assertEquals(tetherState, null);
- // 2. Enable wifi tethering
+ // 2. Enable wifi tethering.
NetworkState upstreamState = buildMobileDualStackUpstreamState();
when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
.thenReturn(upstreamState);
- when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
mLooper.dispatchAll();
tetherState = callback.pollTetherStatesChanged();
@@ -1168,14 +1189,26 @@
assertArrayEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
callback.expectUpstreamChanged(upstreamState.network);
- // 3. Disable wifi tethering.
+ // 3. Register second callback.
+ mTethering.registerTetheringEventCallback(callback2);
+ mLooper.dispatchAll();
+ callback2.expectUpstreamChanged(upstreamState.network);
+ callback2.expectConfigurationChanged(
+ mTethering.getTetheringConfiguration().toStableParcelable());
+ tetherState = callback2.pollTetherStatesChanged();
+ assertEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
+
+ // 4. Unregister first callback and disable wifi tethering
+ mTethering.unregisterTetheringEventCallback(callback);
+ mLooper.dispatchAll();
mTethering.stopTethering(TETHERING_WIFI);
sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
mLooper.dispatchAll();
- tetherState = callback.pollTetherStatesChanged();
+ tetherState = callback2.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
mLooper.dispatchAll();
- callback.expectUpstreamChanged(new Network[] {null});
+ callback2.expectUpstreamChanged(new Network[] {null});
+ callback.assertNoCallback();
}
@Test
diff --git a/packages/WindowManager/OWNERS b/packages/WindowManager/OWNERS
new file mode 100644
index 0000000..063d459
--- /dev/null
+++ b/packages/WindowManager/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include ../../services/core/java/com/android/server/wm/OWNERS
\ No newline at end of file
diff --git a/packages/WindowManager/Shell/Android.bp b/packages/WindowManager/Shell/Android.bp
new file mode 100644
index 0000000..b8934dc
--- /dev/null
+++ b/packages/WindowManager/Shell/Android.bp
@@ -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.
+
+android_library {
+ name: "WindowManager-Shell",
+ srcs: [
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ manifest: "AndroidManifest.xml",
+
+ platform_apis: true,
+ sdk_version: "current",
+ min_sdk_version: "system_current",
+}
diff --git a/apex/sdkext/framework/tests/AndroidManifest.xml b/packages/WindowManager/Shell/AndroidManifest.xml
similarity index 66%
rename from apex/sdkext/framework/tests/AndroidManifest.xml
rename to packages/WindowManager/Shell/AndroidManifest.xml
index 831f132..ea8a5c3 100644
--- a/apex/sdkext/framework/tests/AndroidManifest.xml
+++ b/packages/WindowManager/Shell/AndroidManifest.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 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.
@@ -15,13 +16,5 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.sdkext.tests">
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.sdkext.tests" />
-
+ package="com.android.wm.shell">
</manifest>
diff --git a/packages/WindowManager/Shell/OWNERS b/packages/WindowManager/Shell/OWNERS
new file mode 100644
index 0000000..4390004
--- /dev/null
+++ b/packages/WindowManager/Shell/OWNERS
@@ -0,0 +1,4 @@
+# sysui owners
+hwwang@google.com
+mrenouf@google.com
+winsonc@google.com
\ No newline at end of file
diff --git a/packages/WindowManager/Shell/res/values/config.xml b/packages/WindowManager/Shell/res/values/config.xml
new file mode 100644
index 0000000..c894eb0
--- /dev/null
+++ b/packages/WindowManager/Shell/res/values/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** 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.
+*/
+-->
+
+<resources>
+</resources>
\ No newline at end of file
diff --git a/tests/utils/testutils/java/test/package-info.java b/packages/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java
similarity index 77%
copy from tests/utils/testutils/java/test/package-info.java
copy to packages/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java
index c34d7b2..273bd27 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/packages/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShell.java
@@ -14,8 +14,10 @@
* limitations under the License.
*/
+package com.android.wm.shell;
+
/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+ * Interface for the shell.
+ */
+public class WindowManagerShell {
+}
diff --git a/packages/WindowManager/Shell/tests/Android.bp b/packages/WindowManager/Shell/tests/Android.bp
new file mode 100644
index 0000000..78fa45e
--- /dev/null
+++ b/packages/WindowManager/Shell/tests/Android.bp
@@ -0,0 +1,45 @@
+// 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: "WindowManagerShellTests",
+
+ srcs: ["**/*.java"],
+
+ static_libs: [
+ "WindowManager-Shell",
+ "junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-extended-minus-junit4",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
+ sdk_version: "current",
+ platform_apis: true,
+
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/packages/WindowManager/Shell/tests/AndroidManifest.xml b/packages/WindowManager/Shell/tests/AndroidManifest.xml
new file mode 100644
index 0000000..a8f795e
--- /dev/null
+++ b/packages/WindowManager/Shell/tests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.wm.shell.tests">
+
+ <application android:debuggable="true" android:largeHeap="true">
+ <uses-library android:name="android.test.mock" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Tests for WindowManager-Shell"
+ android:targetPackage="com.android.wm.shell.tests">
+ </instrumentation>
+</manifest>
diff --git a/packages/WindowManager/Shell/tests/AndroidTest.xml b/packages/WindowManager/Shell/tests/AndroidTest.xml
new file mode 100644
index 0000000..4dce4db
--- /dev/null
+++ b/packages/WindowManager/Shell/tests/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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 Tests for WindowManagerShellLib">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="WindowManagerShellTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="WindowManagerShellTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.wm.shell.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/packages/WindowManager/Shell/tests/res/values/config.xml b/packages/WindowManager/Shell/tests/res/values/config.xml
new file mode 100644
index 0000000..c894eb0
--- /dev/null
+++ b/packages/WindowManager/Shell/tests/res/values/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** 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.
+*/
+-->
+
+<resources>
+</resources>
\ No newline at end of file
diff --git a/packages/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java b/packages/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java
new file mode 100644
index 0000000..376875b
--- /dev/null
+++ b/packages/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.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.wm.shell.tests;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.WindowManagerShell;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the shell.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WindowManagerShellTest {
+
+ WindowManagerShell mShell;
+
+ @Test
+ public void testNothing() {
+ // Do nothing
+ }
+}
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index 3eb9049..eecc101 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -42,7 +42,7 @@
IconPackRoundedLauncherOverlay \
IconPackRoundedSettingsOverlay \
IconPackRoundedSystemUIOverlay \
- IconPackRoundedThemePickerUIOverlay \
+ IconPackRoundedThemePickerOverlay \
IconShapeRoundedRectOverlay \
IconShapeSquircleOverlay \
IconShapeTeardropOverlay \
diff --git a/services/Android.bp b/services/Android.bp
index 3b56607..501496d 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -14,6 +14,7 @@
":services.appwidget-sources",
":services.autofill-sources",
":services.backup-sources",
+ ":backuplib-sources",
":services.companion-sources",
":services.contentcapture-sources",
":services.contentsuggestions-sources",
@@ -72,6 +73,7 @@
libs: [
"android.hidl.manager-V1.0-java",
+ "framework-tethering"
],
plugins: [
@@ -101,3 +103,26 @@
name: "art-profile",
srcs: ["art-profile"],
}
+
+// API stub
+// =============================================================
+
+droidstubs {
+ name: "services-stubs.sources",
+ 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\\)" +
+ " --hide-annotation android.annotation.Hide" +
+ " --hide-package com.google.android.startop.iorap" +
+ " --hide ReferencesHidden" +
+ " --hide DeprecationMismatch" +
+ " --hide HiddenTypedefConstant",
+ visibility: ["//visibility:private"],
+}
+
+java_library {
+ name: "services-stubs",
+ srcs: [":services-stubs.sources"],
+ installable: false,
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7fdd83b..6a6e2b2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,6 +16,11 @@
package com.android.server.accessibility;
+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.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -31,6 +36,7 @@
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManagerInternal;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -70,6 +76,7 @@
import android.provider.SettingsStringUtil.SettingStringHelper;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
+import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -113,9 +120,9 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* This class is instantiated by the system as a system level service and can be
@@ -754,6 +761,8 @@
userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
userState.setDisplayMagnificationEnabledLocked(false);
userState.setNavBarMagnificationEnabledLocked(false);
+ userState.disableShortcutMagnificationLocked();
+
userState.setAutoclickEnabledLocked(false);
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
@@ -1072,6 +1081,8 @@
}
}
+ // 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();
@@ -1105,8 +1116,8 @@
if (state.getServiceAssignedToAccessibilityButtonLocked() == null
&& !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
- displayId));
+ AccessibilityManagerService::showAccessibilityTargetsSelection, this,
+ displayId, ACCESSIBILITY_BUTTON));
} else if (state.isNavBarMagnificationEnabledLocked()
&& state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
mMainHandler.sendMessage(obtainMessage(
@@ -1125,8 +1136,8 @@
}
// The user may have turned off the assigned service or feature
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
- displayId));
+ AccessibilityManagerService::showAccessibilityTargetsSelection, this,
+ displayId, ACCESSIBILITY_BUTTON));
}
}
@@ -1138,13 +1149,27 @@
}
}
- private void showAccessibilityButtonTargetSelection(int displayId) {
+ private void showAccessibilityTargetsSelection(int displayId,
+ @ShortcutType int shortcutType) {
Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+ bundle.putInt(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType);
mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
}
+ private void launchShortcutTargetActivity(int displayId, ComponentName name) {
+ final Intent intent = new Intent();
+ final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+ intent.setComponent(name);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ try {
+ mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
+ } catch (ActivityNotFoundException ignore) {
+ // ignore the exception
+ }
+ }
+
private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
final AccessibilityUserState state = getCurrentUserStateLocked();
mIsAccessibilityButtonShown = available;
@@ -1353,9 +1378,8 @@
*/
private void readComponentNamesFromSettingLocked(String settingName, int userId,
Set<ComponentName> outComponentNames) {
- String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- settingName, userId);
- readComponentNamesFromStringLocked(settingValue, outComponentNames, false);
+ readColonDelimitedSettingToSet(settingName, userId, outComponentNames,
+ str -> ComponentName.unflattenFromString(str));
}
/**
@@ -1370,34 +1394,57 @@
private void readComponentNamesFromStringLocked(String names,
Set<ComponentName> outComponentNames,
boolean doMerge) {
- if (!doMerge) {
- outComponentNames.clear();
- }
- if (names != null) {
- TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(names);
- while (splitter.hasNext()) {
- String str = splitter.next();
- if (str == null || str.length() <= 0) {
- continue;
- }
- ComponentName enabledService = ComponentName.unflattenFromString(str);
- if (enabledService != null) {
- outComponentNames.add(enabledService);
- }
- }
- }
+ readColonDelimitedStringToSet(names, outComponentNames, doMerge,
+ str -> ComponentName.unflattenFromString(str));
}
@Override
public void persistComponentNamesToSettingLocked(String settingName,
Set<ComponentName> componentNames, int userId) {
- StringBuilder builder = new StringBuilder();
- for (ComponentName componentName : componentNames) {
+ persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames,
+ componentName -> componentName.flattenToShortString());
+ }
+
+ private <T> void readColonDelimitedSettingToSet(String settingName, int userId, Set<T> outSet,
+ Function<String, T> toItem) {
+ final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ settingName, userId);
+ readColonDelimitedStringToSet(settingValue, outSet, false, toItem);
+ }
+
+ private <T> void readColonDelimitedStringToSet(String names, Set<T> outSet, boolean doMerge,
+ Function<String, T> toItem) {
+ if (!doMerge) {
+ outSet.clear();
+ }
+ if (!TextUtils.isEmpty(names)) {
+ final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ splitter.setString(names);
+ while (splitter.hasNext()) {
+ final String str = splitter.next();
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
+ final T item = toItem.apply(str);
+ if (item != null) {
+ outSet.add(item);
+ }
+ }
+ }
+ }
+
+ private <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId,
+ Set<T> set, Function<T, String> toString) {
+ final StringBuilder builder = new StringBuilder();
+ for (T item : set) {
+ final String str = (item != null ? toString.apply(item) : null);
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
if (builder.length() > 0) {
builder.append(COMPONENT_NAME_SEPARATOR);
}
- builder.append(componentName.flattenToShortString());
+ builder.append(str);
}
final long identity = Binder.clearCallingIdentity();
try {
@@ -1537,7 +1584,8 @@
if (userState.isDisplayMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
}
- if (userState.isNavBarMagnificationEnabledLocked()) {
+ if (userState.isNavBarMagnificationEnabledLocked()
+ || userState.isShortcutKeyMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
}
if (userHasMagnificationServicesLocked(userState)) {
@@ -1647,7 +1695,6 @@
mInitialized = true;
updateLegacyCapabilitiesLocked(userState);
updateServicesLocked(userState);
- updateAccessibilityShortcutLocked(userState);
updateWindowsForAccessibilityCallbackLocked(userState);
updateFilterKeyEventsLocked(userState);
updateTouchExplorationLocked(userState);
@@ -1657,6 +1704,7 @@
scheduleUpdateInputFilter(userState);
updateRelevantEventsLocked(userState);
scheduleUpdateClientsIfNeededLocked(userState);
+ updateAccessibilityShortcutKeyTargetsLocked(userState);
updateAccessibilityButtonTargetsLocked(userState);
}
@@ -1751,7 +1799,7 @@
somethingChanged |= readHighTextContrastEnabledSettingLocked(userState);
somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
somethingChanged |= readAutoclickEnabledSettingLocked(userState);
- somethingChanged |= readAccessibilityShortcutSettingLocked(userState);
+ somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState);
somethingChanged |= readAccessibilityButtonSettingsLocked(userState);
somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
return somethingChanged;
@@ -1847,57 +1895,43 @@
}
}
- private boolean readAccessibilityShortcutSettingLocked(AccessibilityUserState userState) {
- String componentNameToEnableString = AccessibilityShortcutController
- .getTargetServiceComponentNameString(mContext, userState.mUserId);
- if ((componentNameToEnableString == null) || componentNameToEnableString.isEmpty()) {
- if (userState.getServiceToEnableWithShortcutLocked() == null) {
- return false;
+ private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) {
+ final Set<String> targetsFromSetting = new ArraySet<>();
+ readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ userState.mUserId, targetsFromSetting, str -> str);
+ if (targetsFromSetting.isEmpty()) {
+ // Fall back to device's default a11y service.
+ final String defaultService = mContext.getString(
+ R.string.config_defaultAccessibilityService);
+ if (!TextUtils.isEmpty(defaultService)) {
+ targetsFromSetting.add(defaultService);
}
- userState.setServiceToEnableWithShortcutLocked(null);
- return true;
- }
- ComponentName componentNameToEnable =
- ComponentName.unflattenFromString(componentNameToEnableString);
- if ((componentNameToEnable != null)
- && componentNameToEnable.equals(userState.getServiceToEnableWithShortcutLocked())) {
- return false;
}
- userState.setServiceToEnableWithShortcutLocked(componentNameToEnable);
+ final Set<String> currentTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ if (targetsFromSetting.equals(currentTargets)) {
+ return false;
+ }
+ currentTargets.clear();
+ currentTargets.addAll(targetsFromSetting);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
return true;
}
private boolean readAccessibilityButtonSettingsLocked(AccessibilityUserState userState) {
- String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
- if (TextUtils.isEmpty(componentId)) {
- if ((userState.getServiceAssignedToAccessibilityButtonLocked() == null)
- && !userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- return false;
- }
- userState.setServiceAssignedToAccessibilityButtonLocked(null);
- userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
- return true;
- }
+ final Set<String> targetsFromSetting = new ArraySet<>();
+ readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ userState.mUserId, targetsFromSetting, str -> str);
- if (componentId.equals(MagnificationController.class.getName())) {
- if (userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- return false;
- }
- userState.setServiceAssignedToAccessibilityButtonLocked(null);
- userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
- return true;
- }
-
- ComponentName componentName = ComponentName.unflattenFromString(componentId);
- if (Objects.equals(componentName,
- userState.getServiceAssignedToAccessibilityButtonLocked())) {
+ final Set<String> currentTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+ if (targetsFromSetting.equals(currentTargets)) {
return false;
}
- userState.setServiceAssignedToAccessibilityButtonLocked(componentName);
- userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
+ currentTargets.clear();
+ currentTargets.addAll(targetsFromSetting);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
return true;
}
@@ -1921,34 +1955,33 @@
}
/**
- * Check if the service that will be enabled by the shortcut is installed. If it isn't,
- * clear the value and the associated setting so a sideloaded service can't spoof the
- * package name of the default service.
- *
- * @param userState
+ * Check if the targets that will be enabled by the accessibility shortcut key is installed.
+ * If it isn't, remove it from the list and associated setting so a side loaded service can't
+ * spoof the package name of the default service.
*/
- private void updateAccessibilityShortcutLocked(AccessibilityUserState userState) {
- if (userState.getServiceToEnableWithShortcutLocked() == null) {
+ private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) {
+ final Set<String> currentTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ final int lastSize = currentTargets.size();
+ if (lastSize == 0) {
return;
}
- boolean shortcutServiceIsInstalled =
- AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
- .containsKey(userState.getServiceToEnableWithShortcutLocked());
- for (int i = 0; !shortcutServiceIsInstalled && (i < userState.mInstalledServices.size());
- i++) {
- if (userState.mInstalledServices.get(i).getComponentName()
- .equals(userState.getServiceToEnableWithShortcutLocked())) {
- shortcutServiceIsInstalled = true;
- }
+ currentTargets.removeIf(
+ name -> !userState.isShortcutTargetInstalledLocked(name));
+ if (lastSize == currentTargets.size()) {
+ return;
}
- if (!shortcutServiceIsInstalled) {
- userState.setServiceToEnableWithShortcutLocked(null);
+
+ // Update setting key with new value.
+ persistColonDelimitedSetToSettingLocked(
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ userState.mUserId, currentTargets, str -> str);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+
+ // Disable accessibility shortcut key if there's no shortcut installed.
+ if (currentTargets.isEmpty()) {
final long identity = Binder.clearCallingIdentity();
try {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null,
- userState.mUserId);
-
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0, userState.mUserId);
} finally {
@@ -2004,7 +2037,8 @@
// 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.isNavBarMagnificationEnabledLocked()
+ || userState.isShortcutKeyMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
getMagnificationController().register(display.getDisplayId());
@@ -2088,7 +2122,14 @@
}
}
+ /**
+ * 1) Update accessibility button availability to accessibility services.
+ * 2) Check if the targets that will be enabled by the accessibility button is installed.
+ * If it isn't, remove it from the list and associated setting so a side loaded service can't
+ * spoof the package name of the default service.
+ */
private void updateAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
+ // Update accessibility button availability.
for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
@@ -2096,6 +2137,24 @@
service.isAccessibilityButtonAvailableLocked(userState));
}
}
+
+ final Set<String> currentTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+ final int lastSize = currentTargets.size();
+ if (lastSize == 0) {
+ return;
+ }
+ currentTargets.removeIf(
+ name -> !userState.isShortcutTargetInstalledLocked(name));
+ if (lastSize == currentTargets.size()) {
+ return;
+ }
+
+ // Update setting key with new value.
+ persistColonDelimitedSetToSettingLocked(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ userState.mUserId, currentTargets, str -> str);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
}
private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
@@ -2156,7 +2215,7 @@
}
/**
- * AIDL-exposed method to be called when the accessibility shortcut is enabled. Requires
+ * 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.
*/
@@ -2168,52 +2227,177 @@
throw new SecurityException(
"performAccessibilityShortcut requires the MANAGE_ACCESSIBILITY permission");
}
+ mMainHandler.sendMessage(obtainMessage(
+ AccessibilityManagerService::performAccessibilityShortcutInternal, this,
+ Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY));
+ }
+
+ /**
+ * Perform the accessibility shortcut action.
+ *
+ * @param shortcutType The shortcut type.
+ * @param displayId The display id of the accessibility button.
+ */
+ private void performAccessibilityShortcutInternal(int displayId,
+ @ShortcutType int shortcutType) {
+ 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;
+ }
+ final String targetName = shortcutTargets.get(0);
+ // In case user assigned magnification to the given shortcut.
+ if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
+ sendAccessibilityButtonToInputFilter(displayId);
+ return;
+ }
+ final ComponentName targetComponentName = ComponentName.unflattenFromString(targetName);
+ if (targetComponentName == null) {
+ Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
+ return;
+ }
+ // In case user assigned an accessibility framework feature to the given shortcut.
+ if (performAccessibilityFrameworkFeature(targetComponentName)) {
+ return;
+ }
+ // In case user assigned an accessibility shortcut target to the given shortcut.
+ if (performAccessibilityShortcutTargetActivity(displayId, targetComponentName)) {
+ return;
+ }
+ // in case user assigned an accessibility service to the given shortcut.
+ if (performAccessibilityShortcutTargetService(
+ displayId, shortcutType, targetComponentName)) {
+ return;
+ }
+ }
+
+ private boolean performAccessibilityFrameworkFeature(ComponentName assignedTarget) {
final Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
- synchronized(mLock) {
- final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
- final ComponentName serviceName = userState.getServiceToEnableWithShortcutLocked();
- if (serviceName == null) {
- return;
- }
- if (frameworkFeatureMap.containsKey(serviceName)) {
- // Toggle the requested framework feature
- ToggleableFrameworkFeatureInfo featureInfo = frameworkFeatureMap.get(serviceName);
- SettingStringHelper setting = new SettingStringHelper(mContext.getContentResolver(),
- featureInfo.getSettingKey(), mCurrentUserId);
- // Assuming that the default state will be to have the feature off
- if (!TextUtils.equals(featureInfo.getSettingOnValue(), setting.read())) {
- setting.write(featureInfo.getSettingOnValue());
- } else {
- setting.write(featureInfo.getSettingOffValue());
+ if (!frameworkFeatureMap.containsKey(assignedTarget)) {
+ return false;
+ }
+ // Toggle the requested framework feature
+ final ToggleableFrameworkFeatureInfo featureInfo = frameworkFeatureMap.get(assignedTarget);
+ final SettingStringHelper setting = new SettingStringHelper(mContext.getContentResolver(),
+ featureInfo.getSettingKey(), mCurrentUserId);
+ // Assuming that the default state will be to have the feature off
+ if (!TextUtils.equals(featureInfo.getSettingOnValue(), setting.read())) {
+ setting.write(featureInfo.getSettingOnValue());
+ } else {
+ setting.write(featureInfo.getSettingOffValue());
+ }
+ return true;
+ }
+
+ private boolean performAccessibilityShortcutTargetActivity(int displayId,
+ ComponentName assignedTarget) {
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ for (int i = 0; i < userState.mInstalledShortcuts.size(); i++) {
+ final AccessibilityShortcutInfo shortcutInfo = userState.mInstalledShortcuts.get(i);
+ if (!shortcutInfo.getComponentName().equals(assignedTarget)) {
+ continue;
}
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- if (userState.mComponentNameToServiceMap.get(serviceName) == null) {
- enableAccessibilityServiceLocked(serviceName, mCurrentUserId);
- } else {
- disableAccessibilityServiceLocked(serviceName, mCurrentUserId);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
+ launchShortcutTargetActivity(displayId, assignedTarget);
+ return true;
}
}
- };
+ return false;
+ }
+
+ /**
+ * Perform accessibility service shortcut action.
+ *
+ * 1) For {@link AccessibilityManager#ACCESSIBILITY_BUTTON} type and services targeting sdk
+ * version <= Q: callbacks to accessibility service if service is bounded and requests
+ * accessibility button.
+ * 2) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
+ * version <= Q: turns on / off the accessibility service.
+ * 3) For services targeting sdk version > Q:
+ * a) Turns on / off the accessibility service, if service does not request accessibility
+ * button.
+ * b) Callbacks to accessibility service if service is bounded and requests accessibility
+ * button.
+ */
+ private boolean performAccessibilityShortcutTargetService(int displayId,
+ @ShortcutType int shortcutType, ComponentName assignedTarget) {
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ final AccessibilityServiceInfo installedServiceInfo =
+ userState.getInstalledServiceInfoLocked(assignedTarget);
+ if (installedServiceInfo == null) {
+ Slog.d(LOG_TAG, "Perform shortcut failed, invalid component name:"
+ + assignedTarget);
+ return false;
+ }
+
+ final AccessibilityServiceConnection serviceConnection =
+ userState.getServiceConnectionLocked(assignedTarget);
+ final int targetSdk = installedServiceInfo.getResolveInfo()
+ .serviceInfo.applicationInfo.targetSdkVersion;
+ final boolean requestA11yButton = (installedServiceInfo.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ // Turns on / off the accessibility service
+ if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
+ || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
+ if (serviceConnection == null) {
+ enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
+ } else {
+ disableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
+ }
+ return true;
+ }
+ // Callbacks to a11y service if it's bounded and requests a11y button.
+ if (serviceConnection == null
+ || !userState.mBoundServices.contains(serviceConnection)
+ || !serviceConnection.mRequestAccessibilityButton) {
+ Slog.d(LOG_TAG, "Perform shortcut failed, service is not ready:"
+ + assignedTarget);
+ return false;
+ }
+ serviceConnection.notifyAccessibilityButtonClickedLocked(displayId);
+ return true;
+ }
+ }
@Override
- public String getAccessibilityShortcutService() {
- if (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
"getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission");
}
- synchronized(mLock) {
- final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
- if (userState.getServiceToEnableWithShortcutLocked() == null) {
- return null;
+ return getAccessibilityShortcutTargetsInternal(shortcutType);
+ }
+
+ private List<String> getAccessibilityShortcutTargetsInternal(@ShortcutType int shortcutType) {
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ final ArrayList<String> shortcutTargets = new ArrayList<>(
+ userState.getShortcutTargetsLocked(shortcutType));
+ if (shortcutType != ACCESSIBILITY_BUTTON) {
+ return shortcutTargets;
}
- return userState.getServiceToEnableWithShortcutLocked().flattenToString();
+ // Adds legacy a11y services requesting a11y button into the list.
+ for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
+ final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
+ if (!service.mRequestAccessibilityButton
+ || service.getServiceInfo().getResolveInfo().serviceInfo.applicationInfo
+ .targetSdkVersion > Build.VERSION_CODES.Q) {
+ continue;
+ }
+ final String serviceName = service.getComponentName().flattenToString();
+ if (!TextUtils.isEmpty(serviceName)) {
+ shortcutTargets.add(serviceName);
+ }
+ }
+ return shortcutTargets;
}
}
@@ -2609,6 +2793,8 @@
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);
@@ -2713,7 +2899,7 @@
|| mShowImeWithHardKeyboardUri.equals(uri)) {
userState.reconcileSoftKeyboardModeWithSettingsLocked();
} else if (mAccessibilityShortcutServiceIdUri.equals(uri)) {
- if (readAccessibilityShortcutSettingLocked(userState)) {
+ if (readAccessibilityShortcutKeySettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
} else if (mAccessibilityButtonComponentIdUri.equals(uri)) {
@@ -2724,8 +2910,6 @@
|| mUserInteractiveUiTimeoutUri.equals(uri)) {
readUserRecommendedUiTimeoutSettingsLocked(userState);
}
- // TODO(a11y shortcut): Monitor new setting keys, when user adds shortcut, and
- // remove from the list of enabled targets anything that's been uninstalled.
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 6cadb6d..cbff6bd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -294,6 +294,7 @@
}
}
+ // 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) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index a0b9866..a163f74 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -22,6 +22,11 @@
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
+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.MAGNIFICATION_CONTROLLER_NAME;
import android.accessibilityservice.AccessibilityService.SoftKeyboardShowMode;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -33,10 +38,14 @@
import android.os.Binder;
import android.os.RemoteCallbackList;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
+import com.android.internal.accessibility.AccessibilityShortcutController;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -79,19 +88,18 @@
final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
+ final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>();
+
+ final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
+
private final ServiceInfoChangeListener mServiceInfoChangeListener;
- private ComponentName mServiceAssignedToAccessibilityButton;
-
private ComponentName mServiceChangingSoftKeyboardMode;
- private ComponentName mServiceToEnableWithShortcut;
-
private boolean mBindInstantServiceAllowed;
private boolean mIsAutoclickEnabled;
private boolean mIsDisplayMagnificationEnabled;
private boolean mIsFilterKeyEventsEnabled;
- private boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
private boolean mIsNavBarMagnificationEnabled;
private boolean mIsPerformGesturesEnabled;
private boolean mIsTextHighContrastEnabled;
@@ -141,11 +149,11 @@
// Clear state persisted in settings.
mEnabledServices.clear();
mTouchExplorationGrantedServices.clear();
+ mAccessibilityShortcutKeyTargets.clear();
+ mAccessibilityButtonTargets.clear();
mIsTouchExplorationEnabled = false;
mIsDisplayMagnificationEnabled = false;
mIsNavBarMagnificationEnabled = false;
- mServiceAssignedToAccessibilityButton = null;
- mIsNavBarMagnificationAssignedToAccessibilityButton = false;
mIsAutoclickEnabled = false;
mUserNonInteractiveUiTimeout = 0;
mUserInteractiveUiTimeout = 0;
@@ -435,6 +443,26 @@
pw.append(", installedServiceCount=").append(String.valueOf(mInstalledServices.size()));
pw.append("}");
pw.println();
+ pw.append(" shortcut key:{");
+ int size = mAccessibilityShortcutKeyTargets.size();
+ for (int i = 0; i < size; i++) {
+ final String componentId = mAccessibilityShortcutKeyTargets.valueAt(i);
+ pw.append(componentId);
+ if (i + 1 < size) {
+ pw.append(", ");
+ }
+ }
+ pw.println("}");
+ pw.append(" button:{");
+ size = mAccessibilityButtonTargets.size();
+ for (int i = 0; i < size; i++) {
+ final String componentId = mAccessibilityButtonTargets.valueAt(i);
+ pw.append(componentId);
+ if (i + 1 < size) {
+ pw.append(", ");
+ }
+ }
+ pw.println("}");
pw.append(" Bound services:{");
final int serviceCount = mBoundServices.size();
for (int j = 0; j < serviceCount; j++) {
@@ -525,20 +553,85 @@
mLastSentClientState = state;
}
- public boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
- return mIsNavBarMagnificationAssignedToAccessibilityButton;
+ public boolean isShortcutKeyMagnificationEnabledLocked() {
+ return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
}
- public void setNavBarMagnificationAssignedToAccessibilityButtonLocked(boolean assigned) {
- mIsNavBarMagnificationAssignedToAccessibilityButton = assigned;
+ /**
+ * Disable both shortcuts' magnification function.
+ */
+ public void disableShortcutMagnificationLocked() {
+ mAccessibilityShortcutKeyTargets.remove(MAGNIFICATION_CONTROLLER_NAME);
+ mAccessibilityButtonTargets.remove(MAGNIFICATION_CONTROLLER_NAME);
}
- public boolean isNavBarMagnificationEnabledLocked() {
- return mIsNavBarMagnificationEnabled;
+ /**
+ * Returns a set which contains the flattened component names and the system class names
+ * assigned to the given shortcut.
+ *
+ * @param shortcutType The shortcut type.
+ * @return The array set of the strings
+ */
+ public ArraySet<String> getShortcutTargetsLocked(@ShortcutType int shortcutType) {
+ if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ return mAccessibilityShortcutKeyTargets;
+ } else if (shortcutType == ACCESSIBILITY_BUTTON) {
+ return mAccessibilityButtonTargets;
+ }
+ return null;
}
- public void setNavBarMagnificationEnabledLocked(boolean enabled) {
- mIsNavBarMagnificationEnabled = enabled;
+ /**
+ * Whether or not the given shortcut target is installed in device.
+ *
+ * @param name The shortcut target name
+ * @return true if the shortcut target is installed.
+ */
+ public boolean isShortcutTargetInstalledLocked(String name) {
+ if (TextUtils.isEmpty(name)) {
+ return false;
+ }
+ if (MAGNIFICATION_CONTROLLER_NAME.equals(name)) {
+ return true;
+ }
+
+ final ComponentName componentName = ComponentName.unflattenFromString(name);
+ if (componentName == null) {
+ return false;
+ }
+ if (AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
+ .containsKey(componentName)) {
+ return true;
+ }
+ if (getInstalledServiceInfoLocked(componentName) != null) {
+ return true;
+ }
+ for (int i = 0; i < mInstalledShortcuts.size(); i++) {
+ if (mInstalledShortcuts.get(i).getComponentName().equals(componentName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns installed accessibility service info by the given service component name.
+ */
+ public AccessibilityServiceInfo getInstalledServiceInfoLocked(ComponentName componentName) {
+ for (int i = 0; i < mInstalledServices.size(); i++) {
+ final AccessibilityServiceInfo serviceInfo = mInstalledServices.get(i);
+ if (serviceInfo.getComponentName().equals(componentName)) {
+ return serviceInfo;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns accessibility service connection by the given service component name.
+ */
+ public AccessibilityServiceConnection getServiceConnectionLocked(ComponentName componentName) {
+ return mComponentNameToServiceMap.get(componentName);
}
public int getNonInteractiveUiTimeoutLocked() {
@@ -557,14 +650,6 @@
mIsPerformGesturesEnabled = enabled;
}
- public ComponentName getServiceAssignedToAccessibilityButtonLocked() {
- return mServiceAssignedToAccessibilityButton;
- }
-
- public void setServiceAssignedToAccessibilityButtonLocked(ComponentName componentName) {
- mServiceAssignedToAccessibilityButton = componentName;
- }
-
public ComponentName getServiceChangingSoftKeyboardModeLocked() {
return mServiceChangingSoftKeyboardMode;
}
@@ -574,14 +659,6 @@
mServiceChangingSoftKeyboardMode = serviceChangingSoftKeyboardMode;
}
- public ComponentName getServiceToEnableWithShortcutLocked() {
- return mServiceToEnableWithShortcut;
- }
-
- public void setServiceToEnableWithShortcutLocked(ComponentName componentName) {
- mServiceToEnableWithShortcut = componentName;
- }
-
public boolean isTextHighContrastEnabledLocked() {
return mIsTextHighContrastEnabled;
}
@@ -613,4 +690,28 @@
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/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
deleted file mode 100644
index 3dfe59e..0000000
--- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
+++ /dev/null
@@ -1,640 +0,0 @@
-/*
- ** Copyright 2015, The Android Open Source Project
- **
- ** Licensed under the Apache License, Version 2.0 (the "License");
- ** you may not use this file except in compliance with the License.
- ** You may obtain a copy of the License at
- **
- ** http://www.apache.org/licenses/LICENSE-2.0
- **
- ** Unless required by applicable law or agreed to in writing, software
- ** distributed under the License is distributed on an "AS IS" BASIS,
- ** WITHOUT WARRANTIES 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.gestures;
-
-import android.accessibilityservice.AccessibilityGestureEvent;
-import android.accessibilityservice.AccessibilityService;
-import android.content.Context;
-import android.gesture.GesturePoint;
-import android.graphics.PointF;
-import android.util.Slog;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-
-/**
- * This class handles gesture detection for the Touch Explorer. It collects
- * touch events and determines when they match a gesture, as well as when they
- * won't match a gesture. These state changes are then surfaced to mListener.
- */
-class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListener {
-
- private static final boolean DEBUG = false;
-
- // Tag for logging received events.
- private static final String LOG_TAG = "AccessibilityGestureDetector";
-
- // Constants for sampling motion event points.
- // We sample based on a minimum distance between points, primarily to improve accuracy by
- // reducing noisy minor changes in direction.
- private static final float MIN_INCHES_BETWEEN_SAMPLES = 0.1f;
- private final float mMinPixelsBetweenSamplesX;
- private final float mMinPixelsBetweenSamplesY;
-
- // Constants for separating gesture segments
- private static final float ANGLE_THRESHOLD = 0.0f;
-
- // Constants for line segment directions
- private static final int LEFT = 0;
- private static final int RIGHT = 1;
- private static final int UP = 2;
- private static final int DOWN = 3;
- private static final int[][] DIRECTIONS_TO_GESTURE_ID = {
- {
- AccessibilityService.GESTURE_SWIPE_LEFT,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP,
- AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_RIGHT,
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP,
- AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_UP,
- AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN
- },
- {
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT,
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT,
- AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP,
- AccessibilityService.GESTURE_SWIPE_DOWN
- }
- };
-
-
- /**
- * Listener functions are called as a result of onMoveEvent(). The current
- * MotionEvent in the context of these functions is the event passed into
- * onMotionEvent.
- */
- public interface Listener {
- /**
- * Called when the user has performed a double tap and then held down
- * the second tap.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- */
- void onDoubleTapAndHold(MotionEvent event, int policyFlags);
-
- /**
- * Called when the user lifts their finger on the second tap of a double
- * tap.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- *
- * @return true if the event is consumed, else false
- */
- boolean onDoubleTap(MotionEvent event, int policyFlags);
-
- /**
- * Called when the system has decided the event stream is a gesture.
- *
- * @return true if the event is consumed, else false
- */
- boolean onGestureStarted();
-
- /**
- * Called when an event stream is recognized as a gesture.
- *
- * @param gestureEvent Information about the gesture.
- *
- * @return true if the event is consumed, else false
- */
- boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
-
- /**
- * Called when the system has decided an event stream doesn't match any
- * known gesture.
- *
- * @param event The most recent MotionEvent received.
- * @param policyFlags The policy flags of the most recent event.
- *
- * @return true if the event is consumed, else false
- */
- public boolean onGestureCancelled(MotionEvent event, int policyFlags);
- }
-
- private final Listener mListener;
- private final Context mContext; // Retained for on-demand construction of GestureDetector.
- private final GestureDetector mGestureDetector; // Double-tap detector.
-
- // Indicates that a single tap has occurred.
- private boolean mFirstTapDetected;
-
- // Indicates that the down event of a double tap has occured.
- private boolean mDoubleTapDetected;
-
- // Indicates that motion events are being collected to match a gesture.
- private boolean mRecognizingGesture;
-
- // Indicates that we've collected enough data to be sure it could be a
- // gesture.
- private boolean mGestureStarted;
-
- // Indicates that motion events from the second pointer are being checked
- // for a double tap.
- private boolean mSecondFingerDoubleTap;
-
- // Tracks the most recent time where ACTION_POINTER_DOWN was sent for the
- // second pointer.
- private long mSecondPointerDownTime;
-
- // Policy flags of the previous event.
- private int mPolicyFlags;
-
- // These values track the previous point that was saved to use for gesture
- // detection. They are only updated when the user moves more than the
- // recognition threshold.
- private float mPreviousGestureX;
- private float mPreviousGestureY;
-
- // These values track the previous point that was used to determine if there
- // was a transition into or out of gesture detection. They are updated when
- // the user moves more than the detection threshold.
- private float mBaseX;
- private float mBaseY;
- private long mBaseTime;
-
- // This is the calculated movement threshold used track if the user is still
- // moving their finger.
- private final float mGestureDetectionThreshold;
-
- // Buffer for storing points for gesture detection.
- private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
- // The minimal delta between moves to add a gesture point.
- private static final int TOUCH_TOLERANCE = 3;
-
- // The minimal score for accepting a predicted gesture.
- private static final float MIN_PREDICTION_SCORE = 2.0f;
-
- // Distance a finger must travel before we decide if it is a gesture or not.
- private static final int GESTURE_CONFIRM_MM = 10;
-
- // Time threshold used to determine if an interaction is a gesture or not.
- // If the first movement of 1cm takes longer than this value, we assume it's
- // a slow movement, and therefore not a gesture.
- //
- // This value was determined by measuring the time for the first 1cm
- // movement when gesturing, and touch exploring. Based on user testing,
- // all gestures started with the initial movement taking less than 100ms.
- // When touch exploring, the first movement almost always takes longer than
- // 200ms.
- private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
-
- // Time threshold used to determine if a gesture should be cancelled. If
- // the finger takes more than this time to move 1cm, the ongoing gesture is
- // cancelled.
- private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
-
- /**
- * Construct the gesture detector for {@link TouchExplorer}.
- *
- * @see #AccessibilityGestureDetector(Context, Listener, GestureDetector)
- */
- AccessibilityGestureDetector(Context context, Listener listener) {
- this(context, listener, null);
- }
-
- /**
- * Construct the gesture detector for {@link TouchExplorer}.
- *
- * @param context A context handle for accessing resources.
- * @param listener A listener to callback with gesture state or information.
- * @param detector The gesture detector to handle touch event. If null the default one created
- * in place, or for testing purpose.
- */
- AccessibilityGestureDetector(Context context, Listener listener, GestureDetector detector) {
- mListener = listener;
- mContext = context;
-
- // Break the circular dependency between constructors and let the class to be testable
- if (detector == null) {
- mGestureDetector = new GestureDetector(context, this);
- } else {
- mGestureDetector = detector;
- }
- mGestureDetector.setOnDoubleTapListener(this);
- mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
- context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
-
- // Calculate minimum gesture velocity
- final float pixelsPerInchX = context.getResources().getDisplayMetrics().xdpi;
- final float pixelsPerInchY = context.getResources().getDisplayMetrics().ydpi;
- mMinPixelsBetweenSamplesX = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchX;
- mMinPixelsBetweenSamplesY = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchY;
- }
-
- /**
- * Handle a motion event. If an action is completed, the appropriate
- * callback on mListener is called, and the return value of the callback is
- * passed to the caller.
- *
- * @param event The transformed motion event to be handled.
- * @param rawEvent The raw motion event. It's important that this be the raw
- * event, before any transformations have been applied, so that measurements
- * can be made in physical units.
- * @param policyFlags Policy flags for the event.
- *
- * @return true if the event is consumed, else false
- */
- public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- // The accessibility gesture detector is interested in the movements in physical space,
- // so it uses the rawEvent to ignore magnification and other transformations.
- final float x = rawEvent.getX();
- final float y = rawEvent.getY();
- final long time = rawEvent.getEventTime();
-
- mPolicyFlags = policyFlags;
- switch (rawEvent.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDoubleTapDetected = false;
- mSecondFingerDoubleTap = false;
- mRecognizingGesture = true;
- mGestureStarted = false;
- mPreviousGestureX = x;
- mPreviousGestureY = y;
- mStrokeBuffer.clear();
- mStrokeBuffer.add(new GesturePoint(x, y, time));
-
- mBaseX = x;
- mBaseY = y;
- mBaseTime = time;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (mRecognizingGesture) {
- final float deltaX = mBaseX - x;
- final float deltaY = mBaseY - y;
- final double moveDelta = Math.hypot(deltaX, deltaY);
- if (moveDelta > mGestureDetectionThreshold) {
- // If the pointer has moved more than the threshold,
- // update the stored values.
- mBaseX = x;
- mBaseY = y;
- mBaseTime = time;
-
- // Since the pointer has moved, this is not a double
- // tap.
- mFirstTapDetected = false;
- mDoubleTapDetected = false;
-
- // If this hasn't been confirmed as a gesture yet, send
- // the event.
- if (!mGestureStarted) {
- mGestureStarted = true;
- return mListener.onGestureStarted();
- }
- } else if (!mFirstTapDetected) {
- // The finger may not move if they are double tapping.
- // In that case, we shouldn't cancel the gesture.
- final long timeDelta = time - mBaseTime;
- final long threshold = mGestureStarted ?
- CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
- CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
-
- // If the pointer hasn't moved for longer than the
- // timeout, cancel gesture detection.
- if (timeDelta > threshold) {
- cancelGesture();
- return mListener.onGestureCancelled(rawEvent, policyFlags);
- }
- }
-
- final float dX = Math.abs(x - mPreviousGestureX);
- final float dY = Math.abs(y - mPreviousGestureY);
- if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
- mPreviousGestureX = x;
- mPreviousGestureY = y;
- mStrokeBuffer.add(new GesturePoint(x, y, time));
- }
- }
- break;
-
- case MotionEvent.ACTION_UP:
- if (mDoubleTapDetected) {
- return finishDoubleTap(rawEvent, policyFlags);
- }
- if (mGestureStarted) {
- final float dX = Math.abs(x - mPreviousGestureX);
- final float dY = Math.abs(y - mPreviousGestureY);
- if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
- mStrokeBuffer.add(new GesturePoint(x, y, time));
- }
- return recognizeGesture(rawEvent, policyFlags);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_DOWN:
- // Once a second finger is used, we're definitely not
- // recognizing a gesture.
- cancelGesture();
-
- if (rawEvent.getPointerCount() == 2) {
- // If this was the second finger, attempt to recognize double
- // taps on it.
- mSecondFingerDoubleTap = true;
- mSecondPointerDownTime = time;
- } else {
- // If there are more than two fingers down, stop watching
- // for a double tap.
- mSecondFingerDoubleTap = false;
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- // If we're detecting taps on the second finger, see if we
- // should finish the double tap.
- if (mSecondFingerDoubleTap && mDoubleTapDetected) {
- return finishDoubleTap(rawEvent, policyFlags);
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- clear();
- break;
- }
-
- // If we're detecting taps on the second finger, map events from the
- // finger to the first finger.
- if (mSecondFingerDoubleTap) {
- MotionEvent newEvent = mapSecondPointerToFirstPointer(rawEvent);
- if (newEvent == null) {
- return false;
- }
- boolean handled = mGestureDetector.onTouchEvent(newEvent);
- newEvent.recycle();
- return handled;
- }
-
- if (!mRecognizingGesture) {
- return false;
- }
-
- // Pass the transformed event on to the standard gesture detector.
- return mGestureDetector.onTouchEvent(event);
- }
-
- public void clear() {
- mFirstTapDetected = false;
- mDoubleTapDetected = false;
- mSecondFingerDoubleTap = false;
- mGestureStarted = false;
- mGestureDetector.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_CANCEL,
- 0.0f, 0.0f, 0));
- cancelGesture();
- }
-
-
- @Override
- public void onLongPress(MotionEvent e) {
- maybeSendLongPress(e, mPolicyFlags);
- }
-
- @Override
- public boolean onSingleTapUp(MotionEvent event) {
- mFirstTapDetected = true;
- return false;
- }
-
- @Override
- public boolean onSingleTapConfirmed(MotionEvent event) {
- clear();
- return false;
- }
-
- @Override
- public boolean onDoubleTap(MotionEvent event) {
- // The processing of the double tap is deferred until the finger is
- // lifted, so that we can detect a long press on the second tap.
- mDoubleTapDetected = true;
- return false;
- }
-
- private void maybeSendLongPress(MotionEvent event, int policyFlags) {
- if (!mDoubleTapDetected) {
- return;
- }
-
- clear();
-
- mListener.onDoubleTapAndHold(event, policyFlags);
- }
-
- private boolean finishDoubleTap(MotionEvent event, int policyFlags) {
- clear();
-
- return mListener.onDoubleTap(event, policyFlags);
- }
-
- private void cancelGesture() {
- mRecognizingGesture = false;
- mGestureStarted = false;
- mStrokeBuffer.clear();
- }
-
- /**
- * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
- * Listener callbacks for success or failure.
- *
- * @param event The raw motion event to pass to the listener callbacks.
- * @param policyFlags Policy flags for the event.
- *
- * @return true if the event is consumed, else false
- */
- private boolean recognizeGesture(MotionEvent event, int policyFlags) {
- if (mStrokeBuffer.size() < 2) {
- return mListener.onGestureCancelled(event, policyFlags);
- }
-
- // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
- // direction change.
- // Method: for each sampled motion event, check the angle of the most recent motion vector
- // versus the preceding motion vector, and segment the line if the angle is about
- // 90 degrees.
-
- ArrayList<PointF> path = new ArrayList<>();
- PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
- path.add(lastDelimiter);
-
- float dX = 0; // Sum of unit vectors from last delimiter to each following point
- float dY = 0;
- int count = 0; // Number of points since last delimiter
- float length = 0; // Vector length from delimiter to most recent point
-
- PointF next = new PointF();
- for (int i = 1; i < mStrokeBuffer.size(); ++i) {
- next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
- if (count > 0) {
- // Average of unit vectors from delimiter to following points
- float currentDX = dX / count;
- float currentDY = dY / count;
-
- // newDelimiter is a possible new delimiter, based on a vector with length from
- // the last delimiter to the previous point, but in the direction of the average
- // unit vector from delimiter to previous points.
- // Using the averaged vector has the effect of "squaring off the curve",
- // creating a sharper angle between the last motion and the preceding motion from
- // the delimiter. In turn, this sharper angle achieves the splitting threshold
- // even in a gentle curve.
- PointF newDelimiter = new PointF(length * currentDX + lastDelimiter.x,
- length * currentDY + lastDelimiter.y);
-
- // Unit vector from newDelimiter to the most recent point
- float nextDX = next.x - newDelimiter.x;
- float nextDY = next.y - newDelimiter.y;
- float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
- nextDX = nextDX / nextLength;
- nextDY = nextDY / nextLength;
-
- // Compare the initial motion direction to the most recent motion direction,
- // and segment the line if direction has changed by about 90 degrees.
- float dot = currentDX * nextDX + currentDY * nextDY;
- if (dot < ANGLE_THRESHOLD) {
- path.add(newDelimiter);
- lastDelimiter = newDelimiter;
- dX = 0;
- dY = 0;
- count = 0;
- }
- }
-
- // Vector from last delimiter to most recent point
- float currentDX = next.x - lastDelimiter.x;
- float currentDY = next.y - lastDelimiter.y;
- length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
-
- // Increment sum of unit vectors from delimiter to each following point
- count = count + 1;
- dX = dX + currentDX / length;
- dY = dY + currentDY / length;
- }
-
- path.add(next);
- Slog.i(LOG_TAG, "path=" + path.toString());
-
- // Classify line segments, and call Listener callbacks.
- return recognizeGesturePath(event, policyFlags, path);
- }
-
- /**
- * Classifies a pair of line segments, by direction.
- * Calls Listener callbacks for success or failure.
- *
- * @param event The raw motion event to pass to the listener's onGestureCanceled method.
- * @param policyFlags Policy flags for the event.
- * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
- *
- * @return true if the event is consumed, else false
- */
- private boolean recognizeGesturePath(MotionEvent event, int policyFlags,
- ArrayList<PointF> path) {
-
- final int displayId = event.getDisplayId();
- if (path.size() == 2) {
- PointF start = path.get(0);
- PointF end = path.get(1);
-
- float dX = end.x - start.x;
- float dY = end.y - start.y;
- int direction = toDirection(dX, dY);
- switch (direction) {
- case LEFT:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_LEFT,
- displayId));
- case RIGHT:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_RIGHT,
- displayId));
- case UP:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_UP,
- displayId));
- case DOWN:
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_DOWN,
- displayId));
- default:
- // Do nothing.
- }
-
- } else if (path.size() == 3) {
- PointF start = path.get(0);
- PointF mid = path.get(1);
- PointF end = path.get(2);
-
- float dX0 = mid.x - start.x;
- float dY0 = mid.y - start.y;
-
- float dX1 = end.x - mid.x;
- float dY1 = end.y - mid.y;
-
- int segmentDirection0 = toDirection(dX0, dY0);
- int segmentDirection1 = toDirection(dX1, dY1);
- int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
- return mListener.onGestureCompleted(
- new AccessibilityGestureEvent(gestureId, displayId));
- }
- // else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
- return mListener.onGestureCancelled(event, policyFlags);
- }
-
- /** Maps a vector to a dominant direction in set {LEFT, RIGHT, UP, DOWN}. */
- private static int toDirection(float dX, float dY) {
- if (Math.abs(dX) > Math.abs(dY)) {
- // Horizontal
- return (dX < 0) ? LEFT : RIGHT;
- } else {
- // Vertical
- return (dY < 0) ? UP : DOWN;
- }
- }
-
- private MotionEvent mapSecondPointerToFirstPointer(MotionEvent event) {
- // Only map basic events when two fingers are down.
- if (event.getPointerCount() != 2 ||
- (event.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN &&
- event.getActionMasked() != MotionEvent.ACTION_POINTER_UP &&
- event.getActionMasked() != MotionEvent.ACTION_MOVE)) {
- return null;
- }
-
- int action = event.getActionMasked();
-
- if (action == MotionEvent.ACTION_POINTER_DOWN) {
- action = MotionEvent.ACTION_DOWN;
- } else if (action == MotionEvent.ACTION_POINTER_UP) {
- action = MotionEvent.ACTION_UP;
- }
-
- // Map the information from the second pointer to the first.
- return MotionEvent.obtain(mSecondPointerDownTime, event.getEventTime(), action,
- event.getX(1), event.getY(1), event.getPressure(1), event.getSize(1),
- event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
- event.getDeviceId(), event.getEdgeFlags());
- }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
new file mode 100644
index 0000000..50d21ba
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.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 com.android.server.accessibility.gestures;
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+
+import static com.android.server.accessibility.gestures.Swipe.DOWN;
+import static com.android.server.accessibility.gestures.Swipe.LEFT;
+import static com.android.server.accessibility.gestures.Swipe.RIGHT;
+import static com.android.server.accessibility.gestures.Swipe.UP;
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class coordinates a series of individual gesture matchers to serve as a unified gesture
+ * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions
+ * when a gesture starts or completes.
+ */
+class GestureManifold implements GestureMatcher.StateChangeListener {
+
+ private static final String LOG_TAG = "GestureManifold";
+
+ private final List<GestureMatcher> mGestures = new ArrayList<>();
+ private final Context mContext;
+ // Handler for performing asynchronous operations.
+ private final Handler mHandler;
+ // Listener to be notified of gesture start and end.
+ private Listener mListener;
+ // Shared state information.
+ private TouchState mState;
+
+ GestureManifold(Context context, Listener listener, TouchState state) {
+ mContext = context;
+ mHandler = new Handler(context.getMainLooper());
+ mListener = listener;
+ mState = state;
+ // Set up gestures.
+ // Start with double tap.
+ mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
+ mGestures.add(new MultiTapAndHold(context, 2, GESTURE_DOUBLE_TAP_AND_HOLD, this));
+ // Second-finger double tap.
+ mGestures.add(new SecondFingerMultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
+ // One-direction swipes.
+ mGestures.add(new Swipe(context, RIGHT, GESTURE_SWIPE_RIGHT, this));
+ mGestures.add(new Swipe(context, LEFT, GESTURE_SWIPE_LEFT, this));
+ mGestures.add(new Swipe(context, UP, GESTURE_SWIPE_UP, this));
+ mGestures.add(new Swipe(context, DOWN, GESTURE_SWIPE_DOWN, this));
+ // Two-direction swipes.
+ mGestures.add(new Swipe(context, LEFT, RIGHT, GESTURE_SWIPE_LEFT_AND_RIGHT, this));
+ mGestures.add(new Swipe(context, LEFT, UP, GESTURE_SWIPE_LEFT_AND_UP, this));
+ mGestures.add(new Swipe(context, LEFT, DOWN, GESTURE_SWIPE_LEFT_AND_DOWN, this));
+ mGestures.add(new Swipe(context, RIGHT, UP, GESTURE_SWIPE_RIGHT_AND_UP, this));
+ mGestures.add(new Swipe(context, RIGHT, DOWN, GESTURE_SWIPE_RIGHT_AND_DOWN, this));
+ mGestures.add(new Swipe(context, RIGHT, LEFT, GESTURE_SWIPE_RIGHT_AND_LEFT, this));
+ mGestures.add(new Swipe(context, DOWN, UP, GESTURE_SWIPE_DOWN_AND_UP, this));
+ mGestures.add(new Swipe(context, DOWN, LEFT, GESTURE_SWIPE_DOWN_AND_LEFT, this));
+ mGestures.add(new Swipe(context, DOWN, RIGHT, GESTURE_SWIPE_DOWN_AND_RIGHT, this));
+ mGestures.add(new Swipe(context, UP, DOWN, GESTURE_SWIPE_UP_AND_DOWN, this));
+ mGestures.add(new Swipe(context, UP, LEFT, GESTURE_SWIPE_UP_AND_LEFT, this));
+ mGestures.add(new Swipe(context, UP, RIGHT, GESTURE_SWIPE_UP_AND_RIGHT, this));
+ }
+
+ /**
+ * Processes a motion event.
+ *
+ * @param event The event as received from the previous entry in the event stream.
+ * @param rawEvent The event without any transformations e.g. magnification.
+ * @param policyFlags
+ * @return True if the event has been appropriately handled by the gesture manifold and related
+ * callback functions, false if it should be handled further by the calling function.
+ */
+ boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mState.isClear()) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // Sanity safeguard: if touch state is clear, then matchers should always be clear
+ // before processing the next down event.
+ clear();
+ } else {
+ // If for some reason other events come through while in the clear state they could
+ // compromise the state of particular matchers, so we just ignore them.
+ return false;
+ }
+ }
+ for (GestureMatcher matcher : mGestures) {
+ if (matcher.getState() != GestureMatcher.STATE_GESTURE_CANCELED) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, matcher.toString());
+ }
+ matcher.onMotionEvent(event, rawEvent, policyFlags);
+ if (DEBUG) {
+ Slog.d(LOG_TAG, matcher.toString());
+ }
+ if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
+ // Here we just clear and return. The actual gesture dispatch is done in
+ // onStateChanged().
+ clear();
+ // No need to process this event any further.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void clear() {
+ for (GestureMatcher matcher : mGestures) {
+ matcher.clear();
+ }
+ }
+
+ /**
+ * Listener that receives notifications of the state of the gesture detector. Listener functions
+ * are called as a result of onMotionEvent(). The current MotionEvent in the context of these
+ * functions is the event passed into onMotionEvent.
+ */
+ public interface Listener {
+ /**
+ * Called when the user has performed a double tap and then held down the second tap.
+ */
+ void onDoubleTapAndHold();
+
+ /**
+ * Called when the user lifts their finger on the second tap of a double tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap();
+
+ /**
+ * Called when the system has decided the event stream is a gesture.
+ *
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureStarted();
+
+ /**
+ * Called when an event stream is recognized as a gesture.
+ *
+ * @param gestureEvent Information about the gesture.
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
+
+ /**
+ * Called when the system has decided an event stream doesn't match any known gesture.
+ *
+ * @param event The most recent MotionEvent received.
+ * @param policyFlags The policy flags of the most recent event.
+ * @return true if the event is consumed, else false
+ */
+ boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ }
+
+ @Override
+ public void onStateChanged(
+ int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (state == GestureMatcher.STATE_GESTURE_STARTED && !mState.isGestureDetecting()) {
+ mListener.onGestureStarted();
+ } else if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
+ onGestureCompleted(gestureId);
+ } else if (state == GestureMatcher.STATE_GESTURE_CANCELED && mState.isGestureDetecting()) {
+ // We only want to call the cancelation callback if there are no other pending
+ // detectors.
+ for (GestureMatcher matcher : mGestures) {
+ if (matcher.getState() == GestureMatcher.STATE_GESTURE_STARTED) {
+ return;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Cancelling.");
+ }
+ mListener.onGestureCancelled(event, rawEvent, policyFlags);
+ }
+ }
+
+ private void onGestureCompleted(int gestureId) {
+ MotionEvent event = mState.getLastReceivedEvent();
+ // Note that gestures that complete immediately call clear() from onMotionEvent.
+ // Gestures that complete on a delay call clear() here.
+ switch (gestureId) {
+ case GESTURE_DOUBLE_TAP:
+ mListener.onDoubleTap();
+ clear();
+ break;
+ case GESTURE_DOUBLE_TAP_AND_HOLD:
+ mListener.onDoubleTapAndHold();
+ clear();
+ break;
+ default:
+ AccessibilityGestureEvent gestureEvent =
+ new AccessibilityGestureEvent(gestureId, event.getDisplayId());
+ mListener.onGestureCompleted(gestureEvent);
+ break;
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
new file mode 100644
index 0000000..0b30ff57
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
@@ -0,0 +1,371 @@
+/*
+ * 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.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class describes a common base for gesture matchers. A gesture matcher checks a series of
+ * motion events against a single gesture. Coordinating the individual gesture matchers is done by
+ * the GestureManifold. To create a new Gesture, extend this class and override the onDown, onMove,
+ * onUp, etc methods as necessary. If you don't override a method your matcher will do nothing in
+ * response to that type of event. Finally, be sure to give your gesture a name by overriding
+ * getGestureName().
+ */
+abstract class GestureMatcher {
+ // Potential states for this individual gesture matcher.
+ // In STATE_CLEAR, this matcher is accepting new motion events but has not formally signaled
+ // that there is enough data to judge that a gesture has started.
+ static final int STATE_CLEAR = 0;
+ // In STATE_GESTURE_STARTED, this matcher continues to accept motion events and it has signaled
+ // to the gesture manifold that what looks like the specified gesture has started.
+ static final int STATE_GESTURE_STARTED = 1;
+ // In STATE_GESTURE_COMPLETED, this matcher has successfully matched the specified gesture. and
+ // will not accept motion events until it is cleared.
+ static final int STATE_GESTURE_COMPLETED = 2;
+ // In STATE_GESTURE_CANCELED, this matcher will not accept new motion events because it is
+ // impossible that this set of motion events will match the specified gesture.
+ static final int STATE_GESTURE_CANCELED = 3;
+
+ @IntDef({STATE_CLEAR, STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELED})
+ public @interface State {}
+
+ @State private int mState = STATE_CLEAR;
+ // The id number of the gesture that gets passed to accessibility services.
+ private final int mGestureId;
+ // handler for asynchronous operations like timeouts
+ private final Handler mHandler;
+
+ private final StateChangeListener mListener;
+
+ // Use this to transition to new states after a delay.
+ // e.g. cancel or complete after some timeout.
+ // Convenience functions for tapTimeout and doubleTapTimeout are already defined here.
+ protected final DelayedTransition mDelayedTransition;
+
+ GestureMatcher(int gestureId, Handler handler, StateChangeListener listener) {
+ mGestureId = gestureId;
+ mHandler = handler;
+ mDelayedTransition = new DelayedTransition();
+ mListener = listener;
+ }
+
+ /**
+ * Resets all state information for this matcher. Subclasses that include their own state
+ * information should override this method to reset their own state information and call
+ * super.clear().
+ */
+ protected void clear() {
+ mState = STATE_CLEAR;
+ cancelPendingTransitions();
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Transitions to a new state and notifies any listeners. Note that any pending transitions are
+ * canceled.
+ */
+ private void setState(
+ @State int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mState = state;
+ cancelPendingTransitions();
+ mListener.onStateChanged(mGestureId, mState, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates that there is evidence to suggest that this gesture has started. */
+ protected final void startGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_STARTED, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates this stream of motion events can no longer match this gesture. */
+ protected final void cancelGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+ }
+
+ /** Indicates this gesture is completed. */
+ protected final void completeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ setState(STATE_GESTURE_COMPLETED, event, rawEvent, policyFlags);
+ }
+
+ public int getGestureId() {
+ return mGestureId;
+ }
+
+ /**
+ * Process a motion event and attempt to match it to this gesture.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ * @return the state of this matcher.
+ */
+ public final int onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mState == STATE_GESTURE_CANCELED || mState == STATE_GESTURE_COMPLETED) {
+ return mState;
+ }
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ onDown(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ onPointerDown(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ onMove(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onPointerUp(event, rawEvent, policyFlags);
+ break;
+ case MotionEvent.ACTION_UP:
+ onUp(event, rawEvent, policyFlags);
+ break;
+ default:
+ // Cancel because of invalid event.
+ setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+ break;
+ }
+ return mState;
+ }
+
+ /**
+ * Matchers override this method to respond to ACTION_DOWN events. ACTION_DOWN events indicate
+ * the first finger has touched the screen. If not overridden the default response is to do
+ * nothing.
+ */
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_POINTER_DOWN events. ACTION_POINTER_DOWN
+ * indicates that more than one finger has touched the screen. If not overridden the default
+ * response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_MOVE events. ACTION_MOVE indicates that
+ * one or fingers has moved. If not overridden the default response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_POINTER_UP events. ACTION_POINTER_UP
+ * indicates that a finger has lifted from the screen but at least one finger continues to touch
+ * the screen. If not overridden the default response is to do nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /**
+ * Matchers override this method to respond to ACTION_UP events. ACTION_UP indicates that there
+ * are no more fingers touching the screen. If not overridden the default response is to do
+ * nothing.
+ *
+ * @param event the event as passed in from the event stream.
+ * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+ * space.
+ * @param policyFlags the policy flags as passed in from the event stream.
+ */
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+ /** Cancels this matcher after the tap timeout. Any pending state transitions are removed. */
+ protected void cancelAfterTapTimeout(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /** Cancels this matcher after the double tap timeout. Any pending cancelations are removed. */
+ protected final void cancelAfterDoubleTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Cancels this matcher after the specified timeout. Any pending cancelations are removed. Used
+ * to prevent this matcher from accepting motion events until it is cleared.
+ */
+ protected final void cancelAfter(
+ long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mDelayedTransition.cancel();
+ mDelayedTransition.post(STATE_GESTURE_CANCELED, timeout, event, rawEvent, policyFlags);
+ }
+
+ /** Cancels any delayed transitions between states scheduled for this matcher. */
+ protected final void cancelPendingTransitions() {
+ mDelayedTransition.cancel();
+ }
+
+ /**
+ * Signals that this gesture has been completed after the tap timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfterLongPressTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getLongPressTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the tap timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfterTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the specified timeout has expired. Used to
+ * ensure that there is no conflict with another gesture or for gestures that explicitly require
+ * a hold.
+ */
+ protected final void completeAfter(
+ long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mDelayedTransition.cancel();
+ mDelayedTransition.post(STATE_GESTURE_COMPLETED, timeout, event, rawEvent, policyFlags);
+ }
+
+ /**
+ * Signals that this gesture has been completed after the double-tap timeout has expired. Used
+ * to ensure that there is no conflict with another gesture or for gestures that explicitly
+ * require a hold.
+ */
+ protected final void completeAfterDoubleTapTimeout(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ completeAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+ }
+
+ public static String getStateSymbolicName(@State int state) {
+ switch (state) {
+ case STATE_CLEAR:
+ return "STATE_CLEAR";
+ case STATE_GESTURE_STARTED:
+ return "STATE_GESTURE_STARTED";
+ case STATE_GESTURE_COMPLETED:
+ return "STATE_GESTURE_COMPLETED";
+ case STATE_GESTURE_CANCELED:
+ return "STATE_GESTURE_CANCELED";
+ default:
+ return "Unknown state: " + state;
+ }
+ }
+
+ /**
+ * Returns a readable name for this matcher that can be displayed to the user and in system
+ * logs.
+ */
+ abstract String getGestureName();
+
+ /**
+ * Returns a String representation of this matcher. Each matcher can override this method to add
+ * extra state information to the string representation.
+ */
+ public String toString() {
+ return getGestureName() + ":" + getStateSymbolicName(mState);
+ }
+
+ /** This class allows matchers to transition between states on a delay. */
+ protected final class DelayedTransition implements Runnable {
+
+ private static final String LOG_TAG = "GestureMatcher.DelayedTransition";
+ int mTargetState;
+ MotionEvent mEvent;
+ MotionEvent mRawEvent;
+ int mPolicyFlags;
+
+ public void cancel() {
+ // Avoid meaningless debug messages.
+ if (DEBUG && isPending()) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": canceling delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ mHandler.removeCallbacks(this);
+ }
+
+ public void post(
+ int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ mTargetState = state;
+ mEvent = event;
+ mRawEvent = rawEvent;
+ mPolicyFlags = policyFlags;
+ mHandler.postDelayed(this, delay);
+ if (DEBUG) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": posting delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ }
+
+ public boolean isPending() {
+ return mHandler.hasCallbacks(this);
+ }
+
+ public void forceSendAndRemove() {
+ if (isPending()) {
+ run();
+ cancel();
+ }
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Slog.d(
+ LOG_TAG,
+ getGestureName()
+ + ": executing delayed transition to "
+ + getStateSymbolicName(mTargetState));
+ }
+ setState(mTargetState, mEvent, mRawEvent, mPolicyFlags);
+ }
+ }
+
+ /** Interface to allow a class to listen for state changes in a specific gesture matcher */
+ interface StateChangeListener {
+
+ void onStateChanged(
+ int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
new file mode 100644
index 0000000..2891c6c
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class matches multi-tap gestures. The number of taps for each instance is specified in the
+ * constructor.
+ */
+class MultiTap extends GestureMatcher {
+
+ // Maximum reasonable number of taps.
+ public static final int MAX_TAPS = 10;
+ final int mTargetTaps;
+ // The acceptable distance between two taps
+ int mDoubleTapSlop;
+ // The acceptable distance the pointer can move and still count as a tap.
+ int mTouchSlop;
+ int mTapTimeout;
+ int mDoubleTapTimeout;
+ int mCurrentTaps;
+ float mBaseX;
+ float mBaseY;
+
+ MultiTap(Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+ super(gesture, new Handler(context.getMainLooper()), listener);
+ mTargetTaps = taps;
+ mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ clear();
+ }
+
+ @Override
+ protected void clear() {
+ mCurrentTaps = 0;
+ mBaseX = Float.NaN;
+ mBaseY = Float.NaN;
+ super.clear();
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterTapTimeout(event, rawEvent, policyFlags);
+ if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+ mBaseX = event.getX();
+ mBaseY = event.getY();
+ }
+ if (!isInsideSlop(rawEvent, mDoubleTapSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ mBaseX = event.getX();
+ mBaseY = event.getY();
+ }
+
+ @Override
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ if (!isInsideSlop(rawEvent, mTouchSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) {
+ mCurrentTaps++;
+ if (mCurrentTaps == mTargetTaps) {
+ // Done.
+ completeAfterTapTimeout(event, rawEvent, policyFlags);
+ return;
+ }
+ // Needs more taps.
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ } else {
+ // Either too many taps or nonsensical event stream.
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (!isInsideSlop(rawEvent, mTouchSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ public String getGestureName() {
+ switch (mTargetTaps) {
+ case 2:
+ return "Double Tap";
+ case 3:
+ return "Triple Tap";
+ default:
+ return Integer.toString(mTargetTaps) + " Taps";
+ }
+ }
+
+ private boolean isInsideSlop(MotionEvent rawEvent, int slop) {
+ final float deltaX = mBaseX - rawEvent.getX();
+ final float deltaY = mBaseY - rawEvent.getY();
+ if (deltaX == 0 && deltaY == 0) {
+ return true;
+ }
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ return moveDelta <= slop;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString()
+ + ", Taps:"
+ + mCurrentTaps
+ + ", mBaseX: "
+ + Float.toString(mBaseX)
+ + ", mBaseY: "
+ + Float.toString(mBaseY);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
new file mode 100644
index 0000000..6a1f1a5
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * This class matches gestures of the form multi-tap and hold. The number of taps for each instance
+ * is specified in the constructor.
+ */
+class MultiTapAndHold extends MultiTap {
+ MultiTapAndHold(
+ Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+ super(context, taps, gesture, listener);
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ super.onDown(event, rawEvent, policyFlags);
+ if (mCurrentTaps + 1 == mTargetTaps) {
+ completeAfterLongPressTimeout(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ public String getGestureName() {
+ switch (mTargetTaps) {
+ case 2:
+ return "Double Tap and Hold";
+ case 3:
+ return "Triple Tap and Hold";
+ default:
+ return Integer.toString(mTargetTaps) + " Taps and Hold";
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java
new file mode 100644
index 0000000..eb38b53
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.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.accessibility.gestures;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class matches second-finger multi-tap gestures. A second-finger multi-tap gesture is where
+ * one finger is held down and a second finger executes the taps. The number of taps for each
+ * instance is specified in the constructor.
+ */
+class SecondFingerMultiTap extends GestureMatcher {
+ final int mTargetTaps;
+ int mDoubleTapSlop;
+ int mTouchSlop;
+ int mTapTimeout;
+ int mDoubleTapTimeout;
+ int mCurrentTaps;
+ int mSecondFingerPointerId;
+ float mBaseX;
+ float mBaseY;
+
+ SecondFingerMultiTap(
+ Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+ super(gesture, new Handler(context.getMainLooper()), listener);
+ mTargetTaps = taps;
+ mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ clear();
+ }
+
+ @Override
+ protected void clear() {
+ mCurrentTaps = 0;
+ mBaseX = Float.NaN;
+ mBaseY = Float.NaN;
+ mSecondFingerPointerId = INVALID_POINTER_ID;
+ super.clear();
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (event.getPointerCount() > 2) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ // Second finger has gone down.
+ int index = getActionIndex(event);
+ mSecondFingerPointerId = event.getPointerId(index);
+ cancelAfterTapTimeout(event, rawEvent, policyFlags);
+ if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+ mBaseX = event.getX();
+ mBaseY = event.getY();
+ }
+ if (!isSecondFingerInsideSlop(rawEvent, mDoubleTapSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ mBaseX = event.getX();
+ mBaseY = event.getY();
+ }
+
+ @Override
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (event.getPointerCount() > 2) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ if (!isSecondFingerInsideSlop(rawEvent, mTouchSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) {
+ mCurrentTaps++;
+ if (mCurrentTaps == mTargetTaps) {
+ // Done.
+ completeGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ // Needs more taps.
+ cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+ } else {
+ // Nonsensical event stream.
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ }
+
+ @Override
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ switch (event.getPointerCount()) {
+ case 1:
+ // We don't need to track anything about one-finger movements.
+ break;
+ case 2:
+ if (!isSecondFingerInsideSlop(rawEvent, mTouchSlop)) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+ break;
+ default:
+ // More than two fingers means we stop tracking.
+ cancelGesture(event, rawEvent, policyFlags);
+ break;
+ }
+ }
+
+ @Override
+ public String getGestureName() {
+ switch (mTargetTaps) {
+ case 2:
+ return "Second Finger Double Tap";
+ case 3:
+ return "Second Finger Triple Tap";
+ default:
+ return "Second Finger " + Integer.toString(mTargetTaps) + " Taps";
+ }
+ }
+
+ private boolean isSecondFingerInsideSlop(MotionEvent rawEvent, int slop) {
+ int pointerIndex = rawEvent.findPointerIndex(mSecondFingerPointerId);
+ if (pointerIndex == -1) {
+ return false;
+ }
+ final float deltaX = mBaseX - rawEvent.getX(pointerIndex);
+ final float deltaY = mBaseY - rawEvent.getY(pointerIndex);
+ if (deltaX == 0 && deltaY == 0) {
+ return true;
+ }
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ return moveDelta <= slop;
+ }
+
+ private int getActionIndex(MotionEvent event) {
+ return event.getAction()
+ & MotionEvent.ACTION_POINTER_INDEX_MASK << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString()
+ + ", Taps:"
+ + mCurrentTaps
+ + ", mBaseX: "
+ + Float.toString(mBaseX)
+ + ", mBaseY: "
+ + Float.toString(mBaseY);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
new file mode 100644
index 0000000..a285c10
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
@@ -0,0 +1,439 @@
+/*
+ * 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.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.content.Context;
+import android.gesture.GesturePoint;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for matching one-finger swipe gestures. Each instance matches one swipe
+ * gesture. A swipe is specified as a series of one or more directions e.g. left, left and up, etc.
+ * At this time swipes with more than two directions are not supported.
+ */
+class Swipe extends GestureMatcher {
+
+ // Direction constants.
+ public static final int LEFT = 0;
+ public static final int RIGHT = 1;
+ public static final int UP = 2;
+ public static final int DOWN = 3;
+ // This is the calculated movement threshold used track if the user is still
+ // moving their finger.
+ private final float mGestureDetectionThreshold;
+
+ // Buffer for storing points for gesture detection.
+ private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+ // The minimal delta between moves to add a gesture point.
+ private static final int TOUCH_TOLERANCE_PIX = 3;
+
+ // The minimal score for accepting a predicted gesture.
+ private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+ // Distance a finger must travel before we decide if it is a gesture or not.
+ private static final int GESTURE_CONFIRM_CM = 1;
+
+ // Time threshold used to determine if an interaction is a gesture or not.
+ // If the first movement of 1cm takes longer than this value, we assume it's
+ // a slow movement, and therefore not a gesture.
+ //
+ // This value was determined by measuring the time for the first 1cm
+ // movement when gesturing, and touch exploring. Based on user testing,
+ // all gestures started with the initial movement taking less than 100ms.
+ // When touch exploring, the first movement almost always takes longer than
+ // 200ms.
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
+
+ // Time threshold used to determine if a gesture should be cancelled. If
+ // the finger takes more than this time to move 1cm, the ongoing gesture is
+ // cancelled.
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
+
+ private int[] mDirections;
+ private float mBaseX;
+ private float mBaseY;
+ private long mBaseTime;
+ private float mPreviousGestureX;
+ private float mPreviousGestureY;
+ // Constants for sampling motion event points.
+ // We sample based on a minimum distance between points, primarily to improve accuracy by
+ // reducing noisy minor changes in direction.
+ private static final float MIN_CM_BETWEEN_SAMPLES = 0.25f;
+ private final float mMinPixelsBetweenSamplesX;
+ private final float mMinPixelsBetweenSamplesY;
+ // The minmimum distance the finger must travel before we evaluate the initial direction of the
+ // swipe.
+ // Anything less is still considered a touch.
+ private int mTouchSlop;
+
+ // Constants for separating gesture segments
+ private static final float ANGLE_THRESHOLD = 0.0f;
+
+ Swipe(
+ Context context,
+ int direction,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ this(context, new int[] {direction}, gesture, listener);
+ }
+
+ Swipe(
+ Context context,
+ int direction1,
+ int direction2,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ this(context, new int[] {direction1, direction2}, gesture, listener);
+ }
+
+ private Swipe(
+ Context context,
+ int[] directions,
+ int gesture,
+ GestureMatcher.StateChangeListener listener) {
+ super(gesture, new Handler(context.getMainLooper()), listener);
+ mDirections = directions;
+ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+ mGestureDetectionThreshold =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, displayMetrics)
+ * GESTURE_CONFIRM_CM;
+ // Calculate minimum gesture velocity
+ final float pixelsPerCmX = displayMetrics.xdpi / 2.54f;
+ final float pixelsPerCmY = displayMetrics.ydpi / 2.54f;
+ mMinPixelsBetweenSamplesX = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmX;
+ mMinPixelsBetweenSamplesY = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmY;
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ clear();
+ }
+
+ @Override
+ protected void clear() {
+ mBaseX = Float.NaN;
+ mBaseY = Float.NaN;
+ mBaseTime = 0;
+ mStrokeBuffer.clear();
+ super.clear();
+ }
+
+ @Override
+ protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+ mBaseX = rawEvent.getX();
+ mBaseY = rawEvent.getY();
+ mBaseTime = event.getEventTime();
+ mPreviousGestureX = mBaseX;
+ mPreviousGestureY = mBaseY;
+ }
+ // Otherwise do nothing because this event doesn't make sense in the middle of a gesture.
+ }
+
+ @Override
+ protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ final float x = rawEvent.getX();
+ final float y = rawEvent.getY();
+ final long time = event.getEventTime();
+ final float dX = Math.abs(x - mPreviousGestureX);
+ final float dY = Math.abs(y - mPreviousGestureY);
+ final long timeDelta = time - mBaseTime;
+ final double moveDelta = Math.hypot(Math.abs(x - mBaseX), Math.abs(y - mBaseY));
+ if (DEBUG) {
+ Slog.d(
+ getGestureName(),
+ "moveDelta:"
+ + Double.toString(moveDelta)
+ + " mGestureDetectionThreshold: "
+ + Float.toString(mGestureDetectionThreshold));
+ }
+ if (getState() == STATE_CLEAR) {
+ if (moveDelta < mTouchSlop) {
+ // This still counts as a touch not a swipe.
+ return;
+ } else if (mStrokeBuffer.size() == 0) {
+ // First, make sure the pointer is going in the right direction.
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ int direction = toDirection(x - mBaseX, y - mBaseY);
+ if (direction != mDirections[0]) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ } else {
+ // This is confirmed to be some kind of swipe so start tracking points.
+ mStrokeBuffer.add(new GesturePoint(mBaseX, mBaseY, mBaseTime));
+ }
+ }
+ if (moveDelta > mGestureDetectionThreshold) {
+ // If the pointer has moved more than the threshold,
+ // update the stored values.
+ mBaseX = x;
+ mBaseY = y;
+ mBaseTime = time;
+ if (getState() == STATE_CLEAR) {
+ startGesture(event, rawEvent, policyFlags);
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ }
+ }
+ }
+ if (getState() == STATE_GESTURE_STARTED) {
+ if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+ mPreviousGestureX = x;
+ mPreviousGestureY = y;
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
+ cancelAfterDelay(event, rawEvent, policyFlags);
+ }
+ }
+ }
+
+ @Override
+ protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (getState() != STATE_GESTURE_STARTED) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+
+ final float x = rawEvent.getX();
+ final float y = rawEvent.getY();
+ final long time = event.getEventTime();
+ final float dX = Math.abs(x - mPreviousGestureX);
+ final float dY = Math.abs(y - mPreviousGestureY);
+ if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
+ }
+ recognizeGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelGesture(event, rawEvent, policyFlags);
+ }
+
+ /**
+ * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have
+ * transitioned to STATE_GESTURE_STARTED the delay is longer.
+ */
+ private void cancelAfterDelay(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelPendingTransitions();
+ switch (getState()) {
+ case STATE_CLEAR:
+ cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS, event, rawEvent, policyFlags);
+ break;
+ case STATE_GESTURE_STARTED:
+ cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS, event, rawEvent, policyFlags);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
+ * Listener callbacks for success or failure.
+ *
+ * @param event The raw motion event to pass to the listener callbacks.
+ * @param policyFlags Policy flags for the event.
+ * @return true if the event is consumed, else false
+ */
+ private void recognizeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mStrokeBuffer.size() < 2) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+
+ // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
+ // direction change.
+ // Method: for each sampled motion event, check the angle of the most recent motion vector
+ // versus the preceding motion vector, and segment the line if the angle is about
+ // 90 degrees.
+
+ ArrayList<PointF> path = new ArrayList<>();
+ PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
+ path.add(lastDelimiter);
+
+ float dX = 0; // Sum of unit vectors from last delimiter to each following point
+ float dY = 0;
+ int count = 0; // Number of points since last delimiter
+ float length = 0; // Vector length from delimiter to most recent point
+
+ PointF next = new PointF();
+ for (int i = 1; i < mStrokeBuffer.size(); ++i) {
+ next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
+ if (count > 0) {
+ // Average of unit vectors from delimiter to following points
+ float currentDX = dX / count;
+ float currentDY = dY / count;
+
+ // newDelimiter is a possible new delimiter, based on a vector with length from
+ // the last delimiter to the previous point, but in the direction of the average
+ // unit vector from delimiter to previous points.
+ // Using the averaged vector has the effect of "squaring off the curve",
+ // creating a sharper angle between the last motion and the preceding motion from
+ // the delimiter. In turn, this sharper angle achieves the splitting threshold
+ // even in a gentle curve.
+ PointF newDelimiter =
+ new PointF(
+ length * currentDX + lastDelimiter.x,
+ length * currentDY + lastDelimiter.y);
+
+ // Unit vector from newDelimiter to the most recent point
+ float nextDX = next.x - newDelimiter.x;
+ float nextDY = next.y - newDelimiter.y;
+ float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
+ nextDX = nextDX / nextLength;
+ nextDY = nextDY / nextLength;
+
+ // Compare the initial motion direction to the most recent motion direction,
+ // and segment the line if direction has changed by about 90 degrees.
+ float dot = currentDX * nextDX + currentDY * nextDY;
+ if (dot < ANGLE_THRESHOLD) {
+ path.add(newDelimiter);
+ lastDelimiter = newDelimiter;
+ dX = 0;
+ dY = 0;
+ count = 0;
+ }
+ }
+
+ // Vector from last delimiter to most recent point
+ float currentDX = next.x - lastDelimiter.x;
+ float currentDY = next.y - lastDelimiter.y;
+ length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
+
+ // Increment sum of unit vectors from delimiter to each following point
+ count = count + 1;
+ dX = dX + currentDX / length;
+ dY = dY + currentDY / length;
+ }
+
+ path.add(next);
+ if (DEBUG) {
+ Slog.d(getGestureName(), "path=" + path.toString());
+ }
+ // Classify line segments, and call Listener callbacks.
+ recognizeGesturePath(event, rawEvent, policyFlags, path);
+ }
+
+ /**
+ * Classifies a pair of line segments, by direction. Calls Listener callbacks for success or
+ * failure.
+ *
+ * @param event The raw motion event to pass to the listener's onGestureCanceled method.
+ * @param policyFlags Policy flags for the event.
+ * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
+ * @return true if the event is consumed, else false
+ */
+ private void recognizeGesturePath(
+ MotionEvent event, MotionEvent rawEvent, int policyFlags, ArrayList<PointF> path) {
+
+ final int displayId = event.getDisplayId();
+ if (path.size() != mDirections.length + 1) {
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ for (int i = 0; i < path.size() - 1; ++i) {
+ PointF start = path.get(i);
+ PointF end = path.get(i + 1);
+
+ float dX = end.x - start.x;
+ float dY = end.y - start.y;
+ int direction = toDirection(dX, dY);
+ if (direction != mDirections[i]) {
+ if (DEBUG) {
+ Slog.d(
+ getGestureName(),
+ "Found direction "
+ + directionToString(direction)
+ + " when expecting "
+ + directionToString(mDirections[i]));
+ }
+ cancelGesture(event, rawEvent, policyFlags);
+ return;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(getGestureName(), "Completed.");
+ }
+ completeGesture(event, rawEvent, policyFlags);
+ }
+
+ private static int toDirection(float dX, float dY) {
+ if (Math.abs(dX) > Math.abs(dY)) {
+ // Horizontal
+ return (dX < 0) ? LEFT : RIGHT;
+ } else {
+ // Vertical
+ return (dY < 0) ? UP : DOWN;
+ }
+ }
+
+ public static String directionToString(int direction) {
+ switch (direction) {
+ case LEFT:
+ return "left";
+ case RIGHT:
+ return "right";
+ case UP:
+ return "up";
+ case DOWN:
+ return "down";
+ default:
+ return "Unknown Direction";
+ }
+ }
+
+ @Override
+ String getGestureName() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Swipe ").append(directionToString(mDirections[0]));
+ for (int i = 1; i < mDirections.length; ++i) {
+ builder.append(" and ").append(directionToString(mDirections[i]));
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(super.toString());
+ if (getState() != STATE_GESTURE_CANCELED) {
+ builder.append(", mBaseX: ")
+ .append(mBaseX)
+ .append(", mBaseY: ")
+ .append(mBaseY)
+ .append(", mGestureDetectionThreshold:")
+ .append(mGestureDetectionThreshold)
+ .append(", mMinPixelsBetweenSamplesX:")
+ .append(mMinPixelsBetweenSamplesX)
+ .append(", mMinPixelsBetweenSamplesY:")
+ .append(mMinPixelsBetweenSamplesY);
+ }
+ return builder.toString();
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index b62e260..f6eb31b 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -59,7 +59,7 @@
* @hide
*/
public class TouchExplorer extends BaseEventStreamTransformation
- implements AccessibilityGestureDetector.Listener {
+ implements GestureManifold.Listener {
static final boolean DEBUG = false;
@@ -104,7 +104,7 @@
private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
// Helper to detect gestures.
- private final AccessibilityGestureDetector mGestureDetector;
+ private final GestureManifold mGestureDetector;
// Helper class to track received pointers.
private final TouchState.ReceivedPointerTracker mReceivedPointerTracker;
@@ -142,7 +142,7 @@
* one created in place, or for testing purpose.
*/
public TouchExplorer(Context context, AccessibilityManagerService service,
- AccessibilityGestureDetector detector) {
+ GestureManifold detector) {
mContext = context;
mAms = service;
mState = new TouchState();
@@ -161,7 +161,7 @@
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
mDetermineUserIntentTimeout);
if (detector == null) {
- mGestureDetector = new AccessibilityGestureDetector(context, this);
+ mGestureDetector = new GestureManifold(context, this, mState);
} else {
mGestureDetector = detector;
}
@@ -285,12 +285,7 @@
}
@Override
- public void onDoubleTapAndHold(MotionEvent event, int policyFlags) {
- // Ignore the event if we aren't touch interacting.
- if (!mState.isTouchInteracting()) {
- return;
- }
-
+ public void onDoubleTapAndHold() {
// Pointers should not be zero when running this command.
if (mState.getLastReceivedEvent().getPointerCount() == 0) {
return;
@@ -303,11 +298,7 @@
}
@Override
- public boolean onDoubleTap(MotionEvent event, int policyFlags) {
- if (!mState.isTouchInteracting()) {
- return false;
- }
-
+ public boolean onDoubleTap() {
mAms.onTouchInteractionEnd();
// Remove pending event deliveries.
mSendHoverEnterAndMoveDelayed.cancel();
@@ -319,7 +310,7 @@
// Announce the end of a new touch interaction.
mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-
+ mSendTouchInteractionEndDelayed.cancel();
// Try to use the standard accessibility API to click
if (!mAms.performActionOnAccessibilityFocusedItem(
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
@@ -356,7 +347,7 @@
}
@Override
- public boolean onGestureCancelled(MotionEvent event, int policyFlags) {
+ public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (mState.isGestureDetecting()) {
endGestureDetection(event.getActionMasked() == MotionEvent.ACTION_UP);
return true;
@@ -454,7 +445,7 @@
handleActionDown(event, rawEvent, policyFlags);
break;
case MotionEvent.ACTION_POINTER_DOWN:
- handleActionPointerDown();
+ handleActionPointerDown(event, rawEvent, policyFlags);
break;
case MotionEvent.ACTION_MOVE:
handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags);
@@ -479,7 +470,7 @@
// We should have already received ACTION_DOWN. Ignore.
break;
case MotionEvent.ACTION_POINTER_DOWN:
- handleActionPointerDown();
+ handleActionPointerDown(event, rawEvent, policyFlags);
break;
case MotionEvent.ACTION_MOVE:
handleActionMoveStateTouchExploring(event, rawEvent, policyFlags);
@@ -496,12 +487,19 @@
* Handles ACTION_POINTER_DOWN when in the touch exploring state. This event represents an
* additional finger touching the screen.
*/
- private void handleActionPointerDown() {
+ private void handleActionPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
// Another finger down means that if we have not started to deliver
// hover events, we will not have to. The code for ACTION_MOVE will
// decide what we will actually do next.
- mSendHoverEnterAndMoveDelayed.cancel();
- mSendHoverExitDelayed.cancel();
+
+ if (mSendHoverEnterAndMoveDelayed.isPending()) {
+ mSendHoverEnterAndMoveDelayed.cancel();
+ mSendHoverExitDelayed.cancel();
+ } else {
+ // We have already delivered at least one hover event, so send hover exit to keep the
+ // stream consistent.
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+ }
}
/**
* Handles ACTION_MOVE while in the touch interacting state. This is where transitions to
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index f463260..d23dbbe 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -71,7 +71,10 @@
// Helper class to track received pointers.
// Todo: collapse or hide this class so multiple classes don't modify it.
private final ReceivedPointerTracker mReceivedPointerTracker;
+ // The most recently received motion event.
private MotionEvent mLastReceivedEvent;
+ // The accompanying raw event without any transformations.
+ private MotionEvent mLastReceivedRawEvent;
public TouchState() {
mReceivedPointerTracker = new ReceivedPointerTracker();
@@ -97,6 +100,9 @@
if (mLastReceivedEvent != null) {
mLastReceivedEvent.recycle();
}
+ if (mLastReceivedRawEvent != null) {
+ mLastReceivedRawEvent.recycle();
+ }
mLastReceivedEvent = MotionEvent.obtain(rawEvent);
mReceivedPointerTracker.onMotionEvent(rawEvent);
}
@@ -246,7 +252,6 @@
// or if it goes up the next one that most recently went down.
private int mPrimaryPointerId;
-
ReceivedPointerTracker() {
clear();
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 81ce359..26245b1 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -635,7 +635,7 @@
mPackageManagerInternal.getSuspendedDialogInfo(providerPackage,
suspendingPackage, providerUserId);
onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(
- providerPackage, suspendingPackage, dialogInfo, providerUserId);
+ providerPackage, suspendingPackage, dialogInfo, null, providerUserId);
}
} else if (provider.maskedByQuietProfile) {
showBadge = true;
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index d7ed2e9..202f900 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -82,6 +82,7 @@
import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -168,6 +169,8 @@
@Nullable
private ServiceInfo mRemoteAugmentedAutofillServiceInfo;
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+
AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
AutofillCompatState autofillCompatState,
@@ -179,6 +182,7 @@
mUi = ui;
mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId);
mAutofillCompatState = autofillCompatState;
+ mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
updateLocked(disabled);
}
@@ -493,7 +497,7 @@
sessionId, taskId, uid, activityToken, appCallbackToken, hasCallback,
mUiLatencyHistory, mWtfHistory, serviceComponentName,
componentName, compatMode, bindInstantServiceAllowed, forAugmentedAutofillOnly,
- flags);
+ flags, mInputMethodManagerInternal);
mSessions.put(newSession.id, newSession);
return newSession;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3b2da91..67bcccd 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -42,6 +42,8 @@
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;
@@ -80,6 +82,8 @@
import android.util.ArrayMap;
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;
@@ -90,22 +94,38 @@
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;
+import android.view.inline.InlinePresentationSpec;
+import android.view.inputmethod.InlineSuggestion;
+import android.view.inputmethod.InlineSuggestionInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.inline.IInlineContentCallback;
+import com.android.internal.view.inline.IInlineContentProvider;
import com.android.server.autofill.ui.AutoFillUI;
import com.android.server.autofill.ui.PendingUi;
+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;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -290,6 +310,23 @@
@GuardedBy("mLock")
private boolean mForAugmentedAutofillOnly;
+ @NonNull
+ private final InputMethodManagerInternal mInputMethodManagerInternal;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private CompletableFuture<IInlineSuggestionsResponseCallback>
+ mInlineSuggestionsResponseCallbackFuture;
+
+ @Nullable
+ private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
+
+ private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
+
/**
* Receiver of assist data from the app's {@link Activity}.
*/
@@ -386,7 +423,23 @@
final ArrayList<FillContext> contexts =
mergePreviousSessionLocked(/* forSave= */ false);
- request = new FillRequest(requestId, contexts, mClientState, flags);
+
+ InlineSuggestionsRequest suggestionsRequest = null;
+ if (mSuggestionsRequestFuture != null) {
+ try {
+ suggestionsRequest = 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);
+ }
+ }
+
+ request = new FillRequest(requestId, contexts, mClientState, flags,
+ suggestionsRequest);
}
mRemoteFillService.onFillRequest(request);
@@ -569,6 +622,70 @@
}
/**
+ * Returns whether inline suggestions are enabled for Autofill.
+ */
+ // TODO(b/137800469): Implement this
+ private boolean isInlineSuggestionsEnabled() {
+ return true;
+ }
+
+ /**
+ * Ask the IME to make an inline suggestions request if enabled.
+ */
+ private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
+ int newState, int flags) {
+ if (isInlineSuggestionsEnabled()) {
+ mSuggestionsRequestFuture = new CompletableFuture<>();
+ mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>();
+
+ if (mInlineSuggestionsRequestCallback == null) {
+ mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallback(this);
+ }
+
+ mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
+ mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
+ }
+
+ requestNewFillResponseLocked(viewState, newState, flags);
+ }
+
+ private static final class InlineSuggestionsRequestCallback
+ extends IInlineSuggestionsRequestCallback.Stub {
+ private final WeakReference<Session> mSession;
+
+ private InlineSuggestionsRequestCallback(Session session) {
+ mSession = new WeakReference<>(session);
+ }
+
+ @Override
+ public void onInlineSuggestionsUnsupported() throws RemoteException {
+ Log.i(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);
+ }
+ }
+ }
+
+ @Override
+ public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+ IInlineSuggestionsResponseCallback callback) throws RemoteException {
+ Log.i(TAG, "onInlineSuggestionsRequest() received: "
+ + request);
+ final Session session = mSession.get();
+ if (session != null) {
+ synchronized (session.mLock) {
+ session.mSuggestionsRequestFuture.complete(request);
+ session.mInlineSuggestionsResponseCallbackFuture.complete(callback);
+ }
+ }
+ }
+ }
+
+ /**
* Reads a new structure and then request a new fill response from the fill service.
*/
@GuardedBy("mLock")
@@ -584,6 +701,7 @@
triggerAugmentedAutofillLocked();
return;
}
+
viewState.setState(newState);
int requestId;
@@ -636,7 +754,8 @@
@NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
@NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName,
@NonNull ComponentName componentName, boolean compatMode,
- boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags) {
+ boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags,
+ @NonNull InputMethodManagerInternal inputMethodManagerInternal) {
if (sessionId < 0) {
wtf(null, "Non-positive sessionId: %s", sessionId);
}
@@ -661,6 +780,8 @@
mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
setClientLocked(client);
+ mInputMethodManagerInternal = inputMethodManagerInternal;
+
mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
}
@@ -2208,7 +2329,8 @@
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mForAugmentedAutofillOnly = false;
if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
- requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_RESTARTED_SESSION, flags);
return;
}
@@ -2218,7 +2340,8 @@
Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
+ viewState.getStateAsString());
}
- requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_STARTED_PARTITION, flags);
} else {
if (sVerbose) {
Slog.v(TAG, "Not starting new partition for view " + id + ": "
@@ -2325,7 +2448,8 @@
// View is triggering autofill.
mCurrentViewId = viewState.id;
viewState.update(value, virtualBounds, flags);
- requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
+ maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+ ViewState.STATE_STARTED_SESSION, flags);
break;
case ACTION_VALUE_CHANGED:
if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -2527,6 +2651,16 @@
wtf(null, "onFillReady(): no service label or icon");
return;
}
+
+ final List<Slice> inlineSuggestionSlices = response.getInlineSuggestionSlices();
+ if (inlineSuggestionSlices != null) {
+ if (requestShowInlineSuggestions(inlineSuggestionSlices, response)) {
+ //TODO(b/137800469): Add logging instead of bypassing below logic.
+ return;
+ }
+ }
+
+
getUiForShowing().showFillUi(filledId, response, filterText,
mService.getServicePackageName(), mComponentName,
serviceLabel, serviceIcon, this, id, mCompatMode);
@@ -2558,6 +2692,109 @@
}
}
+ /**
+ * Returns whether we made a request to show inline suggestions.
+ */
+ private boolean requestShowInlineSuggestions(List<Slice> inlineSuggestionSlices,
+ FillResponse response) {
+ IInlineSuggestionsResponseCallback inlineContentCallback = null;
+ synchronized (mLock) {
+ if (mInlineSuggestionsResponseCallbackFuture != null) {
+ try {
+ inlineContentCallback = 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);
+ }
+ }
+ }
+
+ if (inlineContentCallback == null) {
+ Log.w(TAG, "Session input method callback is not set yet");
+ return false;
+ }
+
+ final List<Dataset> datasets = response.getDatasets();
+ if (datasets == null) {
+ Log.w(TAG, "response returned null datasets");
+ return false;
+ }
+
+ 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);
+ }
+ }
+
+ 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 InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+ spec, InlineSuggestionInfo.SOURCE_AUTOFILL, new String[] { "" });
+ final Dataset dataset = datasets.get(sliceIndex);
+
+ inlineSuggestions.add(new InlineSuggestion(inlineSuggestionInfo,
+ new IInlineContentProvider.Stub() {
+ @Override
+ public void provideContent(int width, int height,
+ IInlineContentCallback callback) throws RemoteException {
+ getUiForShowing().getSuggestionSurfaceForShowing(dataset, response,
+ mCurrentViewId, width, height, callback);
+ }
+ }));
+ }
+
+ try {
+ inlineContentCallback.onInlineSuggestionsResponse(
+ new InlineSuggestionsResponse(inlineSuggestions));
+ } catch (RemoteException e) {
+ Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()");
+ return false;
+ }
+
+ return true;
+ }
+
boolean isDestroyed() {
synchronized (mLock) {
return mDestroyed;
@@ -2831,6 +3068,7 @@
final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
+ // TODO(b/137800469): implement inlined suggestions for augmented autofill
remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
currentValue);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 7e8edf2..eadfd31 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -24,6 +24,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.metrics.LogMaker;
import android.os.Bundle;
@@ -37,13 +39,19 @@
import android.text.TextUtils;
import android.util.Slog;
import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
import android.view.autofill.IAutofillWindowPresenter;
+import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.view.inline.IInlineContentCallback;
import com.android.server.LocalServices;
import com.android.server.UiModeManagerInternal;
import com.android.server.UiThread;
@@ -171,6 +179,75 @@
}
/**
+ * TODO(b/137800469): Fill in javadoc.
+ * TODO(b/137800469): peoperly manage lifecycle of suggestions surfaces.
+ */
+ public void getSuggestionSurfaceForShowing(@NonNull Dataset dataset,
+ @NonNull FillResponse response, AutofillId autofillId, int width, int height,
+ IInlineContentCallback cb) {
+ if (dataset == null) {
+ Slog.w(TAG, "getSuggestionSurfaceForShowing() called with null dataset");
+ }
+ mHandler.post(() -> {
+ final SurfaceControl suggestionSurface = inflateInlineSuggestion(dataset, response,
+ autofillId, width, height);
+
+ try {
+ cb.onContent(suggestionSurface);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException replying onContent(" + suggestionSurface + "): " + e);
+ }
+ });
+ }
+
+ /**
+ * TODO(b/137800469): Fill in javadoc, generate custom templated view for inline suggestions.
+ * TODO: Move to ExtServices.
+ *
+ * @return a {@link SurfaceControl} with the inflated content embedded in it.
+ */
+ private SurfaceControl inflateInlineSuggestion(@NonNull Dataset dataset,
+ @NonNull FillResponse response, AutofillId autofillId, int width, int height) {
+ Slog.i(TAG, "inflate() called");
+ final Context context = mContext;
+ final int index = dataset.getFieldIds().indexOf(autofillId);
+ if (index < 0) {
+ Slog.w(TAG, "inflateInlineSuggestion(): AutofillId=" + autofillId
+ + " not found in dataset");
+ }
+
+ final AutofillValue datasetValue = dataset.getFieldValues().get(index);
+ final SurfaceControl sc = new SurfaceControl.Builder()
+ // TODO(b/137800469): sanitize name
+ .setName("af suggestion")
+ .build();
+
+ //TODO(b/137800469): Pass in inputToken from IME.
+ final WindowlessViewRoot wvr = new WindowlessViewRoot(context, context.getDisplay(), sc,
+ null);
+
+ TextView textView = new TextView(context);
+ textView.setText(datasetValue.getTextValue());
+ textView.setBackgroundColor(Color.WHITE);
+ textView.setTextColor(Color.BLACK);
+ textView.setOnClickListener(v -> {
+ Slog.d(TAG, "Inline suggestion clicked");
+ hideFillUiUiThread(mCallback, true);
+ if (mCallback != null) {
+ final int datasetIndex = response.getDatasets().indexOf(dataset);
+ mCallback.fill(response.getRequestId(), datasetIndex, dataset);
+ }
+ });
+
+ WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+ wvr.addView(textView, lp);
+
+ return sc;
+ }
+
+ /**
* Shows the fill UI, removing the previous fill UI if the has changed.
*
* @param focusedId the currently focused field
@@ -465,7 +542,7 @@
if (mCreateFillUiRunnable != null) {
if (sDebug) Slog.d(TAG, "start the pending fill UI request..");
- mCreateFillUiRunnable.run();
+ mHandler.post(mCreateFillUiRunnable);
mCreateFillUiRunnable = null;
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index e8c5299..3651a41 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -274,9 +274,14 @@
}
}
- // This method should not perform any I/O (e.g. do not call isBackupActivatedForUser),
- // it's used in multiple places where I/O waits would cause system lock-ups.
- private boolean isUserReadyForBackup(int userId) {
+ /**
+ * This method should not perform any I/O (e.g. do not call isBackupActivatedForUser),
+ * it's used in multiple places where I/O waits would cause system lock-ups.
+ * @param userId User id for which this operation should be performed.
+ * @return true if the user is ready for backup and false otherwise.
+ */
+ @Override
+ public boolean isUserReadyForBackup(int userId) {
return mUserServices.get(UserHandle.USER_SYSTEM) != null
&& mUserServices.get(userId) != null;
}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index be597d7..de6a080 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -766,8 +766,10 @@
backupManagerService.prepareOperationTimeout(
mEphemeralOpToken, restoreAgentTimeoutMillis, this, OP_TYPE_RESTORE_WAIT);
startedAgentRestore = true;
- mAgent.doRestore(mBackupData, appVersionCode, mNewState,
- mEphemeralOpToken, backupManagerService.getBackupManagerBinder());
+ mAgent.doRestoreWithExcludedKeys(mBackupData, appVersionCode, mNewState,
+ mEphemeralOpToken, backupManagerService.getBackupManagerBinder(),
+ mExcludedKeys.containsKey(packageName)
+ ? new ArrayList<>(mExcludedKeys.get(packageName)) : null);
} catch (Exception e) {
Slog.e(TAG, "Unable to call app for restore: " + packageName, e);
EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 8f1e156..e976811 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -18,6 +18,7 @@
package com.android.server.companion;
import static com.android.internal.util.CollectionUtils.size;
+import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
@@ -26,16 +27,15 @@
import android.annotation.CheckResult;
import android.annotation.Nullable;
import android.app.PendingIntent;
+import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.companion.ICompanionDeviceDiscoveryService;
-import android.companion.ICompanionDeviceDiscoveryServiceCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IFindDeviceCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
@@ -67,6 +67,9 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.PerUser;
+import com.android.internal.infra.ServiceConnector;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
@@ -118,12 +121,13 @@
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
private IDeviceIdleController mIdleController;
- private ServiceConnection mServiceConnection;
+ private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
private IAppOpsService mAppOpsManager;
private IFindDeviceCallback mFindDeviceCallback;
private AssociationRequest mRequest;
private String mCallingPackage;
+ private AndroidFuture<Association> mOngoingDeviceDiscovery;
private final Object mLock = new Object();
@@ -134,6 +138,19 @@
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
+
+ Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
+ mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
+ @Override
+ protected ServiceConnector<ICompanionDeviceDiscoveryService> create(int userId) {
+ return new ServiceConnector.Impl<>(
+ getContext(),
+ serviceIntent, 0/* bindingFlags */, userId,
+ ICompanionDeviceDiscoveryService.Stub::asInterface);
+ }
+ };
+
+
registerPackageMonitor();
}
@@ -187,7 +204,10 @@
private void cleanup() {
synchronized (mLock) {
- mServiceConnection = unbind(mServiceConnection);
+ AndroidFuture<Association> ongoingDeviceDiscovery = mOngoingDeviceDiscovery;
+ if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
+ ongoingDeviceDiscovery.cancel(true);
+ }
mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0);
mRequest = null;
mCallingPackage = null;
@@ -207,15 +227,6 @@
return null;
}
- @Nullable
- @CheckResult
- private ServiceConnection unbind(@Nullable ServiceConnection conn) {
- if (conn != null) {
- getContext().unbindService(conn);
- }
- return null;
- }
-
class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@Override
@@ -243,13 +254,27 @@
checkCallerIsSystemOr(callingPackage);
int userId = getCallingUserId();
checkUsesFeature(callingPackage, userId);
+
+ mFindDeviceCallback = callback;
+ mRequest = request;
+ mCallingPackage = callingPackage;
+ callback.asBinder().linkToDeath(CompanionDeviceManagerService.this /* recipient */, 0);
+
final long callingIdentity = Binder.clearCallingIdentity();
try {
- getContext().bindServiceAsUser(
- new Intent().setComponent(SERVICE_TO_BIND_TO),
- createServiceConnection(request, callback, callingPackage),
- Context.BIND_AUTO_CREATE,
- UserHandle.of(userId));
+ mOngoingDeviceDiscovery = mServiceConnectors.forUser(userId).postAsync(service -> {
+ AndroidFuture<Association> future = new AndroidFuture<>();
+ service.startDiscovery(request, callingPackage, callback, future);
+ return future;
+ }).whenComplete(uncheckExceptions((association, err) -> {
+ if (err == null) {
+ addAssociation(association);
+ } else {
+ Log.e(LOG_TAG, "Failed to discover device(s)", err);
+ callback.onFailure("No devices found: " + err.getMessage());
+ }
+ cleanup();
+ }));
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -386,82 +411,14 @@
return Binder.getCallingUid() == Process.SYSTEM_UID;
}
- private ServiceConnection createServiceConnection(
- final AssociationRequest request,
- final IFindDeviceCallback findDeviceCallback,
- final String callingPackage) {
- mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) {
- Slog.i(LOG_TAG,
- "onServiceConnected(name = " + name + ", service = "
- + service + ")");
- }
-
- mFindDeviceCallback = findDeviceCallback;
- mRequest = request;
- mCallingPackage = callingPackage;
-
- try {
- mFindDeviceCallback.asBinder().linkToDeath(
- CompanionDeviceManagerService.this, 0);
- } catch (RemoteException e) {
- cleanup();
- return;
- }
-
- try {
- ICompanionDeviceDiscoveryService.Stub
- .asInterface(service)
- .startDiscovery(
- request,
- callingPackage,
- findDeviceCallback,
- getServiceCallback());
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Error while initiating device discovery", e);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
- }
- };
- return mServiceConnection;
- }
-
- private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {
- return new ICompanionDeviceDiscoveryServiceCallback.Stub() {
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- try {
- return super.onTransact(code, data, reply, flags);
- } catch (Throwable e) {
- Slog.e(LOG_TAG, "Error during IPC", e);
- throw ExceptionUtils.propagate(e, RemoteException.class);
- }
- }
-
- @Override
- public void onDeviceSelected(String packageName, int userId, String deviceAddress) {
- addAssociation(userId, packageName, deviceAddress);
- cleanup();
- }
-
- @Override
- public void onDeviceSelectionCancel() {
- cleanup();
- }
- };
- }
-
void addAssociation(int userId, String packageName, String deviceAddress) {
- updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
- recordAssociation(packageName, deviceAddress);
+ addAssociation(new Association(userId, deviceAddress, packageName));
+ }
+
+ void addAssociation(Association association) {
+ updateSpecialAccessPermissionForAssociatedPackage(
+ association.companionAppPackage, association.userId);
+ recordAssociation(association);
}
void removeAssociation(int userId, String pkg, String deviceMacAddress) {
@@ -525,14 +482,15 @@
}, getContext(), packageName, userId).recycleOnUse());
}
- private void recordAssociation(String priviledgedPackage, String deviceAddress) {
+ private void recordAssociation(Association association) {
if (DEBUG) {
- Log.i(LOG_TAG, "recordAssociation(priviledgedPackage = " + priviledgedPackage
- + ", deviceAddress = " + deviceAddress + ")");
+ Log.i(LOG_TAG, "recordAssociation(" + association + ")");
}
- int userId = getCallingUserId();
- updateAssociations(associations -> CollectionUtils.add(associations,
- new Association(userId, deviceAddress, priviledgedPackage)));
+ updateAssociations(associations -> CollectionUtils.add(associations, association));
+ }
+
+ private void recordAssociation(String privilegedPackage, String deviceAddress) {
+ recordAssociation(new Association(getCallingUserId(), deviceAddress, privilegedPackage));
}
private void updateAssociations(Function<Set<Association>, Set<Association>> update) {
@@ -629,41 +587,6 @@
}
}
-
-
- private class Association {
- public final int uid;
- public final String deviceAddress;
- public final String companionAppPackage;
-
- private Association(int uid, String deviceAddress, String companionAppPackage) {
- this.uid = uid;
- this.deviceAddress = checkNotNull(deviceAddress);
- this.companionAppPackage = checkNotNull(companionAppPackage);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- Association that = (Association) o;
-
- if (uid != that.uid) return false;
- if (!deviceAddress.equals(that.deviceAddress)) return false;
- return companionAppPackage.equals(that.companionAppPackage);
-
- }
-
- @Override
- public int hashCode() {
- int result = uid;
- result = 31 * result + deviceAddress.hashCode();
- result = 31 * result + companionAppPackage.hashCode();
- return result;
- }
- }
-
private class ShellCmd extends ShellCommand {
public static final String USAGE = "help\n"
+ "list USER_ID\n"
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 203bc61..5b98f06 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -96,6 +96,7 @@
"android.hardware.tv.cec-V1.0-java",
"app-compat-annotations",
"vintf-vibrator-java",
+ "framework-tethering",
],
required: [
@@ -116,6 +117,7 @@
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.0-java",
"android.hardware.contexthub-V1.0-java",
+ "android.hardware.soundtrigger-V2.3-java",
"android.hidl.manager-V1.2-java",
"dnsresolver_aidl_interface-V2-java",
"netd_event_listener_interface-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 21f5f89..40a7dfc 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -63,6 +63,30 @@
public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10;
public static final int PACKAGE_APP_PREDICTOR = 11;
public static final int PACKAGE_TELEPHONY = 12;
+ public static final int PACKAGE_WIFI = 13;
+ public static final int PACKAGE_COMPANION = 14;
+
+ @IntDef(value = {
+ INTEGRITY_VERIFICATION_ALLOW,
+ INTEGRITY_VERIFICATION_REJECT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IntegrityVerificationResult {}
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the
+ * integrity component allows the install to proceed.
+ */
+ public static final int INTEGRITY_VERIFICATION_ALLOW = 1;
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManagerInternal#setIntegrityVerificationResult(int, int)} to indicate that the
+ * integrity component does not allow install to proceed.
+ */
+ public static final int INTEGRITY_VERIFICATION_REJECT = 0;
+
@IntDef(value = {
PACKAGE_SYSTEM,
PACKAGE_SETUP_WIZARD,
@@ -77,6 +101,8 @@
PACKAGE_INCIDENT_REPORT_APPROVER,
PACKAGE_APP_PREDICTOR,
PACKAGE_TELEPHONY,
+ PACKAGE_WIFI,
+ PACKAGE_COMPANION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KnownPackage {}
@@ -133,6 +159,12 @@
@PackageInfoFlags int flags, int filterCallingUid, int userId);
/**
+ * Retrieve CE data directory inode number of an application.
+ * Return 0 if there's error.
+ */
+ public abstract long getCeDataInode(String packageName, int userId);
+
+ /**
* Return a List of all application packages that are installed on the
* device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
* set, a list of all applications including those deleted with
@@ -275,6 +307,11 @@
public abstract ComponentName getDefaultHomeActivity(int userId);
/**
+ * @return The SystemUI service component name.
+ */
+ public abstract ComponentName getSystemUiServiceComponent();
+
+ /**
* Called by DeviceOwnerManagerService to set the package names of device owner and profile
* owners.
*/
@@ -336,14 +373,16 @@
* @param responseObj The response of the first phase of ephemeral resolution
* @param origIntent The original intent that triggered ephemeral resolution
* @param resolvedType The resolved type of the intent
- * @param callingPackage The name of the package requesting the ephemeral application
+ * @param callingPkg The app requesting the ephemeral application
+ * @param isRequesterInstantApp Whether or not the app requesting the ephemeral application
+ * is an instant app
* @param verificationBundle Optional bundle to pass to the installer for additional
* verification
* @param userId The ID of the user that triggered ephemeral resolution
*/
public abstract void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
- Intent origIntent, String resolvedType, String callingPackage,
- Bundle verificationBundle, int userId);
+ Intent origIntent, String resolvedType, String callingPkg,
+ boolean isRequesterInstantApp, Bundle verificationBundle, int userId);
/**
* Grants implicit access based on an interaction between two apps. This grants the target app
@@ -633,16 +672,12 @@
public abstract SparseArray<String> getAppsWithSharedUserIds();
/**
- * Get the value of attribute android:sharedUserId for the given packageName if specified,
- * otherwise {@code null}.
+ * Get all packages which share the same userId as the specified package, or an empty array
+ * if the package does not have a shared userId.
*/
- public abstract String getSharedUserIdForPackage(@NonNull String packageName);
-
- /**
- * Get all packages which specified the given sharedUserId as android:sharedUserId attribute
- * or an empty array if no package specified it.
- */
- public abstract String[] getPackagesForSharedUserId(@NonNull String sharedUserId, int userId);
+ @NonNull
+ public abstract String[] getSharedUserPackagesForPackage(@NonNull String packageName,
+ int userId);
/**
* Return if device is currently in a "core" boot environment, typically
@@ -822,4 +857,19 @@
/** Sets the enforcement of reading external storage */
public abstract void setReadExternalStorageEnforced(boolean enforced);
+
+ /**
+ * Allows the integrity component to respond to the
+ * {@link Intent#ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION package verification
+ * broadcast} to respond to the package manager. The response must include
+ * the {@code verificationCode} which is one of
+ * {@link #INTEGRITY_VERIFICATION_ALLOW} and {@link #INTEGRITY_VERIFICATION_REJECT}.
+ *
+ * @param verificationId pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
+ * @param verificationResult either {@link #INTEGRITY_VERIFICATION_ALLOW}
+ * or {@link #INTEGRITY_VERIFICATION_REJECT}.
+ */
+ public abstract void setIntegrityVerificationResult(int verificationId,
+ @IntegrityVerificationResult int verificationResult);
}
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index a2e9341..d84197c 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -57,7 +57,7 @@
* Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set
* restrictions enforced by the user.
*
- * @param userId target user id for the local restrictions.
+ * @param originatingUserId user id of the user where the restriction originated.
* @param restrictions a bundle of user restrictions.
* @param restrictionOwnerType determines which admin {@code userId} corresponds to.
* The admin can be either
@@ -70,8 +70,8 @@
* otherwise it will be applied just on the current user.
* @see OwnerType
*/
- public abstract void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
- @OwnerType int restrictionOwnerType);
+ public abstract void setDevicePolicyUserRestrictions(int originatingUserId,
+ @Nullable Bundle restrictions, @OwnerType int restrictionOwnerType);
/**
* Returns the "base" user restrictions.
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 1e5b915..73b6c7a 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -22,6 +22,8 @@
import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.UserHandle.USER_SYSTEM;
import android.annotation.UserIdInt;
import android.app.Activity;
@@ -41,10 +43,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
@@ -223,15 +222,6 @@
long mLastTimeChangeRealtime;
int mNumTimeChanged;
- // Bookkeeping about the identity of the "System UI" package, determined at runtime.
-
- /**
- * This permission must be defined by the canonical System UI package,
- * with protection level "signature".
- */
- private static final String SYSTEM_UI_SELF_PERMISSION =
- "android.permission.systemui.IDENTITY";
-
/**
* At boot we use SYSTEM_UI_SELF_PERMISSION to look up the definer's uid.
*/
@@ -3201,7 +3191,7 @@
}
void removeUserLocked(int userHandle) {
- if (userHandle == UserHandle.USER_SYSTEM) {
+ if (userHandle == USER_SYSTEM) {
// If we're told we're removing the system user, ignore it.
return;
}
@@ -3845,21 +3835,9 @@
}
int getSystemUiUid() {
- int sysUiUid = -1;
- final PackageManager pm = mContext.getPackageManager();
- try {
- PermissionInfo sysUiPerm = pm.getPermissionInfo(SYSTEM_UI_SELF_PERMISSION, 0);
- ApplicationInfo sysUi = pm.getApplicationInfo(sysUiPerm.packageName, 0);
- if ((sysUi.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
- sysUiUid = sysUi.uid;
- } else {
- Slog.e(TAG, "SysUI permission " + SYSTEM_UI_SELF_PERMISSION
- + " defined by non-privileged app " + sysUi.packageName
- + " - ignoring");
- }
- } catch (NameNotFoundException e) {
- }
- return sysUiUid;
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+ return pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
+ MATCH_SYSTEM_ONLY, USER_SYSTEM);
}
ClockReceiver getClockReceiver(AlarmManagerService service) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 119b987..470300e 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -16,6 +16,9 @@
package com.android.server;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.UserHandle.USER_SYSTEM;
+
import android.Manifest;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -42,6 +45,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Binder;
@@ -242,10 +246,10 @@
}
// DISALLOW_BLUETOOTH can only be set by DO or PO on the system user.
- if (userId == UserHandle.USER_SYSTEM
+ if (userId == USER_SYSTEM
&& UserRestrictionsUtils.restrictionsChanged(prevRestrictions,
newRestrictions, UserManager.DISALLOW_BLUETOOTH)) {
- if (userId == UserHandle.USER_SYSTEM && newRestrictions.getBoolean(
+ if (userId == USER_SYSTEM && newRestrictions.getBoolean(
UserManager.DISALLOW_BLUETOOTH)) {
updateOppLauncherComponentState(userId, true); // Sharing disallowed
sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED,
@@ -437,18 +441,18 @@
}
int systemUiUid = -1;
- try {
- // Check if device is configured with no home screen, which implies no SystemUI.
- boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen);
- if (!noHome) {
- systemUiUid = mContext.getPackageManager()
- .getPackageUidAsUser("com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
- UserHandle.USER_SYSTEM);
- }
+ // Check if device is configured with no home screen, which implies no SystemUI.
+ boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen);
+ if (!noHome) {
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+ systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
+ MATCH_SYSTEM_ONLY, USER_SYSTEM);
+ }
+ if (systemUiUid >= 0) {
Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid));
- } catch (PackageManager.NameNotFoundException e) {
+ } else {
// Some platforms, such as wearables do not have a system ui.
- Slog.w(TAG, "Unable to resolve SystemUI's UID.", e);
+ Slog.w(TAG, "Unable to resolve SystemUI's UID.");
}
mSystemUiUid = systemUiUid;
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 753c117..bb78ace 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -77,7 +77,6 @@
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.ISocketKeepaliveCallback;
-import android.net.ITetheringEventCallback;
import android.net.InetAddresses;
import android.net.IpMemoryStore;
import android.net.IpPrefix;
@@ -278,8 +277,6 @@
private MockableSystemProperties mSystemProperties;
- private TetheringManager mTetheringManager;
-
@VisibleForTesting
protected final PermissionMonitor mPermissionMonitor;
@@ -867,13 +864,6 @@
}
/**
- * Get a reference to the TetheringManager.
- */
- public TetheringManager getTetheringManager() {
- return TetheringManager.getInstance();
- }
-
- /**
* @see ProxyTracker
*/
public ProxyTracker makeProxyTracker(@NonNull Context context,
@@ -1072,8 +1062,6 @@
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mTetheringManager = mDeps.getTetheringManager();
-
mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
// Set up the listener for user state for creating user VPNs.
@@ -1887,14 +1875,6 @@
}
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_DATA_SAVER_CHANGED, restrictBackground ? 1 : 0, 0));
-
- // TODO: relocate this specific callback in Tethering.
- if (restrictBackground) {
- log("onRestrictBackgroundChanged(true): disabling tethering");
- mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
- mTetheringManager.stopTethering(ConnectivityManager.TETHERING_USB);
- mTetheringManager.stopTethering(ConnectivityManager.TETHERING_BLUETOOTH);
- }
}
};
@@ -2024,12 +2004,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid);
}
- private void enforceTetherAccessPermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.ACCESS_NETWORK_STATE,
- "ConnectivityService");
- }
-
private void enforceControlAlwaysOnVpnPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
@@ -2463,12 +2437,6 @@
mKeepaliveTracker.dump(pw);
pw.println();
- pw.println("TetheringManager logs:");
- pw.increaseIndent();
- TetheringManager.getInstance().dump(pw);
- pw.decreaseIndent();
-
- pw.println();
dumpAvoidBadWifiSettings(pw);
pw.println();
@@ -3993,183 +3961,55 @@
}
}
- // javadoc from interface
@Override
- public int tether(String iface, String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- if (isTetheringSupported()) {
- return mTetheringManager.tether(iface);
- } else {
- return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
- }
- }
-
- // javadoc from interface
- @Override
- public int untether(String iface, String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
-
- if (isTetheringSupported()) {
- return mTetheringManager.untether(iface);
- } else {
- return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
- }
- }
-
- // javadoc from interface
- @Override
+ @Deprecated
public int getLastTetherError(String iface) {
- enforceTetherAccessPermission();
-
- if (isTetheringSupported()) {
- return mTetheringManager.getLastTetherError(iface);
- } else {
- return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
- }
- }
-
- // TODO - proper iface API for selection by property, inspection, etc
- @Override
- public String[] getTetherableUsbRegexs() {
- enforceTetherAccessPermission();
- if (isTetheringSupported()) {
- return mTetheringManager.getTetherableUsbRegexs();
- } else {
- return new String[0];
- }
+ final TetheringManager tm = (TetheringManager) mContext.getSystemService(
+ Context.TETHERING_SERVICE);
+ return tm.getLastTetherError(iface);
}
@Override
- public String[] getTetherableWifiRegexs() {
- enforceTetherAccessPermission();
- if (isTetheringSupported()) {
- return mTetheringManager.getTetherableWifiRegexs();
- } else {
- return new String[0];
- }
- }
-
- @Override
- public String[] getTetherableBluetoothRegexs() {
- enforceTetherAccessPermission();
- if (isTetheringSupported()) {
- return mTetheringManager.getTetherableBluetoothRegexs();
- } else {
- return new String[0];
- }
- }
-
- @Override
- public int setUsbTethering(boolean enable, String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- if (isTetheringSupported()) {
- return mTetheringManager.setUsbTethering(enable);
- } else {
- return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
- }
- }
-
- // TODO - move iface listing, queries, etc to new module
- // javadoc from interface
- @Override
+ @Deprecated
public String[] getTetherableIfaces() {
- enforceTetherAccessPermission();
- return mTetheringManager.getTetherableIfaces();
+ final TetheringManager tm = (TetheringManager) mContext.getSystemService(
+ Context.TETHERING_SERVICE);
+ return tm.getTetherableIfaces();
}
@Override
+ @Deprecated
public String[] getTetheredIfaces() {
- enforceTetherAccessPermission();
- return mTetheringManager.getTetheredIfaces();
+ final TetheringManager tm = (TetheringManager) mContext.getSystemService(
+ Context.TETHERING_SERVICE);
+ return tm.getTetheredIfaces();
}
+
@Override
+ @Deprecated
public String[] getTetheringErroredIfaces() {
- enforceTetherAccessPermission();
- return mTetheringManager.getTetheringErroredIfaces();
+ final TetheringManager tm = (TetheringManager) mContext.getSystemService(
+ Context.TETHERING_SERVICE);
+
+ return tm.getTetheringErroredIfaces();
}
@Override
- public String[] getTetheredDhcpRanges() {
- enforceSettingsPermission();
- return mTetheringManager.getTetheredDhcpRanges();
+ @Deprecated
+ public String[] getTetherableUsbRegexs() {
+ final TetheringManager tm = (TetheringManager) mContext.getSystemService(
+ Context.TETHERING_SERVICE);
+
+ return tm.getTetherableUsbRegexs();
}
@Override
- public boolean isTetheringSupported(String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- return isTetheringSupported();
- }
-
- // if ro.tether.denied = true we default to no tethering
- // gservices could set the secure setting to 1 though to enable it on a build where it
- // had previously been turned off.
- private boolean isTetheringSupported() {
- int defaultVal = encodeBool(!mSystemProperties.get("ro.tether.denied").equals("true"));
- boolean tetherSupported = toBool(Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.TETHER_SUPPORTED, defaultVal));
- boolean tetherEnabledInSettings = tetherSupported
- && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
-
- // Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
- boolean adminUser = false;
- final long token = Binder.clearCallingIdentity();
- try {
- adminUser = mUserManager.isAdminUser();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return tetherEnabledInSettings && adminUser
- && mTetheringManager.hasTetherableConfiguration();
- }
-
- @Override
- public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi,
- String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- if (!isTetheringSupported()) {
- receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
- return;
- }
- mTetheringManager.startTethering(type, receiver, showProvisioningUi);
- }
-
- @Override
- public void stopTethering(int type, String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- mTetheringManager.stopTethering(type);
- }
-
- /**
- * Get the latest value of the tethering entitlement check.
- *
- * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns
- * out some such apps are observed to abuse this API, change to per-UID limits on this API
- * if it's really needed.
- */
- @Override
- public void getLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
- boolean showEntitlementUi, String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- mTetheringManager.requestLatestTetheringEntitlementResult(
- type, receiver, showEntitlementUi);
- }
-
- /** Register tethering event callback. */
- @Override
- public void registerTetheringEventCallback(ITetheringEventCallback callback,
- String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- mTetheringManager.registerTetheringEventCallback(callback);
- }
-
- /** Unregister tethering event callback. */
- @Override
- public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
- String callerPkg) {
- ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
- mTetheringManager.unregisterTetheringEventCallback(callback);
+ @Deprecated
+ public String[] getTetherableWifiRegexs() {
+ final TetheringManager tm = (TetheringManager) mContext.getSystemService(
+ Context.TETHERING_SERVICE);
+ return tm.getTetherableWifiRegexs();
}
// Called when we lose the default network and have no replacement yet.
@@ -5573,7 +5413,7 @@
* @param linkProperties the initial link properties of this network. They can be updated
* later : see {@link #updateLinkProperties}.
* @param networkCapabilities the initial capabilites of this network. They can be updated
- * later : see {@link #updateNetworkCapabilities}.
+ * 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.
@@ -5596,7 +5436,7 @@
ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
mDnsResolver, mNMS, factorySerialNumber);
// Make sure the network capabilities reflect what the agent info says.
- nai.setNetworkCapabilities(mixInCapabilities(nai, nc));
+ nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
final String extraInfo = networkInfo.getExtraInfo();
final String name = TextUtils.isEmpty(extraInfo)
? nai.networkCapabilities.getSSID() : extraInfo;
@@ -5950,11 +5790,7 @@
}
}
- final NetworkCapabilities prevNc;
- synchronized (nai) {
- prevNc = nai.networkCapabilities;
- nai.setNetworkCapabilities(newNc);
- }
+ final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc);
updateUids(nai, prevNc, newNc);
@@ -5963,7 +5799,7 @@
// the change we're processing can't affect any requests, it can only affect the listens
// on this network. We might have been called by rematchNetworkAndRequests when a
// network changed foreground state.
- processListenRequests(nai, true);
+ processListenRequests(nai);
} else {
// If the requestable capabilities have changed or the score changed, we can't have been
// called by rematchNetworkAndRequests, so it's safe to start a rematch.
@@ -6271,8 +6107,14 @@
updateAllVpnsCapabilities();
}
- private void processListenRequests(NetworkAgentInfo nai, boolean capabilitiesChanged) {
+ private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
// For consistency with previous behaviour, send onLost callbacks before onAvailable.
+ processNewlyLostListenRequests(nai);
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ processNewlySatisfiedListenRequests(nai);
+ }
+
+ private void processNewlyLostListenRequests(@NonNull final NetworkAgentInfo nai) {
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
NetworkRequest nr = nri.request;
if (!nr.isListen()) continue;
@@ -6281,11 +6123,9 @@
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
}
}
+ }
- if (capabilitiesChanged) {
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
- }
-
+ private void processNewlySatisfiedListenRequests(@NonNull final NetworkAgentInfo nai) {
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
NetworkRequest nr = nri.request;
if (!nr.isListen()) continue;
@@ -6468,19 +6308,20 @@
// before LegacyTypeTracker sends legacy broadcasts
for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
- // Second pass: process all listens.
- if (wasBackgroundNetwork != newNetwork.isBackgroundNetwork()) {
- // TODO : most of the following is useless because the only thing that changed
- // here is whether the network is a background network. Clean this up.
+ // Finally, process listen requests and update capabilities if the background state has
+ // changed for this network. For consistency with previous behavior, send onLost callbacks
+ // before onAvailable.
+ processNewlyLostListenRequests(newNetwork);
- NetworkCapabilities newNc = mixInCapabilities(newNetwork,
+ // Maybe the network changed background states. Update its capabilities.
+ final boolean backgroundChanged = wasBackgroundNetwork != newNetwork.isBackgroundNetwork();
+ if (backgroundChanged) {
+ final NetworkCapabilities newNc = mixInCapabilities(newNetwork,
newNetwork.networkCapabilities);
- if (Objects.equals(newNetwork.networkCapabilities, newNc)) return;
-
final int oldPermission = getNetworkPermission(newNetwork.networkCapabilities);
final int newPermission = getNetworkPermission(newNc);
- if (oldPermission != newPermission && newNetwork.created && !newNetwork.isVPN()) {
+ if (oldPermission != newPermission) {
try {
mNMS.setNetworkPermission(newNetwork.network.netId, newPermission);
} catch (RemoteException e) {
@@ -6488,53 +6329,11 @@
}
}
- final NetworkCapabilities prevNc;
- synchronized (newNetwork) {
- prevNc = newNetwork.networkCapabilities;
- newNetwork.setNetworkCapabilities(newNc);
- }
-
- updateUids(newNetwork, prevNc, newNc);
-
- if (newNetwork.getCurrentScore() == score
- && newNc.equalRequestableCapabilities(prevNc)) {
- // If the requestable capabilities haven't changed, and the score hasn't changed,
- // then the change we're processing can't affect any requests, it can only affect
- // the listens on this network.
- processListenRequests(newNetwork, true);
- } else {
- rematchAllNetworksAndRequests();
- notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_CAP_CHANGED);
- }
-
- if (prevNc != null) {
- final boolean oldMetered = prevNc.isMetered();
- final boolean newMetered = newNc.isMetered();
- final boolean meteredChanged = oldMetered != newMetered;
-
- if (meteredChanged) {
- maybeNotifyNetworkBlocked(newNetwork, oldMetered, newMetered,
- mRestrictBackground, mRestrictBackground);
- }
-
- final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING)
- != newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
-
- // Report changes that are interesting for network statistics tracking.
- if (meteredChanged || roamingChanged) {
- notifyIfacesChangedForNetworkStats();
- }
- }
-
- if (!newNc.hasTransport(TRANSPORT_VPN)) {
- // Tell VPNs about updated capabilities, since they may need to
- // bubble those changes through.
- updateAllVpnsCapabilities();
- }
-
- } else {
- processListenRequests(newNetwork, false);
+ newNetwork.getAndSetNetworkCapabilities(newNc);
+ notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
+
+ processNewlySatisfiedListenRequests(newNetwork);
}
/**
@@ -6719,9 +6518,8 @@
// NetworkCapabilities need to be set before sending the private DNS config to
// NetworkMonitor, otherwise NetworkMonitor cannot determine if validation is required.
- synchronized (networkAgent) {
- networkAgent.setNetworkCapabilities(networkAgent.networkCapabilities);
- }
+ networkAgent.getAndSetNetworkCapabilities(networkAgent.networkCapabilities);
+
handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
null);
@@ -7092,14 +6890,6 @@
// Turn airplane mode off
setAirplaneMode(false);
- if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
- // Untether
- String pkgName = mContext.getOpPackageName();
- for (String tether : getTetheredIfaces()) {
- untether(tether, pkgName);
- }
- }
-
if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
// Remove always-on package
synchronized (mVpns) {
diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java
index d8a2fe3..861c731 100644
--- a/services/core/java/com/android/server/CountryDetectorService.java
+++ b/services/core/java/com/android/server/CountryDetectorService.java
@@ -16,14 +16,6 @@
package com.android.server;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.HashMap;
-
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.DumpUtils;
-import com.android.server.location.ComprehensiveCountryDetector;
-
import android.content.Context;
import android.location.Country;
import android.location.CountryListener;
@@ -36,17 +28,25 @@
import android.util.Printer;
import android.util.Slog;
+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 java.io.FileDescriptor;
+import java.io.PrintWriter;
+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 through {@link ComprehensiveCountryDetector}.
*
* @hide
*/
-public class CountryDetectorService extends ICountryDetector.Stub implements Runnable {
+public class CountryDetectorService extends ICountryDetector.Stub {
/**
- * The class represents the remote listener, it will also removes itself
- * from listener list when the remote process was died.
+ * The class represents the remote listener, it will also removes itself from listener list when
+ * the remote process was died.
*/
private final class Receiver implements IBinder.DeathRecipient {
private final ICountryListener mListener;
@@ -79,9 +79,11 @@
}
}
- private final static String TAG = "CountryDetector";
+ private static final String TAG = "CountryDetector";
- /** Whether to dump the state of the country detector service to bugreports */
+ /**
+ * Whether to dump the state of the country detector service to bugreports
+ */
private static final boolean DEBUG = false;
private final HashMap<IBinder, Receiver> mReceivers;
@@ -92,15 +94,21 @@
private CountryListener mLocationBasedDetectorListener;
public CountryDetectorService(Context context) {
+ this(context, BackgroundThread.getHandler());
+ }
+
+ @VisibleForTesting
+ CountryDetectorService(Context context, Handler handler) {
super();
- mReceivers = new HashMap<IBinder, Receiver>();
+ mReceivers = new HashMap<>();
mContext = context;
+ mHandler = handler;
}
@Override
public Country detectCountry() {
if (!mSystemReady) {
- return null; // server not yet active
+ return null; // server not yet active
} else {
return mCountryDetector.detectCountry();
}
@@ -154,9 +162,8 @@
}
}
-
protected void notifyReceivers(Country country) {
- synchronized(mReceivers) {
+ synchronized (mReceivers) {
for (Receiver receiver : mReceivers.values()) {
try {
receiver.getListener().onCountryDetected(country);
@@ -170,38 +177,23 @@
void systemRunning() {
// Shall we wait for the initialization finish.
- BackgroundThread.getHandler().post(this);
+ mHandler.post(
+ () -> {
+ initialize();
+ mSystemReady = true;
+ });
}
private void initialize() {
mCountryDetector = new ComprehensiveCountryDetector(mContext);
- mLocationBasedDetectorListener = new CountryListener() {
- public void onCountryDetected(final Country country) {
- mHandler.post(new Runnable() {
- public void run() {
- notifyReceivers(country);
- }
- });
- }
- };
- }
-
- public void run() {
- mHandler = new Handler();
- initialize();
- mSystemReady = true;
+ mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country));
}
protected void setCountryListener(final CountryListener listener) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mCountryDetector.setCountryListener(listener);
- }
- });
+ mHandler.post(() -> mCountryDetector.setCountryListener(listener));
}
- // For testing
+ @VisibleForTesting
boolean isSystemReady() {
return mSystemReady;
}
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index cbf2a62..bbcfdc6 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -56,6 +56,7 @@
import com.android.server.location.GnssMeasurementsProvider;
import com.android.server.location.GnssNavigationMessageProvider;
import com.android.server.location.GnssStatusListenerHelper;
+import com.android.server.location.LocationUsageLogger;
import com.android.server.location.RemoteListenerHelper;
import java.io.FileDescriptor;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 54dfc98..cad917b 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -104,6 +104,7 @@
import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.LocationSettingsStore;
+import com.android.server.location.LocationUsageLogger;
import com.android.server.location.MockProvider;
import com.android.server.location.PassiveProvider;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -158,21 +159,15 @@
}
}
- private static final String TAG = "LocationManagerService";
+ public static final String TAG = "LocationManagerService";
public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
private static final String WAKELOCK_KEY = "*location*";
- // Location resolution level: no location data whatsoever
private static final int RESOLUTION_LEVEL_NONE = 0;
- // Location resolution level: coarse location data only
private static final int RESOLUTION_LEVEL_COARSE = 1;
- // Location resolution level: fine location data
private static final int RESOLUTION_LEVEL_FINE = 2;
- private static final String ACCESS_LOCATION_EXTRA_COMMANDS =
- android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS;
-
private static final String NETWORK_LOCATION_SERVICE_ACTION =
"com.android.location.service.v3.NetworkLocationProvider";
private static final String FUSED_LOCATION_SERVICE_ACTION =
@@ -198,6 +193,8 @@
private final Object mLock = new Object();
private final Context mContext;
private final Handler mHandler;
+ private final LocationSettingsStore mSettingsStore;
+ private final LocationUsageLogger mLocationUsageLogger;
private AppOpsManager mAppOps;
private PackageManager mPackageManager;
@@ -205,8 +202,6 @@
private ActivityManager mActivityManager;
private UserManager mUserManager;
- private LocationSettingsStore mSettingsStore;
-
private GeofenceManager mGeofenceManager;
private LocationFudger mLocationFudger;
private GeocoderProxy mGeocodeProvider;
@@ -219,12 +214,12 @@
// list of currently active providers
@GuardedBy("mLock")
- private final ArrayList<LocationProvider> mProviders = new ArrayList<>();
+ 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<LocationProvider> mRealProviders = new ArrayList<>();
+ private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>();
@GuardedBy("mLock")
private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
@@ -251,12 +246,10 @@
@PowerManager.LocationPowerSaveMode
private int mBatterySaverMode;
- @GuardedBy("mLock")
- private final LocationUsageLogger mLocationUsageLogger;
-
private LocationManagerService(Context context) {
mContext = context;
mHandler = FgThread.getHandler();
+ mSettingsStore = new LocationSettingsStore(mContext, mHandler);
mLocationUsageLogger = new LocationUsageLogger();
// Let the package manager query which are the default location
@@ -274,6 +267,8 @@
}
private void onSystemReady() {
+ mSettingsStore.onSystemReady();
+
synchronized (mLock) {
mPackageManager = mContext.getPackageManager();
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -281,7 +276,6 @@
mActivityManager = mContext.getSystemService(ActivityManager.class);
mUserManager = mContext.getSystemService(UserManager.class);
- mSettingsStore = new LocationSettingsStore(mContext, mHandler);
mLocationFudger = new LocationFudger(mContext, mHandler);
mGeofenceManager = new GeofenceManager(mContext, mSettingsStore);
@@ -343,11 +337,6 @@
onLocationModeChangedLocked(userId);
}
});
- mSettingsStore.addOnLocationProvidersAllowedChangedListener((userId) -> {
- synchronized (mLock) {
- onProviderAllowedChangedLocked(userId);
- }
- });
mSettingsStore.addOnBackgroundThrottleIntervalChangedListener(() -> {
synchronized (mLock) {
onBackgroundThrottleIntervalChangedLocked();
@@ -426,14 +415,14 @@
for (Receiver receiver : mReceivers.values()) {
receiver.updateMonitoring(true);
}
- for (LocationProvider p : mProviders) {
+ for (LocationProviderManager p : mProviders) {
applyRequirementsLocked(p);
}
}
@GuardedBy("mLock")
private void onPermissionsChangedLocked() {
- for (LocationProvider p : mProviders) {
+ for (LocationProviderManager p : mProviders) {
applyRequirementsLocked(p);
}
}
@@ -453,7 +442,7 @@
mBatterySaverMode = newLocationMode;
- for (LocationProvider p : mProviders) {
+ for (LocationProviderManager p : mProviders) {
applyRequirementsLocked(p);
}
}
@@ -461,7 +450,7 @@
@GuardedBy("mLock")
private void onScreenStateChangedLocked() {
if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
- for (LocationProvider p : mProviders) {
+ for (LocationProviderManager p : mProviders) {
applyRequirementsLocked(p);
}
}
@@ -474,18 +463,11 @@
}
Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION);
- intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabled());
+ intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId));
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
- for (LocationProvider p : mProviders) {
- p.onLocationModeChangedLocked(userId);
- }
- }
-
- @GuardedBy("mLock")
- private void onProviderAllowedChangedLocked(int userId) {
- for (LocationProvider p : mProviders) {
- p.onAllowedChangedLocked(userId);
+ for (LocationProviderManager p : mProviders) {
+ p.onUseableChangedLocked(userId);
}
}
@@ -539,21 +521,21 @@
@GuardedBy("mLock")
private void onBackgroundThrottleIntervalChangedLocked() {
- for (LocationProvider provider : mProviders) {
+ for (LocationProviderManager provider : mProviders) {
applyRequirementsLocked(provider);
}
}
@GuardedBy("mLock")
private void onBackgroundThrottleWhitelistChangedLocked() {
- for (LocationProvider p : mProviders) {
+ for (LocationProviderManager p : mProviders) {
applyRequirementsLocked(p);
}
}
@GuardedBy("lock")
private void onIgnoreSettingsWhitelistChangedLocked() {
- for (LocationProvider p : mProviders) {
+ for (LocationProviderManager p : mProviders) {
applyRequirementsLocked(p);
}
}
@@ -642,14 +624,15 @@
@GuardedBy("mLock")
private void initializeProvidersLocked() {
// create a passive location provider, which is always enabled
- LocationProvider passiveProviderManager = new LocationProvider(PASSIVE_PROVIDER);
+ 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
- LocationProvider gnssProviderManager = new LocationProvider(GPS_PROVIDER, true);
+ LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER);
mRealProviders.add(gnssProviderManager);
addProviderLocked(gnssProviderManager);
@@ -680,7 +663,8 @@
ensureFallbackFusedProviderPresentLocked(pkgs);
// bind to network provider
- LocationProvider networkProviderManager = new LocationProvider(NETWORK_PROVIDER, true);
+ LocationProviderManager networkProviderManager = new LocationProviderManager(
+ NETWORK_PROVIDER);
LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
mContext,
networkProviderManager,
@@ -697,7 +681,7 @@
}
// bind to fused provider
- LocationProvider fusedProviderManager = new LocationProvider(FUSED_PROVIDER);
+ LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER);
LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
mContext,
fusedProviderManager,
@@ -770,7 +754,7 @@
Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
Integer.parseInt(fragments[8]) /* powerRequirement */,
Integer.parseInt(fragments[9]) /* accuracy */);
- LocationProvider testProviderManager = new LocationProvider(name);
+ LocationProviderManager testProviderManager = new LocationProviderManager(name);
addProviderLocked(testProviderManager);
testProviderManager.attachLocked(
new MockProvider(mContext, testProviderManager, properties));
@@ -792,22 +776,19 @@
onUserProfilesChangedLocked();
// let providers know the current user has changed
- for (LocationProvider p : mProviders) {
- p.onCurrentUserChangedLocked(oldUserId);
- p.onCurrentUserChangedLocked(mCurrentUserId);
+ for (LocationProviderManager p : mProviders) {
+ p.onUseableChangedLocked(oldUserId);
+ p.onUseableChangedLocked(mCurrentUserId);
}
}
/**
* Location provider manager, manages a LocationProvider.
*/
- class LocationProvider implements AbstractLocationProvider.LocationProviderManager {
+ class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager {
private final String mName;
- // whether this provider should respect LOCATION_PROVIDERS_ALLOWED (ie gps and network)
- private final boolean mIsManagedBySettings;
-
// remember to clear binder identity before invoking any provider operation
@GuardedBy("mLock")
@Nullable
@@ -816,36 +797,26 @@
@GuardedBy("mLock")
private SparseArray<Boolean> mUseable; // combined state for each user id
@GuardedBy("mLock")
- private boolean mAllowed; // state of LOCATION_PROVIDERS_ALLOWED
- @GuardedBy("mLock")
private boolean mEnabled; // state of provider
@GuardedBy("mLock")
@Nullable
private ProviderProperties mProperties;
- private LocationProvider(String name) {
- this(name, false);
- }
-
- private LocationProvider(String name, boolean isManagedBySettings) {
+ private LocationProviderManager(String name) {
mName = name;
- mIsManagedBySettings = isManagedBySettings;
mProvider = null;
mUseable = new SparseArray<>(1);
- mAllowed = !mIsManagedBySettings;
mEnabled = false;
mProperties = null;
- if (mIsManagedBySettings) {
- // since we assume providers are disabled by default
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- "-" + mName,
- mCurrentUserId);
- }
+ // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ "-" + mName,
+ mCurrentUserId);
}
@GuardedBy("mLock")
@@ -861,7 +832,7 @@
// 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(false, mCurrentUserId);
+ onUseableChangedLocked(mCurrentUserId);
}
public String getName() {
@@ -931,9 +902,6 @@
pw.println("useable=" + isUseableLocked(mCurrentUserId));
if (!isUseableLocked(mCurrentUserId)) {
pw.println("attached=" + (mProvider != null));
- if (mIsManagedBySettings) {
- pw.println("allowed=" + mAllowed);
- }
pw.println("enabled=" + mEnabled);
}
@@ -972,7 +940,7 @@
return;
}
synchronized (mLock) {
- LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
+ LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
Slog.w(TAG, "reportLocationBatch() called without user permission");
return;
@@ -997,7 +965,7 @@
// 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(false, mCurrentUserId);
+ onUseableChangedLocked(mCurrentUserId);
}
}
@@ -1009,43 +977,6 @@
}
@GuardedBy("mLock")
- public void onLocationModeChangedLocked(int userId) {
- if (!isCurrentProfileLocked(userId)) {
- return;
- }
-
- onUseableChangedLocked(false, userId);
- }
-
- @GuardedBy("mLock")
- public void onAllowedChangedLocked(int userId) {
- if (!isCurrentProfileLocked(userId)) {
- return;
- }
-
- if (mIsManagedBySettings) {
- boolean allowed = mSettingsStore.getLocationProvidersAllowed(
- mCurrentUserId).contains(mName);
-
- if (allowed == mAllowed) {
- return;
- }
-
- if (D) {
- Log.d(TAG, mName + " provider allowed is now " + mAllowed);
- }
-
- mAllowed = allowed;
- onUseableChangedLocked(true, userId);
- }
- }
-
- @GuardedBy("mLock")
- public void onCurrentUserChangedLocked(int userId) {
- onUseableChangedLocked(false, userId);
- }
-
- @GuardedBy("mLock")
public boolean isUseableLocked() {
return isUseableLocked(mCurrentUserId);
}
@@ -1056,38 +987,13 @@
}
@GuardedBy("mLock")
- public void onUseableChangedLocked(boolean isAllowedChanged, int userId) {
+ public void onUseableChangedLocked(int 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 useableIgnoringAllowed = mProvider != null && mProviders.contains(this)
+ boolean useable = mProvider != null && mProviders.contains(this)
&& isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId)
&& mEnabled;
- boolean useable = useableIgnoringAllowed && mAllowed;
-
- // update deprecated provider allowed settings for backwards compatibility
- if (mIsManagedBySettings) {
- // a "-" change derived from the allowed setting should not be overwritten, but a
- // "+" change should be corrected if necessary
- if (useableIgnoringAllowed && !isAllowedChanged) {
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- "+" + mName,
- userId);
- } else if (!useableIgnoringAllowed) {
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- "-" + mName,
- userId);
- }
-
- Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
- intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
- intent.putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable);
- mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
- }
if (useable == isUseableLocked(userId)) {
return;
@@ -1098,6 +1004,21 @@
Log.d(TAG, "[u" + userId + "] " + mName + " provider useable = " + useable);
}
+ // 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);
+
+ Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
+ intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
+ intent.putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, useable);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+ }
+
if (!useable) {
// If any provider has been disabled, clear all last locations for all
// providers. This is to be on the safe side in case a provider has location
@@ -1110,7 +1031,7 @@
}
}
- private class MockLocationProvider extends LocationProvider {
+ private class MockLocationProvider extends LocationProviderManager {
private ProviderRequest mCurrentRequest;
@@ -1260,7 +1181,8 @@
// 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()) {
- LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
+ LocationProviderManager provider = getLocationProviderLocked(
+ updateRecord.mProvider);
if (provider == null) {
continue;
}
@@ -1536,32 +1458,29 @@
}
@GuardedBy("mLock")
- private void addProviderLocked(LocationProvider provider) {
+ private void addProviderLocked(LocationProviderManager provider) {
Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
mProviders.add(provider);
- // allowed state may change while provider was inactive
- provider.onAllowedChangedLocked(mCurrentUserId);
-
// 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(false, mCurrentUserId);
+ provider.onUseableChangedLocked(mCurrentUserId);
}
@GuardedBy("mLock")
- private void removeProviderLocked(LocationProvider provider) {
+ 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(false, mCurrentUserId);
+ provider.onUseableChangedLocked(mCurrentUserId);
}
}
@GuardedBy("mLock")
@Nullable
- private LocationProvider getLocationProviderLocked(String providerName) {
- for (LocationProvider provider : mProviders) {
+ private LocationProviderManager getLocationProviderLocked(String providerName) {
+ for (LocationProviderManager provider : mProviders) {
if (providerName.equals(provider.getName())) {
return provider;
}
@@ -1612,7 +1531,7 @@
// network and fused providers are ok with COARSE or FINE
return RESOLUTION_LEVEL_COARSE;
} else {
- for (LocationProvider lp : mProviders) {
+ for (LocationProviderManager lp : mProviders) {
if (!lp.getName().equals(provider)) {
continue;
}
@@ -1712,7 +1631,7 @@
public List<String> getAllProviders() {
synchronized (mLock) {
ArrayList<String> providers = new ArrayList<>(mProviders.size());
- for (LocationProvider provider : mProviders) {
+ for (LocationProviderManager provider : mProviders) {
String name = provider.getName();
if (FUSED_PROVIDER.equals(name)) {
continue;
@@ -1733,7 +1652,7 @@
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
synchronized (mLock) {
ArrayList<String> providers = new ArrayList<>(mProviders.size());
- for (LocationProvider provider : mProviders) {
+ for (LocationProviderManager provider : mProviders) {
String name = provider.getName();
if (FUSED_PROVIDER.equals(name)) {
continue;
@@ -1783,7 +1702,7 @@
}
@GuardedBy("mLock")
- private void updateProviderUseableLocked(LocationProvider provider) {
+ private void updateProviderUseableLocked(LocationProviderManager provider) {
boolean useable = provider.isUseableLocked();
ArrayList<Receiver> deadReceivers = null;
@@ -1822,14 +1741,14 @@
@GuardedBy("mLock")
private void applyRequirementsLocked(String providerName) {
- LocationProvider provider = getLocationProviderLocked(providerName);
+ LocationProviderManager provider = getLocationProviderLocked(providerName);
if (provider != null) {
applyRequirementsLocked(provider);
}
}
@GuardedBy("mLock")
- private void applyRequirementsLocked(LocationProvider provider) {
+ private void applyRequirementsLocked(LocationProviderManager provider) {
ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
WorkSource worksource = new WorkSource();
ProviderRequest providerRequest = new ProviderRequest();
@@ -2279,7 +2198,7 @@
throw new IllegalArgumentException("provider name must not be null");
}
- LocationProvider provider = getLocationProviderLocked(name);
+ LocationProviderManager provider = getLocationProviderLocked(name);
if (provider == null) {
throw new IllegalArgumentException("provider doesn't exist: " + name);
}
@@ -2401,7 +2320,7 @@
// or use the fused provider
String name = request.getProvider();
if (name == null) name = LocationManager.FUSED_PROVIDER;
- LocationProvider provider = getLocationProviderLocked(name);
+ LocationProviderManager provider = getLocationProviderLocked(name);
if (provider == null) return null;
// only the current user or location providers may get location this way
@@ -2531,7 +2450,7 @@
"Access Fine Location permission not granted to inject Location");
synchronized (mLock) {
- LocationProvider provider = getLocationProviderLocked(location.getProvider());
+ LocationProviderManager provider = getLocationProviderLocked(location.getProvider());
if (provider == null || !provider.isUseableLocked()) {
return false;
}
@@ -2702,6 +2621,10 @@
// throw NullPointerException to remain compatible with previous implementation
throw new NullPointerException();
}
+
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, null);
+
synchronized (mLock) {
checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
providerName);
@@ -2711,13 +2634,7 @@
LocationStatsEnums.API_SEND_EXTRA_COMMAND,
providerName);
- // and check for ACCESS_LOCATION_EXTRA_COMMANDS
- if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
- != PERMISSION_GRANTED)) {
- throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
- }
-
- LocationProvider provider = getLocationProviderLocked(providerName);
+ LocationProviderManager provider = getLocationProviderLocked(providerName);
if (provider != null) {
provider.sendExtraCommand(command, extras);
}
@@ -2740,7 +2657,7 @@
@Override
public ProviderProperties getProviderProperties(String providerName) {
synchronized (mLock) {
- LocationProvider provider = getLocationProviderLocked(providerName);
+ LocationProviderManager provider = getLocationProviderLocked(providerName);
if (provider == null) {
return null;
}
@@ -2753,7 +2670,7 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
Manifest.permission.READ_DEVICE_CONFIG + " permission required");
synchronized (mLock) {
- for (LocationProvider provider : mProviders) {
+ for (LocationProviderManager provider : mProviders) {
if (provider.getPackagesLocked().contains(packageName)) {
return true;
}
@@ -2767,7 +2684,7 @@
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
Manifest.permission.READ_DEVICE_CONFIG + " permission required");
synchronized (mLock) {
- LocationProvider provider = getLocationProviderLocked(providerName);
+ LocationProviderManager provider = getLocationProviderLocked(providerName);
return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
}
}
@@ -2805,10 +2722,6 @@
}
}
- private boolean isLocationEnabled() {
- return isLocationEnabledForUser(mCurrentUserId);
- }
-
@Override
public boolean isLocationEnabledForUser(int userId) {
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
@@ -2840,7 +2753,7 @@
if (FUSED_PROVIDER.equals(providerName)) return false;
synchronized (mLock) {
- LocationProvider provider = getLocationProviderLocked(providerName);
+ LocationProviderManager provider = getLocationProviderLocked(providerName);
return provider != null && provider.isUseableLocked(userId);
}
}
@@ -2879,7 +2792,7 @@
}
@GuardedBy("mLock")
- private void handleLocationChangedLocked(Location location, LocationProvider provider) {
+ private void handleLocationChangedLocked(Location location, LocationProviderManager provider) {
if (!mProviders.contains(provider)) {
return;
}
@@ -3111,7 +3024,7 @@
synchronized (mLock) {
long identity = Binder.clearCallingIdentity();
try {
- LocationProvider oldProvider = getLocationProviderLocked(name);
+ LocationProviderManager oldProvider = getLocationProviderLocked(name);
if (oldProvider != null) {
removeProviderLocked(oldProvider);
}
@@ -3135,7 +3048,7 @@
synchronized (mLock) {
long identity = Binder.clearCallingIdentity();
try {
- LocationProvider testProvider = getLocationProviderLocked(name);
+ LocationProviderManager testProvider = getLocationProviderLocked(name);
if (testProvider == null || !testProvider.isMock()) {
return;
}
@@ -3143,8 +3056,8 @@
removeProviderLocked(testProvider);
// reinstate real provider if available
- LocationProvider realProvider = null;
- for (LocationProvider provider : mRealProviders) {
+ LocationProviderManager realProvider = null;
+ for (LocationProviderManager provider : mRealProviders) {
if (name.equals(provider.getName())) {
realProvider = provider;
break;
@@ -3168,7 +3081,7 @@
}
synchronized (mLock) {
- LocationProvider testProvider = getLocationProviderLocked(providerName);
+ LocationProviderManager testProvider = getLocationProviderLocked(providerName);
if (testProvider == null || !testProvider.isMock()) {
throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
}
@@ -3192,7 +3105,7 @@
}
synchronized (mLock) {
- LocationProvider testProvider = getLocationProviderLocked(providerName);
+ LocationProviderManager testProvider = getLocationProviderLocked(providerName);
if (testProvider == null || !testProvider.isMock()) {
throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
}
@@ -3210,7 +3123,7 @@
}
synchronized (mLock) {
- LocationProvider testProvider = getLocationProviderLocked(providerName);
+ LocationProviderManager testProvider = getLocationProviderLocked(providerName);
if (testProvider == null || !testProvider.isMock()) {
throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
}
@@ -3247,7 +3160,7 @@
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
ipw.println("Current user: " + mCurrentUserId + " " + Arrays.toString(
mCurrentUserProfiles));
- ipw.println("Location Mode: " + isLocationEnabled());
+ ipw.println("Location Mode: " + isLocationEnabledForUser(mCurrentUserId));
ipw.println("Battery Saver Location Mode: "
+ locationPowerSaveModeToString(mBatterySaverMode));
@@ -3319,7 +3232,7 @@
ipw.println("Location Providers:");
ipw.increaseIndent();
- for (LocationProvider provider : mProviders) {
+ for (LocationProviderManager provider : mProviders) {
provider.dumpLocked(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 c0f10a3..92d1da5 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+
import android.Manifest;
import android.app.AppOpsManager;
import android.app.PendingIntent;
@@ -37,6 +39,7 @@
import android.os.UserHandle;
import android.service.carrier.CarrierMessagingService;
import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Slog;
@@ -523,11 +526,11 @@
// Grant permission for the carrier app.
Intent intent = new Intent(action);
- TelephonyManager telephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- List<String> carrierPackages =
- telephonyManager.getCarrierPackageNamesForIntentAndPhone(
- intent, SubscriptionManager.getPhoneId(subId));
+ TelephonyManager telephonyManager = (TelephonyManager)
+ mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ List<String> carrierPackages = telephonyManager
+ .getCarrierPackageNamesForIntentAndPhone(
+ intent, getPhoneIdFromSubId(subId));
if (carrierPackages != null && carrierPackages.size() == 1) {
LocalServices.getService(UriGrantsManagerInternal.class)
.grantUriPermissionFromIntent(callingUid, carrierPackages.get(0),
@@ -539,4 +542,13 @@
return contentUri;
}
}
+
+ private int getPhoneIdFromSubId(int subId) {
+ SubscriptionManager subManager = (SubscriptionManager)
+ mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ if (subManager == null) return INVALID_SIM_SLOT_INDEX;
+ SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId);
+ if (info == null) return INVALID_SIM_SLOT_INDEX;
+ return info.getSimSlotIndex();
+ }
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 840b7af..0d496b6 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -58,6 +58,7 @@
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;
@@ -1023,7 +1024,10 @@
NetworkStack.checkNetworkStackPermission(mContext);
// an odd number of addrs will fail
try {
- mNetdService.tetherStartWithConfiguration(usingLegacyDnsProxy, dhcpRange);
+ final TetherConfigParcel config = new TetherConfigParcel();
+ config.usingLegacyDnsProxy = usingLegacyDnsProxy;
+ config.dhcpRanges = dhcpRange;
+ mNetdService.tetherStartWithConfiguration(config);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index b0b45f4..39be311 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -35,11 +35,11 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
+import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.NtpTrustedTime;
import android.util.TimeUtils;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.DumpUtils;
import java.io.FileDescriptor;
@@ -137,7 +137,7 @@
private void registerForTelephonyIntents() {
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+ intentFilter.addAction(TelephonyManager.ACTION_NETWORK_SET_TIME);
mContext.registerReceiver(mNitzReceiver, intentFilter);
}
@@ -247,7 +247,7 @@
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DBG) Log.d(TAG, "Received " + action);
- if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
+ if (TelephonyManager.ACTION_NETWORK_SET_TIME.equals(action)) {
mNitzTimeSetTime = SystemClock.elapsedRealtime();
}
}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 521b393..deff440 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.SystemProperties;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -82,6 +83,12 @@
static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
"watchdog_explicit_health_check_enabled";
+ // TODO: make the following values configurable via DeviceConfig
+ private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS =
+ TimeUnit.SECONDS.toMillis(30);
+ private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
+
+
public static final int FAILURE_REASON_UNKNOWN = 0;
public static final int FAILURE_REASON_NATIVE_CRASH = 1;
public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
@@ -110,6 +117,8 @@
// Whether explicit health checks are enabled or not
private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
+ private long mNumberOfNativeCrashPollsRemaining;
+
private static final int DB_VERSION = 1;
private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
private static final String TAG_PACKAGE = "package";
@@ -188,6 +197,7 @@
mHealthCheckController = controller;
mConnectivityModuleConnector = connectivityModuleConnector;
mSystemClock = clock;
+ mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
loadFromFile();
}
@@ -336,38 +346,70 @@
if (mAllObservers.isEmpty()) {
return;
}
+ boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
+ || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
+ if (requiresImmediateAction) {
+ handleFailureImmediately(packages, failureReason);
+ } else {
+ for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
+ VersionedPackage versionedPackage = packages.get(pIndex);
+ // Observer that will receive failure for versionedPackage
+ PackageHealthObserver currentObserverToNotify = null;
+ int currentObserverImpact = Integer.MAX_VALUE;
- for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- VersionedPackage versionedPackage = packages.get(pIndex);
- // Observer that will receive failure for versionedPackage
- PackageHealthObserver currentObserverToNotify = null;
- int currentObserverImpact = Integer.MAX_VALUE;
-
- // Find observer with least user impact
- for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- ObserverInternal observer = mAllObservers.valueAt(oIndex);
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null
- && observer.onPackageFailureLocked(
- versionedPackage.getPackageName())) {
- int impact = registeredObserver.onHealthCheckFailed(versionedPackage);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
- && impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
- currentObserverImpact = impact;
+ // Find observer with least user impact
+ for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
+ ObserverInternal observer = mAllObservers.valueAt(oIndex);
+ PackageHealthObserver registeredObserver = observer.registeredObserver;
+ if (registeredObserver != null
+ && observer.onPackageFailureLocked(
+ versionedPackage.getPackageName())) {
+ int impact = registeredObserver.onHealthCheckFailed(
+ versionedPackage, failureReason);
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+ && impact < currentObserverImpact) {
+ currentObserverToNotify = registeredObserver;
+ currentObserverImpact = impact;
+ }
}
}
- }
- // Execute action with least user impact
- if (currentObserverToNotify != null) {
- currentObserverToNotify.execute(versionedPackage, failureReason);
+ // Execute action with least user impact
+ if (currentObserverToNotify != null) {
+ currentObserverToNotify.execute(versionedPackage, failureReason);
+ }
}
}
}
});
}
+ /**
+ * For native crashes or explicit health check failures, call directly into each observer to
+ * mitigate the error without going through failure threshold logic.
+ */
+ private void handleFailureImmediately(List<VersionedPackage> packages,
+ @FailureReasons int failureReason) {
+ VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
+ PackageHealthObserver currentObserverToNotify = null;
+ int currentObserverImpact = Integer.MAX_VALUE;
+ for (ObserverInternal observer: mAllObservers.values()) {
+ PackageHealthObserver registeredObserver = observer.registeredObserver;
+ if (registeredObserver != null) {
+ int impact = registeredObserver.onHealthCheckFailed(
+ failingPackage, failureReason);
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+ && impact < currentObserverImpact) {
+ currentObserverToNotify = registeredObserver;
+ currentObserverImpact = impact;
+ }
+ }
+ }
+ if (currentObserverToNotify != null) {
+ currentObserverToNotify.execute(failingPackage, failureReason);
+ }
+ }
+
// 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
@@ -400,6 +442,37 @@
}
}
+ /**
+ * This method should be only called on mShortTaskHandler, since it modifies
+ * {@link #mNumberOfNativeCrashPollsRemaining}.
+ */
+ private void checkAndMitigateNativeCrashes() {
+ mNumberOfNativeCrashPollsRemaining--;
+ // Check if native watchdog reported a crash
+ if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
+ // We rollback everything available when crash is unattributable
+ onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
+ // we stop polling after an attempt to execute rollback, regardless of whether the
+ // attempt succeeds or not
+ } else {
+ if (mNumberOfNativeCrashPollsRemaining > 0) {
+ mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
+ NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
+ }
+ }
+ }
+
+ /**
+ * Since this method can eventually trigger a rollback, it should be called
+ * only once boot has completed {@code onBootCompleted} and not earlier, because the install
+ * session must be entirely completed before we try to rollback.
+ */
+ public void scheduleCheckAndMitigateNativeCrashes() {
+ Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
+ + "and mitigate native crashes");
+ mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
+ }
+
/** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
@Retention(SOURCE)
@IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
@@ -422,17 +495,28 @@
/**
* Called when health check fails for the {@code versionedPackage}.
*
+ * @param versionedPackage the package that is failing. This may be null if a native
+ * service is crashing.
+ * @param failureReason the type of failure that is occurring.
+ *
+ *
* @return any one of {@link PackageHealthObserverImpact} to express the impact
* to the user on {@link #execute}
*/
- @PackageHealthObserverImpact int onHealthCheckFailed(VersionedPackage versionedPackage);
+ @PackageHealthObserverImpact int onHealthCheckFailed(
+ @Nullable VersionedPackage versionedPackage,
+ @FailureReasons int failureReason);
/**
* Executes mitigation for {@link #onHealthCheckFailed}.
*
+ * @param versionedPackage the package that is failing. This may be null if a native
+ * service is crashing.
+ * @param failureReason the type of failure that is occurring.
* @return {@code true} if action was executed successfully, {@code false} otherwise
*/
- boolean execute(VersionedPackage versionedPackage, @FailureReasons int failureReason);
+ boolean execute(@Nullable VersionedPackage versionedPackage,
+ @FailureReasons int failureReason);
// TODO(b/120598832): Ensure uniqueness?
/**
@@ -448,6 +532,17 @@
default boolean isPersistent() {
return false;
}
+
+ /**
+ * Returns {@code true} if this observer wishes to observe the given package, {@code false}
+ * otherwise
+ *
+ * <p> A persistent observer may choose to start observing certain failing packages, even if
+ * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+ */
+ default boolean mayObservePackage(String packageName) {
+ return false;
+ }
}
long getTriggerFailureCount() {
@@ -784,13 +879,8 @@
Slog.wtf(TAG, "NetworkStack failed but could not find its package");
return;
}
- // This is a severe failure and recovery should be attempted immediately.
- // TODO: have a better way to handle such failures.
final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
- final long failureCount = getTriggerFailureCount();
- for (int i = 0; i < failureCount; i++) {
- onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
- }
+ onPackageFailure(pkgList, FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
});
}
@@ -937,6 +1027,11 @@
*/
@GuardedBy("mLock")
public boolean onPackageFailureLocked(String packageName) {
+ if (packages.get(packageName) == null && registeredObserver.isPersistent()
+ && registeredObserver.mayObservePackage(packageName)) {
+ packages.put(packageName, sPackageWatchdog.newMonitoredPackage(
+ packageName, DEFAULT_OBSERVING_DURATION_MS, false));
+ }
MonitoredPackage p = packages.get(packageName);
if (p != null) {
return p.onFailureLocked();
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 822fc90..22fa8ff4 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
@@ -49,6 +50,7 @@
import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.KeyguardManager;
@@ -116,6 +118,7 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DataUnit;
import android.util.FeatureFlagUtils;
@@ -141,6 +144,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.pm.Installer;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
import com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
@@ -253,6 +257,11 @@
public void onCleanupUser(int userHandle) {
mStorageManagerService.onCleanupUser(userHandle);
}
+
+ @Override
+ public void onStopUser(int userHandle) {
+ mStorageManagerService.onStopUser(userHandle);
+ }
}
private static final boolean DEBUG_EVENTS = false;
@@ -359,6 +368,8 @@
private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
+ private final Installer mInstaller;
+
/** Holding lock for AppFuse business */
private final Object mAppFuseLock = new Object();
@@ -1075,6 +1086,15 @@
}
}
+ private void onStopUser(int userId) {
+ Slog.i(TAG, "onStopUser " + userId);
+ try {
+ mStorageSessionController.onUserStopping(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+
private boolean supportsBlockCheckpoint() throws RemoteException {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
return mVold.supportsBlockCheckpoint();
@@ -1228,6 +1248,13 @@
vol.state = newState;
onVolumeStateChangedLocked(vol, oldState, newState);
}
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) {
+ mInstaller.onPrivateVolumeMounted(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume mounted " + vol, e);
+ }
}
}
@@ -1273,6 +1300,13 @@
if (vol != null) {
mStorageSessionController.onVolumeRemove(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume unmounted " + vol, e);
+ }
}
}
};
@@ -1309,6 +1343,15 @@
Slog.d(TAG, "System booted in core-only mode; ignoring volume " + vol.getId());
return;
}
+ final ActivityManagerInternal amInternal =
+ LocalServices.getService(ActivityManagerInternal.class);
+
+ if (mIsFuseEnabled && vol.mountUserId >= 0
+ && !amInternal.isUserRunning(vol.mountUserId, 0)) {
+ Slog.d(TAG, "Ignoring volume " + vol.getId() + " because user "
+ + Integer.toString(vol.mountUserId) + " is no longer running.");
+ return;
+ }
if (vol.type == VolumeInfo.TYPE_EMULATED) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
@@ -1559,10 +1602,8 @@
// Snapshot feature flag used for this boot
SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE_SNAPSHOT, Boolean.toString(
SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, true)));
- SystemProperties.set(StorageManager.PROP_FUSE_SNAPSHOT, Boolean.toString(
- SystemProperties.getBoolean(StorageManager.PROP_FUSE, false)));
- mIsFuseEnabled = SystemProperties.getBoolean(StorageManager.PROP_FUSE_SNAPSHOT, false);
+ mIsFuseEnabled = SystemProperties.getBoolean(StorageManager.PROP_FUSE, false);
mContext = context;
mResolver = mContext.getContentResolver();
mCallbacks = new Callbacks(FgThread.get().getLooper());
@@ -1577,6 +1618,9 @@
mStorageSessionController = new StorageSessionController(mContext, mIsFuseEnabled);
+ mInstaller = new Installer(mContext);
+ mInstaller.onStart();
+
// Initialize the last-fstrim tracking if necessary
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
@@ -1622,12 +1666,22 @@
* and updates PROP_FUSE (reboots if changed).
*/
private void updateFusePropFromSettings() {
- Boolean settingsFuseFlag = SystemProperties.getBoolean((FeatureFlagUtils.PERSIST_PREFIX
- + FeatureFlagUtils.SETTINGS_FUSE_FLAG), false);
- Slog.d(TAG, "The value of Settings Fuse Flag is " + settingsFuseFlag);
- if (SystemProperties.getBoolean(StorageManager.PROP_FUSE, false) != settingsFuseFlag) {
+ String settingsFuseFlag = SystemProperties.get(StorageManager.PROP_SETTINGS_FUSE);
+ Slog.d(TAG, "The value of Settings Fuse Flag is "
+ + (settingsFuseFlag == null || settingsFuseFlag.isEmpty()
+ ? "null" : settingsFuseFlag));
+ // Set default value of PROP_SETTINGS_FUSE and PROP_FUSE if it
+ // is unset (neither true nor false, this happens only on the first boot
+ // after wiping data partition).
+ if (settingsFuseFlag == null || settingsFuseFlag.isEmpty()) {
+ SystemProperties.set(StorageManager.PROP_SETTINGS_FUSE, "false");
+ SystemProperties.set(StorageManager.PROP_FUSE, "false");
+ return;
+ }
+
+ if (!SystemProperties.get(StorageManager.PROP_FUSE).equals(settingsFuseFlag)) {
Slog.d(TAG, "Set persist.sys.fuse to " + settingsFuseFlag);
- SystemProperties.set(StorageManager.PROP_FUSE, Boolean.toString(settingsFuseFlag));
+ SystemProperties.set(StorageManager.PROP_FUSE, settingsFuseFlag);
// Perform hard reboot to kick policy into place
mContext.getSystemService(PowerManager.class).reboot("Reboot device for FUSE system"
+ "property change to take effect");
@@ -1940,6 +1994,13 @@
try {
mVold.unmount(vol.id);
mStorageSessionController.onVolumeUnmount(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.e(TAG, "Failed unmount mirror data", e);
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -2229,6 +2290,11 @@
}
private void remountUidExternalStorage(int uid, int mode) {
+ if (uid == Process.SYSTEM_UID) {
+ // No need to remount uid for system because it has all access anyways
+ return;
+ }
+
try {
mVold.remountUid(uid, mode);
} catch (Exception e) {
@@ -3067,14 +3133,6 @@
@Override
public void mkdirs(String callingPkg, String appPath) {
- if (mIsFuseEnabled) {
- // TODO(b/144332951): Calling into Vold is risky because the FUSE daemon can go down
- // anytime and Vold will hang forever. We should either remove this call
- // or at least call into the FUSE daemon to mkdir instead
- Slog.w(TAG, "Not making dir for package " + callingPkg + " with path " + appPath);
- return;
- }
-
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
final UserEnvironment userEnv = new UserEnvironment(userId);
@@ -3131,16 +3189,20 @@
final boolean forWrite = (flags & StorageManager.FLAG_FOR_WRITE) != 0;
final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
+ final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
// Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
// are no guarantees that callers will see a consistent view of the volume before that
// point
final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
+ final boolean userIsDemo;
final boolean userKeyUnlocked;
final boolean storagePermission;
final long token = Binder.clearCallingIdentity();
try {
+ userIsDemo = LocalServices.getService(UserManagerInternal.class)
+ .getUserInfo(userId).isDemo();
userKeyUnlocked = isUserKeyUnlocked(userId);
storagePermission = mStorageManagerInternal.hasExternalStorage(uid, packageName);
} finally {
@@ -3150,6 +3212,7 @@
boolean foundPrimary = false;
final ArrayList<StorageVolume> res = new ArrayList<>();
+ final ArraySet<String> resUuids = new ArraySet<>();
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
final VolumeInfo vol = mVolumes.valueAt(i);
@@ -3192,7 +3255,43 @@
} else {
res.add(userVol);
}
+ resUuids.add(userVol.getUuid());
}
+
+ if (includeRecent) {
+ final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
+ for (int i = 0; i < mRecords.size(); i++) {
+ final VolumeRecord rec = mRecords.valueAt(i);
+
+ // Skip if we've already included it above
+ if (resUuids.contains(rec.fsUuid)) continue;
+
+ // Treat as recent if mounted within the last week
+ if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
+ final StorageVolume userVol = rec.buildStorageVolume(mContext);
+ res.add(userVol);
+ resUuids.add(userVol.getUuid());
+ }
+ }
+ }
+ }
+
+ // Synthesize a volume for preloaded media under demo users, so that
+ // it's scanned into MediaStore
+ if (userIsDemo) {
+ final String id = "demo";
+ final File path = Environment.getDataPreloadsMediaDirectory();
+ final boolean primary = false;
+ final boolean removable = false;
+ final boolean emulated = true;
+ final boolean allowMassStorage = false;
+ final long maxFileSize = 0;
+ final UserHandle user = new UserHandle(userId);
+ final String envState = Environment.MEDIA_MOUNTED_READ_ONLY;
+ final String description = mContext.getString(android.R.string.unknownName);
+
+ res.add(new StorageVolume(id, path, path, description, primary, removable,
+ emulated, allowMassStorage, maxFileSize, user, id, envState));
}
if (!foundPrimary) {
@@ -3399,7 +3498,13 @@
public void opChanged(int op, int uid, String packageName) throws RemoteException {
if (!ENABLE_ISOLATED_STORAGE) return;
- remountUidExternalStorage(uid, getMountMode(uid, packageName));
+ int mountMode = getMountMode(uid, packageName);
+ boolean isUidActive = LocalServices.getService(ActivityManagerInternal.class)
+ .getUidProcessState(uid) != PROCESS_STATE_NONEXISTENT;
+
+ if (isUidActive) {
+ remountUidExternalStorage(uid, mountMode);
+ }
}
};
@@ -3788,7 +3893,7 @@
}
}
if ((hasInstall || hasInstallOp) && hasWrite) {
- return Zygote.MOUNT_EXTERNAL_WRITE;
+ return Zygote.MOUNT_EXTERNAL_INSTALLER;
}
// Otherwise we're willing to give out sandboxed or non-sandboxed if
@@ -4092,6 +4197,13 @@
}
}
+ @Override
+ public void resetUser(int userId) {
+ // TODO(b/145931219): ideally, we only reset storage for the user in question,
+ // but for now, reset everything.
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ }
+
public boolean hasExternalStorage(int uid, String packageName) {
// No need to check for system uid. This avoids a deadlock between
// PackageManagerService and AppOpsService.
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 5e659b6..c5409f85 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -29,6 +29,10 @@
import com.android.server.pm.UserManagerService;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* The base class for services running in the system process. Override and implement
* the lifecycle event callback methods as needed.
@@ -164,6 +168,25 @@
}
/**
+ * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+ */
+ protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
+ final List<Integer> supportedUsers = new ArrayList<>(allUsers.size());
+ for (UserInfo user : allUsers) {
+ supportedUsers.add(user.id);
+ }
+ if (allUsers.isEmpty()) {
+ pw.print(prefix); pw.println("No supported users");
+ } else {
+ final int size = supportedUsers.size();
+ pw.print(prefix); pw.print(size); pw.print(" supported user");
+ if (size > 1) pw.print("s");
+ pw.print(": "); pw.println(supportedUsers);
+ }
+ }
+
+ /**
* @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
* calls this method).
*/
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 6105c74..46ff718 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
import static android.telephony.TelephonyRegistryManager.SIM_ACTIVATION_TYPE_DATA;
import static android.telephony.TelephonyRegistryManager.SIM_ACTIVATION_TYPE_VOICE;
@@ -31,7 +32,6 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -46,6 +46,7 @@
import android.telephony.Annotation.SrvccState;
import android.telephony.CallAttributes;
import android.telephony.CallQuality;
+import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.DataFailCause;
@@ -59,6 +60,7 @@
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
@@ -97,7 +99,7 @@
* and 15973975 by saving the phoneId of the registrant and then using the
* phoneId when deciding to to make a callback. This is necessary because
* a subId changes from to a dummy value when a SIM is removed and thus won't
- * compare properly. Because SubscriptionManager.getPhoneId(int subId) handles
+ * compare properly. Because getPhoneIdFromSubId(int subId) handles
* the dummy value conversion we properly do the callbacks.
*
* Eventually we may want to remove the notion of dummy value but for now this
@@ -131,7 +133,7 @@
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+ int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
boolean matchPhoneStateListenerEvent(int events) {
return (callback != null) && ((events & this.events) != 0);
@@ -206,12 +208,10 @@
// Connection state of default APN type data (i.e. internet) of phones
private int[] mDataConnectionState;
- private Bundle[] mCellLocation;
+ private CellIdentity[] mCellIdentity;
private int[] mDataConnectionNetworkType;
- private int[] mOtaspMode;
-
private ArrayList<List<CellInfo>> mCellInfo = null;
private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
@@ -231,7 +231,7 @@
private int mDefaultSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private int mDefaultPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+ private int mDefaultPhoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
private int[] mRingingCallState;
@@ -260,7 +260,9 @@
private final LocalLog mListenLog = new LocalLog(100);
- private PreciseDataConnectionState[] mPreciseDataConnectionState;
+ // Per-phoneMap of APN Type to DataConnectionState
+ private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates =
+ new ArrayList<Map<String, 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;
@@ -294,7 +296,7 @@
int numPhones = getTelephonyManager().getPhoneCount();
for (int sub = 0; sub < numPhones; sub++) {
TelephonyRegistry.this.notifyCellLocationForSubscriber(sub,
- mCellLocation[sub]);
+ mCellIdentity[sub]);
}
break;
}
@@ -357,8 +359,8 @@
SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultSubscriptionId());
int newDefaultPhoneId = intent.getIntExtra(
- PhoneConstants.PHONE_KEY,
- SubscriptionManager.getPhoneId(newDefaultSubId));
+ SubscriptionManager.EXTRA_SLOT_INDEX,
+ getPhoneIdFromSubId(newDefaultSubId));
if (DBG) {
log("onReceive:current mDefaultSubId=" + mDefaultSubId
+ " current mDefaultPhoneId=" + mDefaultPhoneId
@@ -403,9 +405,8 @@
mSignalStrength = copyOf(mSignalStrength, mNumPhones);
mMessageWaiting = copyOf(mMessageWaiting, mNumPhones);
mCallForwarding = copyOf(mCallForwarding, mNumPhones);
- mCellLocation = copyOf(mCellLocation, mNumPhones);
+ mCellIdentity = copyOf(mCellIdentity, mNumPhones);
mSrvccState = copyOf(mSrvccState, mNumPhones);
- mOtaspMode = copyOf(mOtaspMode, mNumPhones);
mPreciseCallState = copyOf(mPreciseCallState, mNumPhones);
mForegroundCallState = copyOf(mForegroundCallState, mNumPhones);
mBackgroundCallState = copyOf(mBackgroundCallState, mNumPhones);
@@ -415,7 +416,6 @@
mCallQuality = copyOf(mCallQuality, mNumPhones);
mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
mCallAttributes = copyOf(mCallAttributes, mNumPhones);
- mPreciseDataConnectionState = copyOf(mPreciseDataConnectionState, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
@@ -423,6 +423,7 @@
if (mNumPhones < oldNumPhones) {
cutListToSize(mCellInfo, mNumPhones);
cutListToSize(mImsReasonInfo, mNumPhones);
+ cutListToSize(mPreciseDataConnectionStates, mNumPhones);
return;
}
@@ -439,31 +440,21 @@
mUserMobileDataState[i] = false;
mMessageWaiting[i] = false;
mCallForwarding[i] = false;
- mCellLocation[i] = new Bundle();
+ mCellIdentity[i] = null;
mCellInfo.add(i, null);
mImsReasonInfo.add(i, null);
mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
- mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
- mCallQuality[i] = new CallQuality();
- mCallAttributes[i] = new CallAttributes(new PreciseCallState(),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
+ mCallQuality[i] = createCallQuality();
+ mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- mPreciseCallState[i] = new PreciseCallState();
+ mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
- }
-
- // Note that location can be null for non-phone builds like
- // like the generic one.
- CellLocation location = CellLocation.getEmpty();
- if (location != null) {
- for (int i = oldNumPhones; i < mNumPhones; i++) {
- location.fillInNotifierBundle(mCellLocation[i]);
- }
+ mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
}
}
@@ -504,9 +495,8 @@
mSignalStrength = new SignalStrength[numPhones];
mMessageWaiting = new boolean[numPhones];
mCallForwarding = new boolean[numPhones];
- mCellLocation = new Bundle[numPhones];
+ mCellIdentity = new CellIdentity[numPhones];
mSrvccState = new int[numPhones];
- mOtaspMode = new int[numPhones];
mPreciseCallState = new PreciseCallState[numPhones];
mForegroundCallState = new int[numPhones];
mBackgroundCallState = new int[numPhones];
@@ -516,7 +506,7 @@
mCallQuality = new CallQuality[numPhones];
mCallNetworkType = new int[numPhones];
mCallAttributes = new CallAttributes[numPhones];
- mPreciseDataConnectionState = new PreciseDataConnectionState[numPhones];
+ mPreciseDataConnectionStates = new ArrayList<>();
mCellInfo = new ArrayList<>();
mImsReasonInfo = new ArrayList<>();
mEmergencyNumberList = new HashMap<>();
@@ -534,30 +524,21 @@
mUserMobileDataState[i] = false;
mMessageWaiting[i] = false;
mCallForwarding[i] = false;
- mCellLocation[i] = new Bundle();
+ mCellIdentity[i] = null;
mCellInfo.add(i, null);
mImsReasonInfo.add(i, null);
mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
- mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
- mCallQuality[i] = new CallQuality();
- mCallAttributes[i] = new CallAttributes(new PreciseCallState(),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
+ mCallQuality[i] = createCallQuality();
+ mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- mPreciseCallState[i] = new PreciseCallState();
+ mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
- }
-
- // Note that location can be null for non-phone builds like
- // like the generic one.
- if (location != null) {
- for (int i = 0; i < numPhones; i++) {
- location.fillInNotifierBundle(mCellLocation[i]);
- }
+ mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -768,7 +749,7 @@
return;
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -840,11 +821,10 @@
}
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
try {
- if (DBG_LOC) log("listen: mCellLocation = "
- + mCellLocation[phoneId]);
+ if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]);
if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
- r.callback.onCellLocationChanged(
- new Bundle(mCellLocation[phoneId]));
+ // null will be translated to empty CellLocation object in client.
+ r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
}
} catch (RemoteException ex) {
remove(r.binder);
@@ -880,13 +860,6 @@
remove(r.binder);
}
}
- if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
- try {
- r.callback.onOtaspChanged(mOtaspMode[phoneId]);
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
try {
if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
@@ -922,8 +895,10 @@
}
if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
try {
- r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ for (PreciseDataConnectionState pdcs
+ : mPreciseDataConnectionStates.get(phoneId).values()) {
+ r.callback.onPreciseDataConnectionStateChanged(pdcs);
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -937,7 +912,8 @@
}
if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) !=0) {
try {
- r.callback.onVoiceActivationStateChanged(mVoiceActivationState[phoneId]);
+ r.callback.onVoiceActivationStateChanged(
+ mVoiceActivationState[phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1099,7 +1075,7 @@
// Called only by Telecomm to communicate call state across different phone accounts. So
// there is no need to add a valid subId or slotId.
broadcastCallStateChanged(state, phoneNumber,
- SubscriptionManager.INVALID_PHONE_INDEX,
+ SubscriptionManager.INVALID_SIM_SLOT_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
@@ -1313,7 +1289,8 @@
// only CarrierService with carrier privilege rule should have the permission
int[] subIds = Arrays.stream(SubscriptionManager.from(mContext)
.getActiveSubscriptionIdList(false))
- .filter(i -> TelephonyPermissions.checkCarrierPrivilegeForSubId(i)).toArray();
+ .filter(i -> TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext,
+ i)).toArray();
if (ArrayUtils.isEmpty(subIds)) {
loge("notifyCarrierNetworkChange without carrier privilege");
// the active subId does not have carrier privilege.
@@ -1323,7 +1300,7 @@
synchronized (mRecords) {
mCarrierNetworkChangeState = active;
for (int subId : subIds) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
if (VDBG) {
log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
@@ -1356,7 +1333,7 @@
log("notifyCellInfoForSubscriber: subId=" + subId
+ " cellInfo=" + cellInfo);
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mCellInfo.set(phoneId, cellInfo);
@@ -1447,7 +1424,7 @@
log("notifyCallForwardingChangedForSubscriber: subId=" + subId
+ " cfi=" + cfi);
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mCallForwarding[phoneId] = cfi;
@@ -1475,7 +1452,7 @@
if (!checkNotifyPermission("notifyDataActivity()" )) {
return;
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mDataActivity[phoneId] = state;
@@ -1495,30 +1472,38 @@
}
}
- public void notifyDataConnection(int state, boolean isDataAllowed, String apn, String apnType,
- LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities, int networkType,
- boolean roaming) {
- notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_PHONE_INDEX,
- SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state,
- isDataAllowed, apn, apnType, linkProperties,
- networkCapabilities, networkType, roaming);
- }
-
- public void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
- boolean isDataAllowed,
- String apn, String apnType,
- LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int networkType, boolean roaming) {
+ /**
+ * Send a notification to registrants that the data connection state has changed.
+ *
+ * @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 preciseState a PreciseDataConnectionState that has info about the data connection
+ */
+ public void notifyDataConnectionForSubscriber(
+ int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
+
+ String apn = "";
+ int state = TelephonyManager.DATA_UNKNOWN;
+ int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ LinkProperties linkProps = null;
+
+ if (preciseState != null) {
+ apn = preciseState.getDataConnectionApn();
+ state = preciseState.getState();
+ networkType = preciseState.getNetworkType();
+ linkProps = preciseState.getDataConnectionLinkProperties();
+ }
if (VDBG) {
log("notifyDataConnectionForSubscriber: subId=" + subId
- + " state=" + state + " isDataAllowed=" + isDataAllowed
- + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
- + " mRecords.size()=" + mRecords.size());
+ + " state=" + state + "' apn='" + apn
+ + "' apnType=" + apnType + " networkType=" + networkType
+ + "' preciseState=" + preciseState);
}
+
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
// We only call the callback when the change is for default APN type.
@@ -1550,38 +1535,48 @@
mDataConnectionState[phoneId] = state;
mDataConnectionNetworkType[phoneId] = networkType;
}
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- state, networkType,
- ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
- linkProperties, DataFailCause.NONE);
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
- && idMatch(r.subId, subId, phoneId)) {
- try {
- r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+
+ boolean needsNotify = false;
+ // State has been cleared for this APN Type
+ if (preciseState == null) {
+ // We try clear the state and check if the state was previously not cleared
+ needsNotify = mPreciseDataConnectionStates.get(phoneId).remove(apnType) != null;
+ } else {
+ // We need to check to see if the state actually changed
+ PreciseDataConnectionState oldPreciseState =
+ mPreciseDataConnectionStates.get(phoneId).put(apnType, preciseState);
+ needsNotify = !preciseState.equals(oldPreciseState);
+ }
+
+ if (needsNotify) {
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onPreciseDataConnectionStateChanged(preciseState);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
}
handleRemoveListLocked();
}
- broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties,
- networkCapabilities, roaming, subId);
- broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn,
- linkProperties, DataFailCause.NONE);
+
+ broadcastDataConnectionStateChanged(state, apn, apnType, subId);
}
+ /**
+ * Stub to satisfy the ITelephonyRegistry aidl interface; do not use this function.
+ * @see #notifyDataConnectionFailedForSubscriber
+ */
public void notifyDataConnectionFailed(String apnType) {
- notifyDataConnectionFailedForSubscriber(SubscriptionManager.DEFAULT_PHONE_INDEX,
- SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
- apnType);
+ loge("This function should not be invoked");
}
- public void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
+ private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
return;
}
@@ -1591,17 +1586,20 @@
}
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
- DataFailCause.NONE);
+ mPreciseDataConnectionStates.get(phoneId).put(
+ apnType,
+ new PreciseDataConnectionState(
+ TelephonyManager.DATA_UNKNOWN,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ DataFailCause.NONE));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ mPreciseDataConnectionStates.get(phoneId).get(apnType));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1612,16 +1610,15 @@
handleRemoveListLocked();
}
broadcastDataConnectionFailed(apnType, subId);
- broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN,
- TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, null, null,
- DataFailCause.NONE);
}
- public void notifyCellLocation(Bundle cellLocation) {
- notifyCellLocationForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellLocation);
+ @Override
+ public void notifyCellLocation(CellIdentity cellLocation) {
+ notifyCellLocationForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellLocation);
}
- public void notifyCellLocationForSubscriber(int subId, Bundle cellLocation) {
+ @Override
+ public void notifyCellLocationForSubscriber(int subId, CellIdentity cellLocation) {
log("notifyCellLocationForSubscriber: subId=" + subId
+ " cellLocation=" + cellLocation);
if (!checkNotifyPermission("notifyCellLocation()")) {
@@ -1631,10 +1628,10 @@
log("notifyCellLocationForSubscriber: subId=" + subId
+ " cellLocation=" + cellLocation);
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- mCellLocation[phoneId] = cellLocation;
+ mCellIdentity[phoneId] = cellLocation;
for (Record r : mRecords) {
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) &&
idMatch(r.subId, subId, phoneId) &&
@@ -1644,30 +1641,7 @@
log("notifyCellLocation: cellLocation=" + cellLocation
+ " r=" + r);
}
- r.callback.onCellLocationChanged(new Bundle(cellLocation));
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
- }
- }
- }
- }
- handleRemoveListLocked();
- }
- }
-
- public void notifyOtaspChanged(int subId, int otaspMode) {
- if (!checkNotifyPermission("notifyOtaspChanged()" )) {
- return;
- }
- int phoneId = SubscriptionManager.getPhoneId(subId);
- synchronized (mRecords) {
- if (validatePhoneId(phoneId)) {
- mOtaspMode[phoneId] = otaspMode;
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_OTASP_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
- try {
- r.callback.onOtaspChanged(otaspMode);
+ r.callback.onCellLocationChanged(cellLocation);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1704,7 +1678,7 @@
if (mPreciseCallState[phoneId].getForegroundCallState()
!= PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- mCallQuality[phoneId] = new CallQuality();
+ mCallQuality[phoneId] = createCallQuality();
}
mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
mCallNetworkType[phoneId], mCallQuality[phoneId]);
@@ -1732,8 +1706,6 @@
}
handleRemoveListLocked();
}
- broadcastPreciseCallStateChanged(ringingCallState, foregroundCallState,
- backgroundCallState);
}
public void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
@@ -1765,7 +1737,7 @@
if (!checkNotifyPermission("notifyImsCallDisconnectCause()")) {
return;
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mImsReasonInfo.set(phoneId, imsReasonInfo);
@@ -1794,29 +1766,34 @@
if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
return;
}
+
+ // precise notify invokes imprecise notify
+ notifyDataConnectionFailedForSubscriber(phoneId, subId, apnType);
+
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
- TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
+ mPreciseDataConnectionStates.get(phoneId).put(
+ apnType,
+ new PreciseDataConnectionState(
+ TelephonyManager.DATA_UNKNOWN,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ failCause));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
&& idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState[phoneId]);
+ mPreciseDataConnectionStates.get(phoneId).get(apnType));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
}
}
}
-
handleRemoveListLocked();
}
- broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN,
- TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, apn, null, failCause);
}
@Override
@@ -1827,7 +1804,7 @@
if (VDBG) {
log("notifySrvccStateChanged: subId=" + subId + " srvccState=" + state);
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
+ int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mSrvccState[phoneId] = state;
@@ -2072,7 +2049,6 @@
}
}
-
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2102,16 +2078,15 @@
pw.println("mCallForwarding=" + mCallForwarding[i]);
pw.println("mDataActivity=" + mDataActivity[i]);
pw.println("mDataConnectionState=" + mDataConnectionState[i]);
- pw.println("mCellLocation=" + mCellLocation[i]);
+ pw.println("mCellIdentity=" + mCellIdentity[i]);
pw.println("mCellInfo=" + mCellInfo.get(i));
pw.println("mImsCallDisconnectCause=" + mImsReasonInfo.get(i));
pw.println("mSrvccState=" + mSrvccState[i]);
- pw.println("mOtaspMode=" + mOtaspMode[i]);
pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
pw.println("mCallQuality=" + mCallQuality[i]);
pw.println("mCallAttributes=" + mCallAttributes[i]);
pw.println("mCallNetworkType=" + mCallNetworkType[i]);
- pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState[i]);
+ pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
pw.decreaseIndent();
@@ -2146,6 +2121,16 @@
// the legacy intent broadcasting
//
+ // Legacy intent action.
+ /** Fired when a subscription's phone state changes. */
+ private static final String ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED =
+ "android.intent.action.SUBSCRIPTION_PHONE_STATE";
+
+ // Legacy intent extra keys, copied from PhoneConstants.
+ // Used in legacy intents sent here, for backward compatibility.
+ private static final String PHONE_CONSTANTS_SLOT_KEY = "slot";
+ private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
+
private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
long ident = Binder.clearCallingIdentity();
try {
@@ -2162,9 +2147,10 @@
state.fillInNotifierBundle(data);
intent.putExtras(data);
// Pass the subscription along with the intent.
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+ intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
- intent.putExtra(PhoneConstants.SLOT_KEY, phoneId);
+ intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId);
+ intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -2183,8 +2169,8 @@
Bundle data = new Bundle();
signalStrength.fillInNotifierBundle(data);
intent.putExtras(data);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
- intent.putExtra(PhoneConstants.SLOT_KEY, phoneId);
+ intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
+ intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -2219,13 +2205,14 @@
// If a valid subId was specified, we should fire off a subId-specific state
// change intent and include the subId.
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
- intent.setAction(PhoneConstants.ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+ intent.setAction(ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED);
+ intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
}
// If the phoneId is invalid, the broadcast is for overall call state.
- if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
- intent.putExtra(PhoneConstants.SLOT_KEY, phoneId);
+ if (phoneId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId);
+ intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, phoneId);
}
// Wakeup apps for the (SUBSCRIPTION_)PHONE_STATE broadcast.
@@ -2259,76 +2246,33 @@
}
}
- private void broadcastDataConnectionStateChanged(int state, boolean isDataAllowed, String apn,
- String apnType, LinkProperties linkProperties,
- NetworkCapabilities networkCapabilities,
- boolean roaming, int subId) {
+ private void broadcastDataConnectionStateChanged(int state, String apn,
+ String 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));
- if (!isDataAllowed) {
- intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
- }
- if (linkProperties != null) {
- intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
- String iface = linkProperties.getInterfaceName();
- if (iface != null) {
- intent.putExtra(PhoneConstants.DATA_IFACE_NAME_KEY, iface);
- }
- }
- if (networkCapabilities != null) {
- intent.putExtra(PhoneConstants.DATA_NETWORK_CAPABILITIES_KEY, networkCapabilities);
- }
- if (roaming) intent.putExtra(PhoneConstants.DATA_NETWORK_ROAMING_KEY, true);
intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+ intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void broadcastDataConnectionFailed(String apnType, int subId) {
Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+ intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
- private void broadcastPreciseCallStateChanged(int ringingCallState, int foregroundCallState,
- int backgroundCallState) {
- Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_CALL_STATE_CHANGED);
- intent.putExtra(TelephonyManager.EXTRA_RINGING_CALL_STATE, ringingCallState);
- intent.putExtra(TelephonyManager.EXTRA_FOREGROUND_CALL_STATE, foregroundCallState);
- intent.putExtra(TelephonyManager.EXTRA_BACKGROUND_CALL_STATE, backgroundCallState);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- android.Manifest.permission.READ_PRECISE_PHONE_STATE);
- }
-
- private void broadcastPreciseDataConnectionStateChanged(int state, int networkType,
- String apnType, String apn, LinkProperties linkProperties,
- @DataFailureCause int failCause) {
- Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED);
- intent.putExtra(TelephonyManager.EXTRA_STATE, state);
- intent.putExtra(PhoneConstants.DATA_NETWORK_TYPE_KEY, networkType);
- if (apnType != null) intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
- if (apn != null) intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
- if (linkProperties != null) {
- intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
- }
- intent.putExtra(PhoneConstants.DATA_FAILURE_CAUSE_KEY, failCause);
-
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- android.Manifest.permission.READ_PRECISE_PHONE_STATE);
- }
-
private void enforceNotifyPermissionOrCarrierPrivilege(String method) {
if (checkNotifyPermission()) {
return;
}
- TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(
+ TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mContext,
SubscriptionManager.getDefaultSubscriptionId(), method);
}
@@ -2624,10 +2568,13 @@
if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
try {
- if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = "
- + mCellLocation[phoneId]);
+ if (DBG_LOC) {
+ log("checkPossibleMissNotify: onCellLocationChanged mCellIdentity = "
+ + mCellIdentity[phoneId]);
+ }
if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
- r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId]));
+ // null will be translated to empty CellLocation object in client.
+ r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
}
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
@@ -2719,4 +2666,31 @@
}
}
+ /** Returns a new PreciseCallState object with default values. */
+ private static PreciseCallState createPreciseCallState() {
+ return new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_NOT_VALID,
+ PreciseCallState.PRECISE_CALL_STATE_NOT_VALID,
+ PreciseCallState.PRECISE_CALL_STATE_NOT_VALID,
+ DisconnectCause.NOT_VALID,
+ PreciseDisconnectCause.NOT_VALID);
+ }
+
+ /** Returns a new CallQuality object with default values. */
+ private static CallQuality createCallQuality() {
+ return new CallQuality(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ }
+
+ private int getPhoneIdFromSubId(int subId) {
+ SubscriptionManager subManager = (SubscriptionManager)
+ mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ if (subManager == null) return INVALID_SIM_SLOT_INDEX;
+
+ if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ subId = SubscriptionManager.getDefaultSubscriptionId();
+ }
+
+ SubscriptionInfo info = subManager.getActiveSubscriptionInfo(subId);
+ if (info == null) return INVALID_SIM_SLOT_INDEX;
+ return info.getSimSlotIndex();
+ }
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index b1e2c0f..2091c2a 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -56,6 +56,7 @@
import android.service.vr.IVrStateCallbacks;
import android.util.ArraySet;
import android.util.Slog;
+
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.DisableCarModeActivity;
@@ -73,8 +74,6 @@
import java.util.Map;
import java.util.Set;
-import static android.content.Intent.ACTION_SCREEN_OFF;
-
final class UiModeManagerService extends SystemService {
private static final String TAG = UiModeManager.class.getSimpleName();
private static final boolean LOG = false;
@@ -97,10 +96,15 @@
private boolean mCarModeEnabled = false;
private boolean mCharging = false;
private boolean mPowerSave = false;
+ // Do not change configuration now. wait until screen turns off.
+ // This prevents jank and activity restart when the user
+ // is actively using the device
+ private boolean mWaitForScreenOff = false;
private int mDefaultUiModeType;
private boolean mCarModeKeepsScreenOn;
private boolean mDeskModeKeepsScreenOn;
private boolean mTelevision;
+ private boolean mCar;
private boolean mWatch;
private boolean mVrHeadset;
private boolean mComputedNightMode;
@@ -208,24 +212,27 @@
public void onTwilightStateChanged(@Nullable TwilightState state) {
synchronized (mLock) {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
- final IntentFilter intentFilter =
- new IntentFilter(ACTION_SCREEN_OFF);
- getContext().registerReceiver(mOnScreenOffHandler, intentFilter);
+ if (mCar) {
+ updateLocked(0, 0);
+ } else {
+ registerScreenOffEvent();
+ }
}
}
}
};
+ /**
+ * DO NOT USE DIRECTLY
+ * see register registerScreenOffEvent and unregisterScreenOffEvent
+ */
private final BroadcastReceiver mOnScreenOffHandler = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
+ // must unregister first before updating
+ unregisterScreenOffEvent();
updateLocked(0, 0);
- try {
- getContext().unregisterReceiver(mOnScreenOffHandler);
- } catch (IllegalArgumentException e) {
- // we ignore this exception if the receiver is unregistered already.
- }
}
}
};
@@ -327,6 +334,7 @@
final PackageManager pm = context.getPackageManager();
mTelevision = pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
|| pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ mCar = pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
mWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
updateNightModeFromSettings(context, res, UserHandle.getCallingUserId());
@@ -335,7 +343,7 @@
SystemServerInitThreadPool.submit(() -> {
synchronized (mLock) {
updateConfigurationLocked();
- sendConfigurationLocked();
+ applyConfigurationExternallyLocked();
}
}, TAG + ".onStart");
@@ -404,6 +412,22 @@
return oldNightMode != mNightMode;
}
+ private void registerScreenOffEvent() {
+ mWaitForScreenOff = true;
+ final IntentFilter intentFilter =
+ new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ getContext().registerReceiver(mOnScreenOffHandler, intentFilter);
+ }
+
+ private void unregisterScreenOffEvent() {
+ mWaitForScreenOff = false;
+ try {
+ getContext().unregisterReceiver(mOnScreenOffHandler);
+ } catch (IllegalArgumentException e) {
+ // we ignore this exception if the receiver is unregistered already.
+ }
+ }
+
private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
@Override
public void enableCarMode(@UiModeManager.EnableCarMode int flags,
@@ -525,28 +549,20 @@
synchronized (mLock) {
if (mNightMode != mode) {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
- try {
- getContext().unregisterReceiver(mOnScreenOffHandler);
- } catch (IllegalArgumentException e) {
- // we ignore this exception if the receiver is unregistered already.
- }
- }
- // Only persist setting if not in car mode
- if (!mCarModeEnabled) {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.UI_NIGHT_MODE, mode, user);
- Secure.putIntForUser(getContext().getContentResolver(),
- OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
+ unregisterScreenOffEvent();
}
mNightMode = mode;
mNightModeOverride = mode;
- //on screen off will update configuration instead
- if (mNightMode != UiModeManager.MODE_NIGHT_AUTO) {
+ // Only persist setting if not in car mode
+ if (!mCarModeEnabled) {
+ persistNightMode(user);
+ }
+ // on screen off will update configuration instead
+ if (mNightMode != UiModeManager.MODE_NIGHT_AUTO || mCar) {
updateLocked(0, 0);
} else {
- getContext().registerReceiver(
- mOnScreenOffHandler, new IntentFilter(ACTION_SCREEN_OFF));
+ registerScreenOffEvent();
}
}
}
@@ -591,13 +607,11 @@
@Override
public boolean setNightModeActivated(boolean active) {
synchronized (mLock) {
+ final int user = UserHandle.getCallingUserId();
final long ident = Binder.clearCallingIdentity();
try {
if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
- try {
- getContext().unregisterReceiver(mOnScreenOffHandler);
- } catch (IllegalArgumentException e) {
- }
+ unregisterScreenOffEvent();
mNightModeOverride = active
? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO;
} else if (mNightMode == UiModeManager.MODE_NIGHT_NO
@@ -608,7 +622,8 @@
mNightMode = UiModeManager.MODE_NIGHT_NO;
}
updateConfigurationLocked();
- sendConfigurationLocked();
+ applyConfigurationExternallyLocked();
+ persistNightMode(user);
return true;
} finally {
Binder.restoreCallingIdentity(ident);
@@ -818,6 +833,13 @@
}
}
+ private void persistNightMode(int user) {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.UI_NIGHT_MODE, mNightMode, user);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
+ }
+
private void updateConfigurationLocked() {
int uiMode = mDefaultUiModeType;
if (mUiModeLocked) {
@@ -863,12 +885,12 @@
}
mCurUiMode = uiMode;
- if (!mHoldingConfiguration) {
+ if (!mHoldingConfiguration || !mWaitForScreenOff) {
mConfiguration.uiMode = uiMode;
}
}
- private void sendConfigurationLocked() {
+ private void applyConfigurationExternallyLocked() {
if (mSetUiMode != mConfiguration.uiMode) {
mSetUiMode = mConfiguration.uiMode;
// load splash screen instead of screenshot
@@ -1052,7 +1074,7 @@
}
// Send the new configuration.
- sendConfigurationLocked();
+ applyConfigurationExternallyLocked();
// If we did not start a dock app, then start dreaming if supported.
if (category != null && !dockAppStarted) {
@@ -1130,7 +1152,6 @@
final int user = UserHandle.getCallingUserId();
Secure.putIntForUser(getContext().getContentResolver(),
OVERRIDE_NIGHT_MODE, mNightModeOverride, user);
-
}
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 3330882..76a8f92 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
@@ -60,6 +61,7 @@
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;
@@ -79,7 +81,6 @@
implements InputManager.InputDeviceListener {
private static final String TAG = "VibratorService";
private static final boolean DEBUG = false;
- private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled";
@@ -139,6 +140,7 @@
private final PowerManager.WakeLock mWakeLock;
private final AppOpsManager mAppOps;
private final IBatteryStats mBatteryStatsService;
+ private final String mSystemUiPackage;
private PowerManagerInternal mPowerManagerInternal;
private InputManager mIm;
private Vibrator mVibrator;
@@ -161,6 +163,8 @@
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
+ private SparseArray<Pair<VibrationEffect, AudioAttributes>> mAlwaysOnEffects =
+ new SparseArray<>();
static native boolean vibratorExists();
static native void vibratorInit();
@@ -172,6 +176,8 @@
static native boolean vibratorSupportsExternalControl();
static native void vibratorSetExternalControl(boolean enabled);
static native long vibratorGetCapabilities();
+ static native void vibratorAlwaysOnEnable(long id, long effect, long strength);
+ static native void vibratorAlwaysOnDisable(long id);
private final IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
@@ -284,7 +290,7 @@
}
public boolean isFromSystem() {
- return uid == Process.SYSTEM_UID || uid == 0 || SYSTEM_UI_PACKAGE.equals(opPkg);
+ return uid == Process.SYSTEM_UID || uid == 0 || mSystemUiPackage.equals(opPkg);
}
public VibrationInfo toInfo() {
@@ -372,6 +378,8 @@
mAppOps = mContext.getSystemService(AppOpsManager.class);
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
+ mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
+ .getSystemUiServiceComponent().getPackageName();
mPreviousVibrationsLimit = mContext.getResources().getInteger(
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
@@ -519,6 +527,41 @@
}
}
+ @Override // Binder call
+ public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attrs) {
+ if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
+ throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
+ }
+ if ((mCapabilities & IVibrator.CAP_ALWAYS_ON_CONTROL) == 0) {
+ Slog.e(TAG, "Always-on effects not supported.");
+ return false;
+ }
+ if (effect == null) {
+ synchronized (mLock) {
+ mAlwaysOnEffects.delete(id);
+ vibratorAlwaysOnDisable(id);
+ }
+ } else {
+ if (!verifyVibrationEffect(effect)) {
+ return false;
+ }
+ if (!(effect instanceof VibrationEffect.Prebaked)) {
+ Slog.e(TAG, "Only prebaked effects supported for always-on.");
+ return false;
+ }
+ if (attrs == null) {
+ attrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_UNKNOWN)
+ .build();
+ }
+ synchronized (mLock) {
+ mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
+ updateAlwaysOnLocked(id, effect, attrs);
+ }
+ }
+ return true;
+ }
+
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -989,6 +1032,8 @@
// If the state changes out from under us then just reset.
doCancelVibrateLocked();
}
+
+ updateAlwaysOnLocked();
}
}
@@ -1055,6 +1100,27 @@
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);
+ final int intensity = getCurrentIntensityLocked(vib);
+ if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ vibratorAlwaysOnDisable(id);
+ } else {
+ final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ final int strength = intensityToEffectStrength(intensity);
+ vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
+ }
+ }
+
+ 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);
+ }
+ }
+
@Override
public void onInputDeviceAdded(int deviceId) {
updateVibrators();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index b5cab1f..5996b7d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1289,6 +1289,33 @@
}
protected UserAccounts getUserAccounts(int userId) {
+ try {
+ return getUserAccountsNotChecked(userId);
+ } catch (RuntimeException e) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ // Let it go...
+ throw e;
+ }
+ // User accounts database is corrupted, we must wipe out the whole user, otherwise the
+ // system will crash indefinitely
+ Slog.wtf(TAG, "Removing user " + userId + " due to exception (" + e + ") reading its "
+ + "account database");
+ if (userId == ActivityManager.getCurrentUser() && userId != UserHandle.USER_SYSTEM) {
+ Slog.i(TAG, "Switching to system user first");
+ try {
+ ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Could not switch to " + UserHandle.USER_SYSTEM + ": " + re);
+ }
+ }
+ if (!getUserManager().removeUserEvenWhenDisallowed(userId)) {
+ Slog.e(TAG, "could not remove user " + userId);
+ }
+ throw e;
+ }
+ }
+
+ private UserAccounts getUserAccountsNotChecked(int userId) {
synchronized (mUsers) {
UserAccounts accounts = mUsers.get(userId);
boolean validateAccounts = false;
@@ -4382,7 +4409,6 @@
return true;
}
- @Override
public boolean renameSharedAccountAsUser(Account account, String newName, int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
@@ -4398,7 +4424,6 @@
return r > 0;
}
- @Override
public boolean removeSharedAccountAsUser(Account account, int userId) {
return removeSharedAccountAsUser(account, userId, getCallingUid());
}
@@ -4416,7 +4441,6 @@
return deleted;
}
- @Override
public Account[] getSharedAccountsAsUser(int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 01f3c26..d7a46fe 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -158,7 +158,6 @@
import android.app.ApplicationThreadConstants;
import android.app.BroadcastOptions;
import android.app.ContentProviderHolder;
-import android.app.Dialog;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.app.IApplicationThread;
@@ -490,7 +489,7 @@
static final String[] EMPTY_STRING_ARRAY = new String[0];
// How many bytes to write into the dropbox log before truncating
- static final int DROPBOX_MAX_SIZE = 192 * 1024;
+ static final int DROPBOX_DEFAULT_MAX_SIZE = 192 * 1024;
// Assumes logcat entries average around 100 bytes; that's not perfect stack traces count
// as one line, but close enough for now.
static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100;
@@ -957,10 +956,12 @@
new DeviceConfig.OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(Properties properties) {
- mPssDeferralTime = properties.getLong(ACTIVITY_START_PSS_DEFER_CONFIG, 0);
- if (DEBUG_PSS) {
- Slog.d(TAG_PSS, "Activity-start PSS delay now "
- + mPssDeferralTime + " ms");
+ if (properties.getKeyset().contains(ACTIVITY_START_PSS_DEFER_CONFIG)) {
+ mPssDeferralTime = properties.getLong(ACTIVITY_START_PSS_DEFER_CONFIG, 0);
+ if (DEBUG_PSS) {
+ Slog.d(TAG_PSS, "Activity-start PSS delay now "
+ + mPssDeferralTime + " ms");
+ }
}
}
};
@@ -1619,82 +1620,72 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case SHOW_ERROR_UI_MSG: {
- mAppErrors.handleShowAppErrorUi(msg);
- ensureBootCompleted();
- } break;
- case SHOW_NOT_RESPONDING_UI_MSG: {
- mAppErrors.handleShowAnrUi(msg);
- ensureBootCompleted();
- } break;
- case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
- HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
- synchronized (ActivityManagerService.this) {
- ProcessRecord proc = (ProcessRecord) data.get("app");
- if (proc == null) {
- Slog.e(TAG, "App not found when showing strict mode dialog.");
- break;
- }
- if (proc.crashDialog != null) {
- Slog.e(TAG, "App already has strict mode dialog: " + proc);
- return;
- }
- AppErrorResult res = (AppErrorResult) data.get("result");
- if (mAtmInternal.showStrictModeViolationDialog()) {
- Dialog d = new StrictModeViolationDialog(mUiContext,
- ActivityManagerService.this, res, proc);
- d.show();
- proc.crashDialog = d;
- } else {
- // The device is asleep, so just pretend that the user
- // saw a crash dialog and hit "force quit".
- res.set(0);
- }
- }
- ensureBootCompleted();
- } break;
- case WAIT_FOR_DEBUGGER_UI_MSG: {
- synchronized (ActivityManagerService.this) {
- ProcessRecord app = (ProcessRecord)msg.obj;
- if (msg.arg1 != 0) {
- if (!app.waitedForDebugger) {
- Dialog d = new AppWaitingForDebuggerDialog(
- ActivityManagerService.this,
- mUiContext, app);
- app.waitDialog = d;
- app.waitedForDebugger = true;
- d.show();
+ case SHOW_ERROR_UI_MSG: {
+ mAppErrors.handleShowAppErrorUi(msg);
+ ensureBootCompleted();
+ } break;
+ case SHOW_NOT_RESPONDING_UI_MSG: {
+ mAppErrors.handleShowAnrUi(msg);
+ ensureBootCompleted();
+ } break;
+ case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
+ HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
+ synchronized (ActivityManagerService.this) {
+ ProcessRecord proc = (ProcessRecord) data.get("app");
+ if (proc == null) {
+ Slog.e(TAG, "App not found when showing strict mode dialog.");
+ break;
}
- } else {
- if (app.waitDialog != null) {
- app.waitDialog.dismiss();
- app.waitDialog = null;
+ if (proc.getDialogController().hasViolationDialogs()) {
+ Slog.e(TAG, "App already has strict mode dialog: " + proc);
+ return;
+ }
+ AppErrorResult res = (AppErrorResult) data.get("result");
+ if (mAtmInternal.showStrictModeViolationDialog()) {
+ proc.getDialogController().showViolationDialogs(res);
+ } else {
+ // The device is asleep, so just pretend that the user
+ // saw a crash dialog and hit "force quit".
+ res.set(0);
}
}
+ ensureBootCompleted();
+ } break;
+ case WAIT_FOR_DEBUGGER_UI_MSG: {
+ synchronized (ActivityManagerService.this) {
+ ProcessRecord app = (ProcessRecord) msg.obj;
+ if (msg.arg1 != 0) {
+ if (!app.waitedForDebugger) {
+ app.getDialogController().showDebugWaitingDialogs();
+ app.waitedForDebugger = true;
+ }
+ } else {
+ app.getDialogController().clearWaitingDialog();
+ }
+ }
+ } break;
+ case DISPATCH_PROCESSES_CHANGED_UI_MSG: {
+ dispatchProcessesChanged();
+ break;
}
- } break;
- case DISPATCH_PROCESSES_CHANGED_UI_MSG: {
- dispatchProcessesChanged();
- break;
- }
- case DISPATCH_PROCESS_DIED_UI_MSG: {
- final int pid = msg.arg1;
- final int uid = msg.arg2;
- dispatchProcessDied(pid, uid);
- break;
- }
- case DISPATCH_UIDS_CHANGED_UI_MSG: {
- if (false) { // DO NOT SUBMIT WITH TRUE
- maybeTriggerWatchdog();
+ case DISPATCH_PROCESS_DIED_UI_MSG: {
+ final int pid = msg.arg1;
+ final int uid = msg.arg2;
+ dispatchProcessDied(pid, uid);
+ break;
}
- dispatchUidsChanged();
- } break;
- case DISPATCH_OOM_ADJ_OBSERVER_MSG: {
- dispatchOomAdjObserver((String)msg.obj);
- } break;
- case PUSH_TEMP_WHITELIST_UI_MSG: {
- pushTempWhitelist();
- } break;
+ case DISPATCH_UIDS_CHANGED_UI_MSG: {
+ if (false) { // DO NOT SUBMIT WITH TRUE
+ maybeTriggerWatchdog();
+ }
+ dispatchUidsChanged();
+ } break;
+ case DISPATCH_OOM_ADJ_OBSERVER_MSG: {
+ dispatchOomAdjObserver((String) msg.obj);
+ } break;
+ case PUSH_TEMP_WHITELIST_UI_MSG: {
+ pushTempWhitelist();
+ } break;
}
}
}
@@ -2386,7 +2377,8 @@
mConstants = hasHandlerThread
? new ActivityManagerConstants(mContext, this, mHandler) : null;
final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
- mProcessList.init(this, activeUids);
+ mPlatformCompat = null;
+ mProcessList.init(this, activeUids, mPlatformCompat);
mLowMemDetector = null;
mOomAdjuster = hasHandlerThread
? new OomAdjuster(this, mProcessList, activeUids, handlerThread) : null;
@@ -2410,7 +2402,6 @@
mFactoryTest = FACTORY_TEST_OFF;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mInternal = new LocalService();
- mPlatformCompat = null;
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2439,7 +2430,9 @@
mConstants = new ActivityManagerConstants(mContext, this, mHandler);
final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */);
- mProcessList.init(this, activeUids);
+ mPlatformCompat = (PlatformCompat) ServiceManager.getService(
+ Context.PLATFORM_COMPAT_SERVICE);
+ mProcessList.init(this, activeUids, mPlatformCompat);
mLowMemDetector = new LowMemDetector(this);
mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
@@ -2547,9 +2540,6 @@
mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext);
- mPlatformCompat = (PlatformCompat) ServiceManager.getService(
- Context.PLATFORM_COMPAT_SERVICE);
-
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
@@ -5032,9 +5022,7 @@
bindApplicationTimeMillis = SystemClock.elapsedRealtime();
mAtmInternal.preBindApplication(app.getWindowProcessController());
final ActiveInstrumentation instr2 = app.getActiveInstrumentation();
- long[] disabledCompatChanges = {};
if (mPlatformCompat != null) {
- disabledCompatChanges = mPlatformCompat.getDisabledChanges(app.info);
mPlatformCompat.resetReporting(app.info);
}
if (app.isolatedEntryPoint != null) {
@@ -5053,7 +5041,7 @@
app.compat, getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
- disabledCompatChanges);
+ app.mDisabledCompatChanges);
} else {
thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
null, null, null, testMode,
@@ -5063,7 +5051,7 @@
app.compat, getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
- disabledCompatChanges);
+ app.mDisabledCompatChanges);
}
if (profilerInfo != null) {
profilerInfo.closeFd();
@@ -7570,6 +7558,26 @@
});
}
+ @Override
+ public void appNotResponding(final String reason) {
+ final int callingPid = Binder.getCallingPid();
+
+ synchronized (mPidsSelfLocked) {
+ final ProcessRecord app = mPidsSelfLocked.get(callingPid);
+ if (app == null) {
+ throw new SecurityException("Unknown process: " + callingPid);
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ app.appNotResponding(
+ null, app.info, null, null, false, "App requested: " + reason);
+ }
+ });
+ }
+ }
+
public final void installSystemProviders() {
List<ProviderInfo> providers;
synchronized (this) {
@@ -8392,6 +8400,18 @@
return BugReportHandlerUtil.launchBugReportHandlerApp(mContext);
}
+ /**
+ * Get packages of bugreport-whitelisted apps to handle a bug report.
+ *
+ * @return packages of bugreport-whitelisted apps to handle a bug report.
+ */
+ @Override
+ public List<String> getBugreportWhitelistedPackages() {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_DEBUGGING,
+ "getBugreportWhitelistedPackages");
+ return new ArrayList<>(SystemConfig.getInstance().getBugreportWhitelistedPackages());
+ }
+
public void registerProcessObserver(IProcessObserver observer) {
enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
"registerProcessObserver()");
@@ -9370,9 +9390,9 @@
}
}
- void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
+ void killAppAtUsersRequest(ProcessRecord app) {
synchronized (this) {
- mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
+ mAppErrors.killAppAtUserRequestLocked(app);
}
}
@@ -9773,9 +9793,12 @@
sb.append(report);
}
- String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
- int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0);
- int maxDataFileSize = DROPBOX_MAX_SIZE - sb.length()
+ String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+ String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
+ int lines = Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
+ int dropboxMaxSize = Settings.Global.getInt(
+ mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
+ int maxDataFileSize = dropboxMaxSize - sb.length()
- lines * RESERVED_BYTES_PER_LOGCAT_LINE;
if (dataFile != null && maxDataFileSize > 0) {
@@ -13048,14 +13071,31 @@
pw.println(totalPss - cachedPss);
}
}
- long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
+ long kernelUsed = memInfo.getKernelUsedSizeKb();
+ final long ionHeap = Debug.getIonHeapsSizeKb();
+ if (ionHeap > 0) {
+ final long ionMapped = Debug.getIonMappedSizeKb();
+ final long ionUnmapped = ionHeap - ionMapped;
+ final long ionPool = Debug.getIonPoolsSizeKb();
+ pw.print(" ION: ");
+ pw.print(stringifyKBSize(ionHeap + ionPool));
+ pw.print(" (");
+ pw.print(stringifyKBSize(ionMapped));
+ pw.print(" mapped + ");
+ pw.print(stringifyKBSize(ionUnmapped));
+ pw.print(" unmapped + ");
+ pw.print(stringifyKBSize(ionPool));
+ pw.println(" pools)");
+ kernelUsed += ionUnmapped;
+ }
+ final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
- - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
+ - kernelUsed - memInfo.getZramTotalSizeKb();
if (!opts.isCompact) {
pw.print(" Used RAM: "); pw.print(stringifyKBSize(totalPss - cachedPss
- + memInfo.getKernelUsedSizeKb())); pw.print(" (");
+ + kernelUsed)); pw.print(" (");
pw.print(stringifyKBSize(totalPss - cachedPss)); pw.print(" used pss + ");
- pw.print(stringifyKBSize(memInfo.getKernelUsedSizeKb())); pw.print(" kernel)\n");
+ pw.print(stringifyKBSize(kernelUsed)); pw.print(" kernel)\n");
pw.print(" Lost RAM: "); pw.println(stringifyKBSize(lostRAM));
} else {
pw.print("lostram,"); pw.println(lostRAM);
@@ -13821,14 +13861,25 @@
memInfoBuilder.append(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb()
+ memInfo.getFreeSizeKb()));
memInfoBuilder.append("\n");
+ long kernelUsed = memInfo.getKernelUsedSizeKb();
+ final long ionHeap = Debug.getIonHeapsSizeKb();
+ if (ionHeap > 0) {
+ final long ionMapped = Debug.getIonMappedSizeKb();
+ final long ionUnmapped = ionHeap - ionMapped;
+ final long ionPool = Debug.getIonPoolsSizeKb();
+ memInfoBuilder.append(" ION: ");
+ memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
+ memInfoBuilder.append("\n");
+ kernelUsed += ionUnmapped;
+ }
memInfoBuilder.append(" Used RAM: ");
memInfoBuilder.append(stringifyKBSize(
- totalPss - cachedPss + memInfo.getKernelUsedSizeKb()));
+ totalPss - cachedPss + kernelUsed));
memInfoBuilder.append("\n");
memInfoBuilder.append(" Lost RAM: ");
memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
- (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
- - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb()));
+ - kernelUsed - memInfo.getZramTotalSizeKb()));
memInfoBuilder.append("\n");
Slog.i(TAG, "Low on memory:");
Slog.i(TAG, shortNativeBuilder.toString());
@@ -13994,18 +14045,7 @@
ProcessList.abortNextPssTime(app.procStateMemTracker);
// Dismiss any open dialogs.
- if (app.crashDialog != null && !app.forceCrashReport) {
- app.crashDialog.dismiss();
- app.crashDialog = null;
- }
- if (app.anrDialog != null) {
- app.anrDialog.dismiss();
- app.anrDialog = null;
- }
- if (app.waitDialog != null) {
- app.waitDialog.dismiss();
- app.waitDialog = null;
- }
+ app.getDialogController().clearAllErrorDialogs();
app.setCrashing(false);
app.setNotResponding(false);
@@ -18436,7 +18476,7 @@
public ActivityPresentationInfo getActivityPresentationInfo(IBinder token) {
int displayId = Display.INVALID_DISPLAY;
try {
- displayId = mActivityTaskManager.getActivityDisplayId(token);
+ displayId = mActivityTaskManager.getDisplayId(token);
} catch (RemoteException e) {
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 7f1d5a3..79fe610 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2909,6 +2909,12 @@
final PlatformCompat platformCompat = (PlatformCompat)
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
String toggleValue = getNextArgRequired();
+ if (toggleValue.equals("reset-all")) {
+ final String packageName = getNextArgRequired();
+ pw.println("Reset all changes for " + packageName + " to default value.");
+ platformCompat.clearOverrides(packageName);
+ return 0;
+ }
long changeId;
String changeIdString = getNextArgRequired();
try {
@@ -3267,9 +3273,14 @@
pw.println(" without restarting any processes.");
pw.println(" write");
pw.println(" Write all pending state to storage.");
- pw.println(" compat enable|disable|reset <CHANGE_ID|CHANGE_NAME> <PACKAGE_NAME>");
- pw.println(" Toggles a change either by id or by name for <PACKAGE_NAME>.");
- pw.println(" It kills <PACKAGE_NAME> (to allow the toggle to take effect).");
+ pw.println(" compat [COMMAND] [...]: sub-commands for toggling app-compat changes.");
+ pw.println(" enable|disable|reset <CHANGE_ID|CHANGE_NAME> <PACKAGE_NAME>");
+ pw.println(" Toggles a change either by id or by name for <PACKAGE_NAME>.");
+ pw.println(" It kills <PACKAGE_NAME> (to allow the toggle to take effect).");
+ pw.println(" reset-all <PACKAGE_NAME>");
+ pw.println(" Removes all existing overrides for all changes for ");
+ pw.println(" <PACKAGE_NAME> (back to default behaviour).");
+ pw.println(" It kills <PACKAGE_NAME> (to allow the toggle to take effect).");
pw.println();
Intent.printIntentArgsHelp(pw, "");
}
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index 852c9b65..d76e2d7 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -167,8 +167,10 @@
private void setResult(int result) {
synchronized (mService) {
- if (mProc != null && mProc.crashDialog == AppErrorDialog.this) {
- mProc.crashDialog = null;
+ if (mProc != null) {
+ // Don't dismiss again since it leads to recursive call between dismiss and this
+ // method.
+ mProc.getDialogController().clearCrashDialogs(false /* needDismiss */);
}
}
mResult.set(result);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 83a7341..c380726 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -29,7 +29,6 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ApplicationErrorReport;
-import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -313,13 +312,8 @@
}
}
- void killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog) {
- if (app.anrDialog == fromDialog) {
- app.anrDialog = null;
- }
- if (app.waitDialog == fromDialog) {
- app.waitDialog = null;
- }
+ void killAppAtUserRequestLocked(ProcessRecord app) {
+ app.getDialogController().clearAllErrorDialogs();
killAppImmediateLocked(app, "user-terminated", "user request after error");
}
@@ -795,7 +789,6 @@
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
- AppErrorDialog dialogToShow = null;
final String packageName;
final int userId;
synchronized (mService) {
@@ -807,7 +800,7 @@
}
packageName = proc.info.packageName;
userId = proc.userId;
- if (proc.crashDialog != null) {
+ if (proc.getDialogController().hasCrashDialogs()) {
Slog.e(TAG, "App already has crash dialog: " + proc);
if (res != null) {
res.set(AppErrorDialog.ALREADY_SHOWING);
@@ -840,7 +833,7 @@
if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
&& !crashSilenced
&& (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
- proc.crashDialog = dialogToShow = new AppErrorDialog(mContext, mService, data);
+ proc.getDialogController().showCrashDialogs(data);
} else {
// The device is asleep, so just pretend that the user
// saw a crash dialog and hit "force quit".
@@ -849,11 +842,6 @@
}
}
}
- // If we've created a crash dialog, show it without the lock held
- if (dialogToShow != null) {
- Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId);
- dialogToShow.show();
- }
}
private void stopReportingCrashesLocked(ProcessRecord proc) {
@@ -864,7 +852,6 @@
}
void handleShowAnrUi(Message msg) {
- Dialog dialogToShow = null;
List<VersionedPackage> packageList = null;
synchronized (mService) {
AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
@@ -876,7 +863,7 @@
if (!proc.isPersistent()) {
packageList = proc.getPackageListWithVersionCode();
}
- if (proc.anrDialog != null) {
+ if (proc.getDialogController().hasAnrDialogs()) {
Slog.e(TAG, "App already has anr dialog: " + proc);
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
AppNotRespondingDialog.ALREADY_SHOWING);
@@ -886,19 +873,14 @@
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
- dialogToShow = new AppNotRespondingDialog(mService, mContext, data);
- proc.anrDialog = dialogToShow;
+ proc.getDialogController().showAnrDialogs(data);
} else {
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
AppNotRespondingDialog.CANT_SHOW);
// Just kill the app if there is no dialog to be shown.
- mService.killAppAtUsersRequest(proc, null);
+ mService.killAppAtUsersRequest(proc);
}
}
- // If we've created a crash dialog, show it without the lock held
- if (dialogToShow != null) {
- dialogToShow.show();
- }
// Notify PackageWatchdog without the lock held
if (packageList != null) {
mPackageWatchdog.onPackageFailure(packageList,
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index 65d7e01..dac5325 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -16,13 +16,10 @@
package com.android.server.am;
-import android.content.pm.ApplicationInfo;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
@@ -35,6 +32,9 @@
import android.widget.FrameLayout;
import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+
final class AppNotRespondingDialog extends BaseErrorDialog implements View.OnClickListener {
private static final String TAG = "AppNotRespondingDialog";
@@ -145,7 +145,7 @@
switch (msg.what) {
case FORCE_CLOSE:
// Kill the application.
- mService.killAppAtUsersRequest(mProc, AppNotRespondingDialog.this);
+ mService.killAppAtUsersRequest(mProc);
break;
case WAIT_AND_REPORT:
case WAIT:
@@ -160,9 +160,7 @@
app.setNotResponding(false);
app.notRespondingReport = null;
- if (app.anrDialog == AppNotRespondingDialog.this) {
- app.anrDialog = null;
- }
+ app.getDialogController().clearAnrDialogs();
mService.mServices.scheduleServiceTimeoutLocked(app);
}
break;
diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
index 27865a8..3ce2471 100644
--- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
+++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
@@ -66,7 +66,7 @@
switch (msg.what) {
case 1:
// Kill the application.
- mService.killAppAtUsersRequest(mProc, AppWaitingForDebuggerDialog.this);
+ mService.killAppAtUsersRequest(mProc);
break;
}
}
diff --git a/services/core/java/com/android/server/am/LmkdConnection.java b/services/core/java/com/android/server/am/LmkdConnection.java
index d1e09db..f41c364 100644
--- a/services/core/java/com/android/server/am/LmkdConnection.java
+++ b/services/core/java/com/android/server/am/LmkdConnection.java
@@ -18,6 +18,7 @@
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -35,9 +36,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.TimeUnit;
/**
* Lmkd connection to communicate with lowmemorykiller daemon.
@@ -46,7 +44,7 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "LmkdConnection" : TAG_AM;
// lmkd reply max size in bytes
- private static final int LMKD_REPLY_MAX_SIZE = 8;
+ private static final int LMKD_REPLY_MAX_SIZE = 12;
// connection listener interface
interface LmkdConnectionListener {
@@ -64,6 +62,15 @@
*/
public boolean isReplyExpected(ByteBuffer replyBuf, ByteBuffer dataReceived,
int receivedLen);
+
+ /**
+ * Handle the received message if it's unsolicited.
+ *
+ * @param dataReceived The buffer holding received data
+ * @param receivedLen Size of the data received
+ * @return True if the message has been handled correctly, false otherwise.
+ */
+ boolean handleUnsolicitedMessage(ByteBuffer dataReceived, int receivedLen);
}
private final MessageQueue mMsgQueue;
@@ -187,17 +194,17 @@
mReplyBuf.rewind();
// wakeup the waiting thread
mReplyBufLock.notifyAll();
- } else {
- // received asynchronous or unexpected packet
+ } else if (!mListener.handleUnsolicitedMessage(mInputBuf, len)) {
+ // received unexpected packet
// treat this as an error
mReplyBuf = null;
mReplyBufLock.notifyAll();
- Slog.e(TAG, "Received unexpected packet from lmkd");
+ Slog.e(TAG, "Received an unexpected packet from lmkd");
}
- } else {
+ } else if (!mListener.handleUnsolicitedMessage(mInputBuf, len)) {
// received asynchronous communication from lmkd
- // we don't support this yet
- Slog.w(TAG, "Received an asynchronous packet from lmkd");
+ // but we don't recognize it.
+ Slog.w(TAG, "Received an unexpected packet from lmkd");
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 32975d7..772f9b3 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -59,6 +59,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.AppZygote;
@@ -78,6 +79,7 @@
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
+import android.provider.DeviceConfig;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -85,6 +87,7 @@
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -101,6 +104,7 @@
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.Watchdog;
+import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.DexManager;
import com.android.server.wm.ActivityServiceConnectionsHolder;
import com.android.server.wm.WindowManagerService;
@@ -116,6 +120,7 @@
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
+import java.util.Map;
/**
* Activity manager code dealing with processes.
@@ -123,6 +128,13 @@
public final class ProcessList {
static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
+ // A device config to control the minimum target SDK to enable app data isolation
+ static final String ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.zygote.app_data_isolation";
+
+ // A device config to control the minimum target SDK to enable app data isolation
+ static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK = "android_app_data_isolation_min_sdk";
+
// The minimum time we allow between crashes, for us to consider this
// application to be bad and stop and its services and reject broadcasts.
static final int MIN_CRASH_INTERVAL = 60 * 1000;
@@ -277,14 +289,16 @@
// LMK_PROCREMOVE <pid>
// LMK_PROCPURGE
// LMK_GETKILLCNT
+ // LMK_PROCKILL
static final byte LMK_TARGET = 0;
static final byte LMK_PROCPRIO = 1;
static final byte LMK_PROCREMOVE = 2;
static final byte LMK_PROCPURGE = 3;
static final byte LMK_GETKILLCNT = 4;
+ static final byte LMK_PROCKILL = 5; // Note: this is an unsolicated command
// lmkd reconnect delay in msecs
- private final static long LMDK_RECONNECT_DELAY_MS = 1000;
+ private static final long LMKD_RECONNECT_DELAY_MS = 1000;
/**
* How long between a process kill and we actually receive its death recipient
@@ -334,6 +348,8 @@
private boolean mOomLevelsSet = false;
+ private boolean mAppDataIsolationEnabled = false;
+
/**
* Temporary to avoid allocations. Protected by main lock.
*/
@@ -390,6 +406,12 @@
ActiveUids mActiveUids;
/**
+ * The listener who is intereted with the lmkd kills.
+ */
+ @GuardedBy("mService")
+ private LmkdKillListener mLmkdKillListener = null;
+
+ /**
* The currently running isolated processes.
*/
final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>();
@@ -405,6 +427,15 @@
final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
new ArrayMap<AppZygote, ArrayList<ProcessRecord>>();
+ private PlatformCompat mPlatformCompat = null;
+
+ interface LmkdKillListener {
+ /**
+ * Called when there is a process kill by lmkd.
+ */
+ void onLmkdKillOccurred(int pid, int uid);
+ }
+
final class IsolatedUidRange {
@VisibleForTesting
public final int mFirstUid;
@@ -443,9 +474,7 @@
@GuardedBy("ProcessList.this.mService")
void freeIsolatedUidLocked(int uid) {
- // Strip out userId
- final int appId = UserHandle.getAppId(uid);
- mUidUsed.delete(appId);
+ mUidUsed.delete(uid);
}
};
@@ -557,7 +586,8 @@
final class KillHandler extends Handler {
static final int KILL_PROCESS_GROUP_MSG = 4000;
- static final int LMDK_RECONNECT_MSG = 4001;
+ static final int LMKD_RECONNECT_MSG = 4001;
+ static final int LMKD_PROC_KILLED_MSG = 4002;
public KillHandler(Looper looper) {
super(looper, null, true);
@@ -571,15 +601,18 @@
Process.killProcessGroup(msg.arg1 /* uid */, msg.arg2 /* pid */);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
- case LMDK_RECONNECT_MSG:
+ case LMKD_RECONNECT_MSG:
if (!sLmkdConnection.connect()) {
Slog.i(TAG, "Failed to connect to lmkd, retry after " +
- LMDK_RECONNECT_DELAY_MS + " ms");
- // retry after LMDK_RECONNECT_DELAY_MS
+ LMKD_RECONNECT_DELAY_MS + " ms");
+ // retry after LMKD_RECONNECT_DELAY_MS
sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage(
- KillHandler.LMDK_RECONNECT_MSG), LMDK_RECONNECT_DELAY_MS);
+ KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
}
break;
+ case LMKD_PROC_KILLED_MSG:
+ handleLmkdProcKilled(msg.arg1 /* pid */, msg.arg2 /* uid */);
+ break;
default:
super.handleMessage(msg);
@@ -596,9 +629,15 @@
updateOomLevels(0, 0, false);
}
- void init(ActivityManagerService service, ActiveUids activeUids) {
+ void init(ActivityManagerService service, ActiveUids activeUids,
+ PlatformCompat platformCompat) {
mService = service;
mActiveUids = activeUids;
+ mPlatformCompat = platformCompat;
+ // 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);
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
@@ -612,13 +651,15 @@
Slog.i(TAG, "Connection with lmkd established");
return onLmkdConnect(ostream);
}
+
@Override
public void onDisconnect() {
Slog.w(TAG, "Lost connection to lmkd");
// start reconnection after delay to let lmkd restart
sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage(
- KillHandler.LMDK_RECONNECT_MSG), LMDK_RECONNECT_DELAY_MS);
+ KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
}
+
@Override
public boolean isReplyExpected(ByteBuffer replyBuf,
ByteBuffer dataReceived, int receivedLen) {
@@ -627,6 +668,26 @@
return (receivedLen == replyBuf.array().length &&
dataReceived.getInt(0) == replyBuf.getInt(0));
}
+
+ @Override
+ public boolean handleUnsolicitedMessage(ByteBuffer dataReceived,
+ int receivedLen) {
+ if (receivedLen < 4) {
+ return false;
+ }
+ switch (dataReceived.getInt(0)) {
+ case LMK_PROCKILL:
+ if (receivedLen != 12) {
+ return false;
+ }
+ sKillHandler.obtainMessage(KillHandler.LMKD_PROC_KILLED_MSG,
+ dataReceived.getInt(4), dataReceived.getInt(8))
+ .sendToTarget();
+ return true;
+ default:
+ return false;
+ }
+ }
}
);
}
@@ -1303,10 +1364,10 @@
if (!sLmkdConnection.isConnected()) {
// try to connect immediately and then keep retrying
sKillHandler.sendMessage(
- sKillHandler.obtainMessage(KillHandler.LMDK_RECONNECT_MSG));
+ sKillHandler.obtainMessage(KillHandler.LMKD_RECONNECT_MSG));
// wait for connection retrying 3 times (up to 3 seconds)
- if (!sLmkdConnection.waitForConnection(3 * LMDK_RECONNECT_DELAY_MS)) {
+ if (!sLmkdConnection.waitForConnection(3 * LMKD_RECONNECT_DELAY_MS)) {
return false;
}
}
@@ -1664,6 +1725,10 @@
Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
+ " with non-zero pid:" + app.pid);
}
+ app.mDisabledCompatChanges = null;
+ if (mPlatformCompat != null) {
+ app.mDisabledCompatChanges = mPlatformCompat.getDisabledChanges(app.info);
+ }
final long startSeq = app.startSeq = ++mProcStartSeqCounter;
app.setStartParams(uid, hostingRecord, seInfo, startTime);
app.setUsingWrapper(invokeWith != null
@@ -1805,6 +1870,32 @@
}
}
+ private boolean shouldIsolateAppData(ProcessRecord app) {
+ if (!mAppDataIsolationEnabled) {
+ return false;
+ }
+ if (!UserHandle.isApp(app.uid)) {
+ return false;
+ }
+ final int minTargetSdk = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ ANDROID_APP_DATA_ISOLATION_MIN_SDK, Build.VERSION_CODES.R);
+ return app.info.targetSdkVersion >= minTargetSdk;
+ }
+
+ private Map<String, Pair<String, Long>> getPackageAppDataInfoMap(PackageManagerInternal pmInt,
+ String[] packages, int uid) {
+ Map<String, Pair<String, Long>> result = new ArrayMap<>(packages.length);
+ int userId = UserHandle.getUserId(uid);
+ for (String packageName : packages) {
+ String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
+ long inode = pmInt.getCeDataInode(packageName, userId);
+ if (inode != 0) {
+ result.put(packageName, Pair.create(volumeUuid, inode));
+ }
+ }
+ return result;
+ }
+
private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
@@ -1821,13 +1912,27 @@
app.setHasForegroundActivities(true);
}
+ final Map<String, Pair<String, Long>> pkgDataInfoMap;
+
+ if (shouldIsolateAppData(app)) {
+ // Get all packages belongs to the same shared uid. sharedPackages is empty array
+ // if it doesn't have shared uid.
+ final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
+ final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
+ app.info.packageName, app.userId);
+ pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
+ ? new String[]{app.info.packageName} : sharedPackages, uid);
+ } else {
+ pkgDataInfoMap = null;
+ }
+
final Process.ProcessStartResult startResult;
if (hostingRecord.usesWebviewZygote()) {
startResult = startWebView(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
- app.info.dataDir, null, app.info.packageName,
- new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+ app.info.dataDir, null, app.info.packageName, app.mDisabledCompatChanges,
+ new String[]{PROC_START_SEQ_IDENT + app.startSeq});
} else if (hostingRecord.usesAppZygote()) {
final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
@@ -1835,14 +1940,15 @@
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
- /*useUsapPool=*/ false, isTopApp,
- new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+ /*useUsapPool=*/ false, isTopApp, app.mDisabledCompatChanges,
+ pkgDataInfoMap, new String[]{PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName, isTopApp,
- new String[] {PROC_START_SEQ_IDENT + app.startSeq});
+ app.mDisabledCompatChanges, pkgDataInfoMap,
+ new String[]{PROC_START_SEQ_IDENT + app.startSeq});
}
checkSlow(startTime, "startProcess: returned from zygote!");
return startResult;
@@ -3393,4 +3499,28 @@
}
}
}
+
+ void setLmkdKillListener(final LmkdKillListener listener) {
+ synchronized (mService) {
+ mLmkdKillListener = listener;
+ }
+ }
+
+ private void handleLmkdProcKilled(final int pid, final int uid) {
+ // Log only now
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "lmkd kill: pid=" + pid + " uid=" + uid);
+ }
+
+ if (mService == null) {
+ return;
+ }
+ // Notify any interesed party regarding the lmkd kills
+ synchronized (mService) {
+ final LmkdKillListener listener = mLmkdKillListener;
+ if (listener != null) {
+ mService.mHandler.post(()-> listener.onLmkdKillOccurred(pid, uid));
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 6f6f193..1fef353 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -62,6 +62,8 @@
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.Zygote;
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowProcessController;
import com.android.server.wm.WindowProcessListener;
@@ -70,6 +72,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
/**
* Full information about a particular process that
@@ -246,6 +249,8 @@
Debug.MemoryInfo lastMemInfo;
long lastMemInfoTime;
+ // Controller for error dialogs
+ private final ErrorDialogController mDialogController = new ErrorDialogController();
// Controller for driving the process state on the window manager side.
final private WindowProcessController mWindowProcessController;
// all ServiceRecord running in this process
@@ -272,16 +277,13 @@
boolean execServicesFg; // do we need to be executing services in the foreground?
private boolean mPersistent;// always keep this application running?
private boolean mCrashing; // are we in the process of crashing?
- Dialog crashDialog; // dialog being displayed due to crash.
boolean forceCrashReport; // suppress normal auto-dismiss of crash dialog & report UI?
private boolean mNotResponding; // does the app have a not responding dialog?
- Dialog anrDialog; // dialog being displayed due to app not resp.
volatile boolean removed; // Whether this process should be killed and removed from process
// list. It is set when the package is force-stopped or the process
// has crashed too many times.
private boolean mDebugging; // was app launched for debugging?
boolean waitedForDebugger; // has process show wait for debugger dialog?
- Dialog waitDialog; // current wait for debugger dialog
String shortStringName; // caching of toShortString() result.
String stringName; // caching of toString() result.
@@ -310,6 +312,8 @@
long startTime;
// This will be same as {@link #uid} usually except for some apps used during factory testing.
int startUid;
+ // set of disabled compat changes for the process (all others are enabled)
+ long[] mDisabledCompatChanges;
// Cached task info for OomAdjuster
private static final int VALUE_INVALID = -1;
@@ -516,21 +520,21 @@
pw.print(" killedByAm="); pw.print(killedByAm);
pw.print(" waitingToKill="); pw.println(waitingToKill);
}
- if (mDebugging || mCrashing || crashDialog != null || mNotResponding
- || anrDialog != null || bad) {
+ if (mDebugging || mCrashing || mDialogController.hasCrashDialogs() || mNotResponding
+ || mDialogController.hasAnrDialogs() || bad) {
pw.print(prefix); pw.print("mDebugging="); pw.print(mDebugging);
- pw.print(" mCrashing="); pw.print(mCrashing);
- pw.print(" "); pw.print(crashDialog);
- pw.print(" mNotResponding="); pw.print(mNotResponding);
- pw.print(" " ); pw.print(anrDialog);
- pw.print(" bad="); pw.print(bad);
+ pw.print(" mCrashing=" + mCrashing);
+ pw.print(" " + mDialogController.mCrashDialogs);
+ pw.print(" mNotResponding=" + mNotResponding);
+ pw.print(" " + mDialogController.mAnrDialogs);
+ pw.print(" bad=" + bad);
- // mCrashing or mNotResponding is always set before errorReportReceiver
- if (errorReportReceiver != null) {
- pw.print(" errorReportReceiver=");
- pw.print(errorReportReceiver.flattenToShortString());
- }
- pw.println();
+ // mCrashing or mNotResponding is always set before errorReportReceiver
+ if (errorReportReceiver != null) {
+ pw.print(" errorReportReceiver=");
+ pw.print(errorReportReceiver.flattenToShortString());
+ }
+ pw.println();
}
if (whitelistManager) {
pw.print(prefix); pw.print("whitelistManager="); pw.println(whitelistManager);
@@ -1773,4 +1777,153 @@
mCachedAdj += minLayer;
}
}
+
+ ErrorDialogController getDialogController() {
+ return mDialogController;
+ }
+
+ /** A controller to generate error dialogs in {@link ProcessRecord} */
+ class ErrorDialogController {
+ /** dialogs being displayed due to crash */
+ private List<AppErrorDialog> mCrashDialogs;
+ /** dialogs being displayed due to app not responding */
+ private List<AppNotRespondingDialog> mAnrDialogs;
+ /** dialogs displayed due to strict mode violation */
+ private List<StrictModeViolationDialog> mViolationDialogs;
+ /** current wait for debugger dialog */
+ private AppWaitingForDebuggerDialog mWaitDialog;
+
+ private final WindowManagerInternal mWmInternal =
+ LocalServices.getService(WindowManagerInternal.class);
+
+ boolean hasCrashDialogs() {
+ return mCrashDialogs != null;
+ }
+
+ boolean hasAnrDialogs() {
+ return mAnrDialogs != null;
+ }
+
+ boolean hasViolationDialogs() {
+ return mViolationDialogs != null;
+ }
+
+ boolean hasDebugWaitingDialog() {
+ return mWaitDialog != null;
+ }
+
+ void clearAllErrorDialogs() {
+ clearCrashDialogs();
+ clearAnrDialogs();
+ clearViolationDialogs();
+ clearWaitingDialog();
+ }
+
+ void clearCrashDialogs() {
+ clearCrashDialogs(true /* needDismiss */);
+ }
+
+ void clearCrashDialogs(boolean needDismiss) {
+ if (mCrashDialogs == null) {
+ return;
+ }
+ if (needDismiss) {
+ forAllDialogs(mCrashDialogs, Dialog::dismiss);
+ }
+ mCrashDialogs = null;
+ }
+
+ void clearAnrDialogs() {
+ if (mAnrDialogs == null) {
+ return;
+ }
+ forAllDialogs(mAnrDialogs, Dialog::dismiss);
+ mAnrDialogs = null;
+ }
+
+ void clearViolationDialogs() {
+ if (mViolationDialogs == null) {
+ return;
+ }
+ forAllDialogs(mViolationDialogs, Dialog::dismiss);
+ mViolationDialogs = null;
+ }
+
+ void clearWaitingDialog() {
+ if (mWaitDialog == null) {
+ return;
+ }
+ mWaitDialog.dismiss();
+ mWaitDialog = null;
+ }
+
+ void forAllDialogs(List<? extends BaseErrorDialog> dialogs,
+ Consumer<BaseErrorDialog> c) {
+ for (int i = dialogs.size() - 1; i >= 0; i--) {
+ c.accept(dialogs.get(i));
+ }
+ }
+
+ void showCrashDialogs(AppErrorDialog.Data data) {
+ List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
+ mCrashDialogs = new ArrayList<>();
+
+ for (int i = contexts.size() - 1; i >= 0; i--) {
+ final Context c = contexts.get(i);
+ mCrashDialogs.add(new AppErrorDialog(c, mService, data));
+ }
+ mService.mUiHandler.post(() -> mCrashDialogs.forEach(Dialog::show));
+ }
+
+ void showAnrDialogs(AppNotRespondingDialog.Data data) {
+ List<Context> contexts = getDisplayContexts(isSilentAnr() /* lastUsedOnly */);
+ mAnrDialogs = new ArrayList<>();
+
+ for (int i = contexts.size() - 1; i >= 0; i--) {
+ final Context c = contexts.get(i);
+ mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data));
+ }
+ mService.mUiHandler.post(() -> mAnrDialogs.forEach(Dialog::show));
+ }
+
+ void showViolationDialogs(AppErrorResult res) {
+ List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
+ mViolationDialogs = new ArrayList<>();
+
+ for (int i = contexts.size() - 1; i >= 0; i--) {
+ final Context c = contexts.get(i);
+ mViolationDialogs.add(
+ new StrictModeViolationDialog(c, mService, res, ProcessRecord.this));
+ }
+ mService.mUiHandler.post(() -> mViolationDialogs.forEach(Dialog::show));
+ }
+
+ void showDebugWaitingDialogs() {
+ List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */);
+ final Context c = contexts.get(0);
+ mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, ProcessRecord.this);
+ mService.mUiHandler.post(() -> mWaitDialog.show());
+ }
+
+ /**
+ * Helper function to collect contexts from crashed app located displays
+ *
+ * @param lastUsedOnly Sets to {@code true} to indicate to only get last used context.
+ * Sets to {@code false} to collect contexts from crashed app located
+ * displays.
+ *
+ * @return display context list
+ */
+ private List<Context> getDisplayContexts(boolean lastUsedOnly) {
+ List<Context> displayContexts = new ArrayList<>();
+ if (!lastUsedOnly) {
+ mWindowProcessController.getDisplayContextsWithErrorDialogs(displayContexts);
+ }
+ // If there is no foreground window display, fallback to last used display.
+ if (displayContexts.isEmpty() || lastUsedOnly) {
+ displayContexts.add(mWmInternal.getTopFocusedDisplayUiContext());
+ }
+ return displayContexts;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/StrictModeViolationDialog.java b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
index 6da84bd..783f150 100644
--- a/services/core/java/com/android/server/am/StrictModeViolationDialog.java
+++ b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
@@ -85,8 +85,8 @@
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
synchronized (mService) {
- if (mProc != null && mProc.crashDialog == StrictModeViolationDialog.this) {
- mProc.crashDialog = null;
+ if (mProc != null) {
+ mProc.getDialogController().clearViolationDialogs();
}
}
mResult.set(msg.what);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 728291d..f9730a9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -407,6 +407,7 @@
*/
private boolean finishUserUnlocking(final UserState uss) {
final int userId = uss.mHandle.getIdentifier();
+ Slog.d(TAG, "UserController event: finishUserUnlocking(" + userId + ")");
// Only keep marching forward if user is actually unlocked
if (!StorageManager.isUserKeyUnlocked(userId)) return false;
synchronized (mLock) {
@@ -451,6 +452,7 @@
*/
void finishUserUnlocked(final UserState uss) {
final int userId = uss.mHandle.getIdentifier();
+ Slog.d(TAG, "UserController event: finishUserUnlocked(" + userId + ")");
// Only keep marching forward if user is actually unlocked
if (!StorageManager.isUserKeyUnlocked(userId)) return;
synchronized (mLock) {
@@ -521,6 +523,7 @@
private void finishUserUnlockedCompleted(UserState uss) {
final int userId = uss.mHandle.getIdentifier();
+ Slog.d(TAG, "UserController event: finishUserUnlockedCompleted(" + userId + ")");
synchronized (mLock) {
// Bail if we ended up with a stale user
if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
@@ -730,6 +733,7 @@
}
void finishUserStopping(final int userId, final UserState uss) {
+ Slog.d(TAG, "UserController event: finishUserStopping(" + userId + ")");
// On to the next.
final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
// This is the result receiver for the final shutdown broadcast.
@@ -769,6 +773,7 @@
void finishUserStopped(UserState uss) {
final int userId = uss.mHandle.getIdentifier();
+ Slog.d(TAG, "UserController event: finishUserStopped(" + userId + ")");
final boolean stopped;
boolean lockUser = true;
final ArrayList<IStopUserCallback> stopCallbacks;
@@ -1280,6 +1285,7 @@
boolean unlockUser(final @UserIdInt int userId, byte[] token, byte[] secret,
IProgressListener listener) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "unlockUser");
+ Slog.i(TAG, "unlocking user " + userId);
final long binderToken = Binder.clearCallingIdentity();
try {
return unlockUserCleared(userId, token, secret, listener);
@@ -1364,6 +1370,7 @@
boolean switchUser(final int targetUserId) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
+ Slog.i(TAG, "switching to user " + targetUserId);
int currentUserId = getCurrentUserId();
UserInfo targetUserInfo = getUserInfo(targetUserId);
if (targetUserId == currentUserId) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 366766e..9f23cdaf 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -17,10 +17,12 @@
package com.android.server.appop;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
-import static android.app.AppOpsManager.MAX_PRIORITY_UID_STATE;
-import static android.app.AppOpsManager.MIN_PRIORITY_UID_STATE;
+import static android.app.AppOpsManager.NoteOpEvent;
+import static android.app.AppOpsManager.OpEventProxyInfo;
import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
@@ -32,6 +34,9 @@
import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
import static android.app.AppOpsManager._NUM_OP;
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.app.AppOpsManager.makeKey;
import static android.app.AppOpsManager.modeToName;
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
@@ -92,7 +97,6 @@
import android.util.AtomicFile;
import android.util.KeyValueListParser;
import android.util.LongSparseArray;
-import android.util.LongSparseLongArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -364,8 +368,6 @@
public long pendingStateCommitTime;
public int capability;
public int pendingCapability;
- // For all features combined
- public int startNesting;
public ArrayMap<String, Ops> pkgOps;
public SparseIntArray opModes;
@@ -473,126 +475,318 @@
}
}
- private static final class FeatureOp {
+ /** A in progress startOp->finishOp event */
+ private static final class InProgressStartOpEvent implements IBinder.DeathRecipient {
+ /** Time of startOp event */
+ final long startTime;
+
+ /** Id of the client that started the event */
+ final @NonNull IBinder clientId;
+
+ /** To call when client dies */
+ final @NonNull Runnable onDeath;
+
+ /** uidstate used when calling startOp */
+ final @AppOpsManager.UidState int uidState;
+
+ /** How many times the op was started but not finished yet */
+ int numUnfinishedStarts;
+
+ private InProgressStartOpEvent(long startTime, @NonNull IBinder clientId,
+ @NonNull Runnable onDeath, int uidState) throws RemoteException {
+ this.startTime = startTime;
+ this.clientId = clientId;
+ this.onDeath = onDeath;
+ this.uidState = uidState;
+
+ clientId.linkToDeath(this, 0);
+ }
+
+ /** Clean up event */
+ public void finish() {
+ clientId.unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ onDeath.run();
+ }
+ }
+
+ private final class FeatureOp {
public final @NonNull Op parent;
- public boolean running;
+ /**
+ * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
+ *
+ * <p>Key is {@link AppOpsManager#makeKey}
+ */
+ @GuardedBy("AppOpsService.this")
+ private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mAccessEvents;
- private @Nullable LongSparseLongArray mAccessTimes;
- private @Nullable LongSparseLongArray mRejectTimes;
- private @Nullable LongSparseLongArray mDurations;
- private @Nullable LongSparseLongArray mProxyUids;
- private @Nullable LongSparseArray<String> mProxyFeatureIds;
- private @Nullable LongSparseArray<String> mProxyPackageNames;
+ /**
+ * Last rejected accesses for each uidState/opFlag combination
+ *
+ * <p>Key is {@link AppOpsManager#makeKey}
+ */
+ @GuardedBy("AppOpsService.this")
+ private @Nullable LongSparseArray<AppOpsManager.NoteOpEvent> mRejectEvents;
- public int startNesting;
- public long startRealtime;
+ /**
+ * Currently in progress startOp events
+ *
+ * <p>Key is clientId
+ */
+ @GuardedBy("AppOpsService.this")
+ private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
FeatureOp(@NonNull Op parent) {
this.parent = parent;
}
- public void accessed(long time, int proxyUid, @Nullable String proxyPackageName,
+ /**
+ * Update state when noteOp was rejected or startOp->finishOp event finished
+ *
+ * @param proxyUid The uid of the proxy
+ * @param proxyPackageName The package name of the proxy
+ * @param proxyFeatureId the featureId in the proxies package
+ * @param uidState UID state of the app noteOp/startOp was called for
+ * @param flags OpFlags of the call
+ */
+ public void accessed(int proxyUid, @Nullable String proxyPackageName,
@Nullable String proxyFeatureId, @AppOpsManager.UidState int uidState,
@OpFlags int flags) {
- final long key = AppOpsManager.makeKey(uidState, flags);
- if (mAccessTimes == null) {
- mAccessTimes = new LongSparseLongArray();
- }
- mAccessTimes.put(key, time);
- updateProxyState(key, proxyUid, proxyPackageName, proxyFeatureId);
- if (mDurations != null) {
- mDurations.delete(key);
- }
+ accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName,
+ proxyFeatureId, uidState, flags);
}
- public void rejected(long time, int proxyUid, @Nullable String proxyPackageName,
- @Nullable String proxyFeatureId, @AppOpsManager.UidState int uidState,
- @OpFlags int flags) {
- final long key = AppOpsManager.makeKey(uidState, flags);
- if (mRejectTimes == null) {
- mRejectTimes = new LongSparseLongArray();
- }
- mRejectTimes.put(key, time);
- updateProxyState(key, proxyUid, proxyPackageName, proxyFeatureId);
- if (mDurations != null) {
- mDurations.delete(key);
- }
- }
-
- public void started(long time, @AppOpsManager.UidState int uidState, @OpFlags int flags) {
- updateAccessTimeAndDuration(time, -1 /*duration*/, uidState, flags);
- running = true;
- }
-
- public void finished(long time, long duration, @AppOpsManager.UidState int uidState,
- @OpFlags int flags) {
- updateAccessTimeAndDuration(time, duration, uidState, flags);
- running = false;
- }
-
- public void running(long time, long duration, @AppOpsManager.UidState int uidState,
- @OpFlags int flags) {
- updateAccessTimeAndDuration(time, duration, uidState, flags);
- }
-
- public void continuing(long duration, @AppOpsManager.UidState int uidState,
- @OpFlags int flags) {
- final long key = AppOpsManager.makeKey(uidState, flags);
- if (mDurations == null) {
- mDurations = new LongSparseLongArray();
- }
- mDurations.put(key, duration);
- }
-
- private void updateAccessTimeAndDuration(long time, long duration,
+ /**
+ * Add an access that was previously collected.
+ *
+ * @param noteTime The time of the event
+ * @param duration The duration of the event
+ * @param proxyUid The uid of the proxy
+ * @param proxyPackageName The package name of the proxy
+ * @param proxyFeatureId the featureId in the proxies package
+ * @param uidState UID state of the app noteOp/startOp was called for
+ * @param flags OpFlags of the call
+ */
+ public void accessed(long noteTime, long duration, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyFeatureId,
@AppOpsManager.UidState int uidState, @OpFlags int flags) {
- final long key = AppOpsManager.makeKey(uidState, flags);
- if (mAccessTimes == null) {
- mAccessTimes = new LongSparseLongArray();
+ long key = makeKey(uidState, flags);
+
+ if (mAccessEvents == null) {
+ mAccessEvents = new LongSparseArray<>(1);
}
- mAccessTimes.put(key, time);
- if (mDurations == null) {
- mDurations = new LongSparseLongArray();
+
+ OpEventProxyInfo proxyInfo = null;
+ if (proxyUid != Process.INVALID_UID) {
+ proxyInfo = new OpEventProxyInfo(proxyUid, proxyPackageName, proxyFeatureId);
}
- mDurations.put(key, duration);
+ mAccessEvents.put(key, new NoteOpEvent(noteTime, duration, proxyInfo));
}
- private void updateProxyState(long key, int proxyUid,
- @Nullable String proxyPackageName, @Nullable String featureId) {
- if (proxyUid == Process.INVALID_UID) {
+ /**
+ * Update state when noteOp/startOp was rejected.
+ *
+ * @param uidState UID state of the app noteOp is called for
+ * @param flags OpFlags of the call
+ */
+ public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) {
+ rejected(System.currentTimeMillis(), uidState, flags);
+ }
+
+ /**
+ * Add an rejection that was previously collected
+ *
+ * @param noteTime The time of the event
+ * @param uidState UID state of the app noteOp/startOp was called for
+ * @param flags OpFlags of the call
+ */
+ public void rejected(long noteTime, @AppOpsManager.UidState int uidState,
+ @OpFlags int flags) {
+ long key = makeKey(uidState, flags);
+
+ if (mRejectEvents == null) {
+ mRejectEvents = new LongSparseArray<>(1);
+ }
+
+ // We do not collect proxy information for rejections yet
+ mRejectEvents.put(key, new NoteOpEvent(noteTime, -1, null));
+ }
+
+ /**
+ * Update state when start was called
+ *
+ * @param clientId Id of the startOp caller
+ * @param uidState UID state of the app startOp is called for
+ */
+ public void started(@NonNull IBinder clientId, @AppOpsManager.UidState int uidState)
+ throws RemoteException {
+ if (!parent.isRunning()) {
+ scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+ parent.packageName, true);
+ }
+
+ if (mInProgressEvents == null) {
+ mInProgressEvents = new ArrayMap<>(1);
+ }
+
+
+ InProgressStartOpEvent event = mInProgressEvents.get(clientId);
+ if (event == null) {
+ event = new InProgressStartOpEvent(System.currentTimeMillis(), clientId, () -> {
+ // In the case the client dies without calling finish first
+ synchronized (AppOpsService.this) {
+ if (mInProgressEvents == null) {
+ return;
+ }
+
+ InProgressStartOpEvent deadEvent = mInProgressEvents.get(clientId);
+ if (deadEvent != null) {
+ deadEvent.numUnfinishedStarts = 1;
+ }
+
+ finished(clientId);
+ }
+ }, uidState);
+ mInProgressEvents.put(clientId, event);
+ } else {
+ if (uidState != event.uidState) {
+ onUidStateChanged(uidState);
+ }
+ }
+
+ event.numUnfinishedStarts++;
+
+ // startOp events don't support proxy, hence use flags==SELF
+ mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
+ uidState, OP_FLAG_SELF);
+ }
+
+ /**
+ * Update state when finishOp was called
+ *
+ * @param clientId Id of the finishOp caller
+ */
+ public void finished(@NonNull IBinder clientId) {
+ finished(clientId, true);
+ }
+
+ private void finished(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded) {
+ if (mInProgressEvents == null) {
+ Slog.wtf(TAG, "No ops running");
return;
}
- if (mProxyUids == null) {
- mProxyUids = new LongSparseLongArray();
+ int indexOfToken = mInProgressEvents.indexOfKey(clientId);
+ if (indexOfToken < 0) {
+ Slog.wtf(TAG, "No op running for the client");
+ return;
}
- mProxyUids.put(key, proxyUid);
- if (mProxyPackageNames == null) {
- mProxyPackageNames = new LongSparseArray<>();
- }
- mProxyPackageNames.put(key, proxyPackageName);
+ InProgressStartOpEvent event = mInProgressEvents.valueAt(indexOfToken);
+ event.numUnfinishedStarts--;
+ if (event.numUnfinishedStarts == 0) {
+ event.finish();
+ mInProgressEvents.removeAt(indexOfToken);
- if (mProxyFeatureIds == null) {
- mProxyFeatureIds = new LongSparseArray<>();
+ if (mAccessEvents == null) {
+ mAccessEvents = new LongSparseArray<>(1);
+ }
+
+ // startOp events don't support proxy, hence use flags==SELF
+ NoteOpEvent finishedEvent = new NoteOpEvent(event.startTime,
+ System.currentTimeMillis() - event.startTime, null);
+ mAccessEvents.put(makeKey(event.uidState, OP_FLAG_SELF), finishedEvent);
+
+ mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
+ parent.packageName, event.uidState, AppOpsManager.OP_FLAG_SELF,
+ finishedEvent.duration);
+
+ if (mInProgressEvents.isEmpty()) {
+ mInProgressEvents = null;
+
+ // TODO moltmann: Also callback for single feature activity changes
+ if (triggerCallbackIfNeeded && !parent.isRunning()) {
+ scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
+ parent.packageName, false);
+ }
+ }
}
- mProxyFeatureIds.put(key, featureId);
+ }
+
+ /**
+ * Notify that the state of the uid changed
+ *
+ * @param newState The new state
+ */
+ public void onUidStateChanged(@AppOpsManager.UidState int newState) {
+ if (mInProgressEvents == null) {
+ return;
+ }
+
+ int numInProgressEvents = mInProgressEvents.size();
+ for (int i = 0; i < numInProgressEvents; i++) {
+ InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
+
+ if (event.uidState != newState) {
+ try {
+ finished(event.clientId, false);
+ started(event.clientId, newState);
+ } catch (RemoteException e) {
+ if (DEBUG) Slog.e(TAG, "Cannot switch to new uidState " + newState);
+ }
+ }
+ }
+ }
+
+ public boolean isRunning() {
+ return mInProgressEvents != null;
}
boolean hasAnyTime() {
- return (mAccessTimes != null && mAccessTimes.size() > 0)
- || (mRejectTimes != null && mRejectTimes.size() > 0);
+ return (mAccessEvents != null && mAccessEvents.size() > 0)
+ || (mRejectEvents != null && mRejectEvents.size() > 0);
}
- @NonNull OpFeatureEntry.Builder createFeatureEntryBuilderLocked() {
- return new OpFeatureEntry.Builder(running, mAccessTimes, mRejectTimes, mDurations,
- mProxyUids, mProxyPackageNames, mProxyFeatureIds);
+ @NonNull OpFeatureEntry createFeatureEntryLocked() {
+ LongSparseArray<NoteOpEvent> accessEvents = null;
+ if (mAccessEvents != null) {
+ accessEvents = mAccessEvents.clone();
+ }
+
+ // Add in progress events as access events
+ if (mInProgressEvents != null) {
+ long now = System.currentTimeMillis();
+ int numInProgressEvents = mInProgressEvents.size();
+
+ if (accessEvents == null) {
+ accessEvents = new LongSparseArray<>(numInProgressEvents);
+ }
+
+ for (int i = 0; i < numInProgressEvents; i++) {
+ InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
+
+ // startOp events don't support proxy
+ accessEvents.append(makeKey(event.uidState, OP_FLAG_SELF),
+ new NoteOpEvent(event.startTime, now - event.startTime, null));
+ }
+ }
+
+ LongSparseArray<NoteOpEvent> rejectEvents = null;
+ if (mRejectEvents != null) {
+ rejectEvents = mRejectEvents.clone();
+ }
+
+ return new OpFeatureEntry(parent.op, isRunning(), accessEvents, rejectEvents);
}
}
- final static class Op {
+ final class Op {
int op;
+ int uid;
final UidState uidState;
final @NonNull String packageName;
@@ -601,8 +795,9 @@
/** featureId -> FeatureOp */
final ArrayMap<String, FeatureOp> mFeatures = new ArrayMap<>(1);
- Op(UidState uidState, String packageName, int op) {
+ Op(UidState uidState, String packageName, int op, int uid) {
this.op = op;
+ this.uid = uid;
this.uidState = uidState;
this.packageName = packageName;
this.mode = AppOpsManager.opToDefaultMode(op);
@@ -640,11 +835,10 @@
@NonNull OpEntry createEntryLocked() {
final int numFeatures = mFeatures.size();
- final Pair<String, OpFeatureEntry.Builder>[] featureEntries =
- new Pair[numFeatures];
+ final ArrayMap<String, OpFeatureEntry> featureEntries = new ArrayMap<>(numFeatures);
for (int i = 0; i < numFeatures; i++) {
- featureEntries[i] = new Pair<>(mFeatures.keyAt(i),
- mFeatures.valueAt(i).createFeatureEntryBuilderLocked());
+ featureEntries.put(mFeatures.keyAt(i),
+ mFeatures.valueAt(i).createFeatureEntryLocked());
}
return new OpEntry(op, mode, featureEntries);
@@ -653,18 +847,28 @@
@NonNull OpEntry createSingleFeatureEntryLocked(@Nullable String featureId) {
final int numFeatures = mFeatures.size();
- final Pair<String, AppOpsManager.OpFeatureEntry.Builder>[] featureEntries =
- new Pair[1];
+ final ArrayMap<String, OpFeatureEntry> featureEntries = new ArrayMap<>(1);
for (int i = 0; i < numFeatures; i++) {
if (Objects.equals(mFeatures.keyAt(i), featureId)) {
- featureEntries[0] = new Pair<>(mFeatures.keyAt(i),
- mFeatures.valueAt(i).createFeatureEntryBuilderLocked());
+ featureEntries.put(mFeatures.keyAt(i),
+ mFeatures.valueAt(i).createFeatureEntryLocked());
break;
}
}
return new OpEntry(op, mode, featureEntries);
}
+
+ boolean isRunning() {
+ final int numFeatures = mFeatures.size();
+ for (int i = 0; i < numFeatures; i++) {
+ if (mFeatures.valueAt(i).isRunning()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
final SparseArray<ArraySet<ModeCallback>> mOpModeWatchers = new SparseArray<>();
@@ -814,53 +1018,6 @@
}
}
- final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
-
- final class ClientState extends Binder implements DeathRecipient {
- final ArrayList<Pair<Op, String>> mStartedOps = new ArrayList<>();
- final IBinder mAppToken;
- final int mPid;
-
- ClientState(IBinder appToken) {
- mAppToken = appToken;
- mPid = Binder.getCallingPid();
- // Watch only for remote processes dying
- if (!(appToken instanceof Binder)) {
- try {
- mAppToken.linkToDeath(this, 0);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- }
-
- @Override
- public String toString() {
- return "ClientState{" +
- "mAppToken=" + mAppToken +
- ", " + "pid=" + mPid +
- '}';
- }
-
- @Override
- public void binderDied() {
- synchronized (AppOpsService.this) {
- for (int i=mStartedOps.size()-1; i>=0; i--) {
- final Pair<Op, String> startedOp = mStartedOps.get(i);
- final Op op = startedOp.first;
- final String featureId = startedOp.second;
-
- finishOperationLocked(op, featureId, /*finishNested*/ true);
- if (op.mFeatures.get(featureId).startNesting <= 0) {
- scheduleOpActiveChangedIfNeededLocked(op.op, op.uidState.uid,
- op.packageName, false);
- }
- }
- mClients.remove(mAppToken);
- }
- }
- }
-
public AppOpsService(File storagePath, Handler handler) {
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mFile = new AtomicFile(storagePath, "appops");
@@ -1023,43 +1180,19 @@
mUidStates.remove(uid);
}
- // Finish ops other packages started on behalf of the package.
- final int clientCount = mClients.size();
- for (int i = 0; i < clientCount; i++) {
- final ClientState client = mClients.valueAt(i);
- if (client.mStartedOps == null) {
- continue;
- }
- final int startedOpCount = client.mStartedOps.size();
- for (int j = startedOpCount - 1; j >= 0; j--) {
- final Pair<Op, String> startedOp = client.mStartedOps.get(j);
- final Op op = startedOp.first;
- final String featureId = startedOp.second;
-
- if (uid == op.uidState.uid && packageName.equals(op.packageName)) {
- finishOperationLocked(op, featureId, /*finishNested*/ true);
- client.mStartedOps.remove(j);
- if (op.mFeatures.get(featureId).startNesting <= 0) {
- scheduleOpActiveChangedIfNeededLocked(op.op,
- uid, packageName, false);
- }
- }
- }
- }
-
if (ops != null) {
scheduleFastWriteLocked();
- final int opCount = ops.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
+ final int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
final Op op = ops.valueAt(opNum);
final int numFeatures = op.mFeatures.size();
for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
- if (op.mFeatures.valueAt(featureNum).running) {
- scheduleOpActiveChangedIfNeededLocked(
- op.op, op.uidState.uid, op.packageName, false);
- break;
+ FeatureOp featureOp = op.mFeatures.valueAt(featureNum);
+
+ while (featureOp.mInProgressEvents != null) {
+ featureOp.mInProgressEvents.valueAt(0).onDeath.run();
}
}
}
@@ -1111,35 +1244,21 @@
}
uidState.pendingStateCommitTime = SystemClock.elapsedRealtime() + settleTime;
}
- if (uidState.startNesting != 0) {
- // There is some actively running operation... need to find it
- // and appropriately update its state.
- final long now = System.currentTimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
- for (int i = uidState.pkgOps.size() - 1; i >= 0; i--) {
- final Ops ops = uidState.pkgOps.valueAt(i);
- for (int j = ops.size() - 1; j >= 0; j--) {
- final Op op = ops.valueAt(j);
+
+ if (uidState.pkgOps != null) {
+ int numPkgs = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
int numFeatures = op.mFeatures.size();
- for (int featureNum = 0; featureNum < numFeatures;
- featureNum++) {
- final FeatureOp featureOp = op.mFeatures.valueAt(
- featureNum);
- if (featureOp.startNesting > 0) {
- final long duration = SystemClock.elapsedRealtime()
- - featureOp.startRealtime;
- // We don't support proxy long running ops (start/stop)
- mHistoricalRegistry.increaseOpAccessDuration(op.op,
- op.uidState.uid, op.packageName, oldPendingState,
- AppOpsManager.OP_FLAG_SELF, duration);
- // Finish the op in the old state
- featureOp.finished(now, duration, oldPendingState,
- AppOpsManager.OP_FLAG_SELF);
- // Start the op in the new state
- featureOp.startRealtime = nowElapsed;
- featureOp.started(now, newState, AppOpsManager.OP_FLAG_SELF);
- }
+ for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+ FeatureOp featureOp = op.mFeatures.valueAt(featureNum);
+
+ featureOp.onUidStateChanged(newState);
}
}
}
@@ -1201,7 +1320,7 @@
resOps = new ArrayList<>();
for (int i = 0; i < opModeCount; i++) {
int code = uidState.opModes.keyAt(i);
- resOps.add(new OpEntry(code, uidState.opModes.get(code), new Pair[0]));
+ resOps.add(new OpEntry(code, uidState.opModes.get(code), Collections.emptyMap()));
}
} else {
for (int j=0; j<ops.length; j++) {
@@ -1210,7 +1329,8 @@
if (resOps == null) {
resOps = new ArrayList<>();
}
- resOps.add(new OpEntry(code, uidState.opModes.get(code), new Pair[0]));
+ resOps.add(new OpEntry(code, uidState.opModes.get(code),
+ Collections.emptyMap()));
}
}
}
@@ -1218,16 +1338,6 @@
}
private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op, long elapsedNow) {
- final int numFeatures = op.mFeatures.size();
-
- for (int i = 0; i < numFeatures; i++) {
- final FeatureOp featureOp = op.mFeatures.valueAt(i);
- if (featureOp.running) {
- featureOp.continuing(elapsedNow - featureOp.startRealtime,
- op.uidState.state, AppOpsManager.OP_FLAG_SELF);
- }
- }
-
return op.createEntryLocked();
}
@@ -1642,6 +1752,13 @@
return;
}
+ // STOPSHIP: Remove this check once we are sure no one is doing it.
+ if (code == OP_COARSE_LOCATION && mode != AppOpsManager.opToDefaultMode(code)) {
+ Slog.wtf(TAG, "Trying to setMode() instead of setUidMode(), " + "code=" + code
+ + ", uid=" + uid + ", packageName=" + packageName + ", mode=" + mode
+ + ", callingUid=" + Binder.getCallingUid(), new RuntimeException());
+ }
+
synchronized (this) {
UidState uidState = getUidStateLocked(uid, false);
Op op = getOpLocked(code, uid, packageName, isPrivileged, true);
@@ -1954,18 +2071,6 @@
}
}
- @Override
- public IBinder getToken(IBinder clientToken) {
- synchronized (this) {
- ClientState cs = mClients.get(clientToken);
- if (cs == null) {
- cs = new ClientState(clientToken);
- mClients.put(clientToken, cs);
- }
- return cs;
- }
- }
-
public CheckOpsDelegate getAppOpsServiceDelegate() {
synchronized (this) {
return mCheckOpsDelegate;
@@ -2206,15 +2311,10 @@
return AppOpsManager.MODE_IGNORED;
}
final UidState uidState = ops.uidState;
- if (featureOp.running) {
- final OpFeatureEntry entry = getOpLocked(ops, code,
- false).createSingleFeatureEntryLocked(featureId).getFeatures().get(
- featureId);
-
- Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
- + " code " + code + " time=" + entry.getLastAccessTime(uidState.state,
- uidState.state, flags) + " duration=" + entry.getLastDuration(
- uidState.state, uidState.state, flags));
+ if (featureOp.isRunning()) {
+ Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ + code + " startTime of in progress event="
+ + featureOp.mInProgressEvents.valueAt(0).startTime);
}
final int switchCode = AppOpsManager.opToSwitch(code);
@@ -2226,8 +2326,7 @@
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
- featureOp.rejected(System.currentTimeMillis(), proxyUid, proxyPackageName,
- proxyFeatureId, uidState.state, flags);
+ featureOp.rejected(uidState.state, flags);
mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
@@ -2236,12 +2335,11 @@
} else {
final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
final int mode = switchOp.evalMode();
- if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode != AppOpsManager.MODE_ALLOWED) {
if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
- featureOp.rejected(System.currentTimeMillis(), proxyUid, proxyPackageName,
- proxyFeatureId, uidState.state, flags);
+ featureOp.rejected(uidState.state, flags);
mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, mode);
@@ -2250,8 +2348,7 @@
}
if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+ " package " + packageName + (featureId == null ? "" : "." + featureId));
- featureOp.accessed(System.currentTimeMillis(), proxyUid, proxyPackageName,
- proxyFeatureId, uidState.state, flags);
+ 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);
@@ -2488,7 +2585,7 @@
}
@Override
- public int startOperation(IBinder token, int code, int uid, String packageName,
+ public int startOperation(IBinder clientId, int code, int uid, String packageName,
String featureId, boolean startIfModeDefault) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
@@ -2496,7 +2593,6 @@
if (resolvedPackageName == null) {
return AppOpsManager.MODE_IGNORED;
}
- ClientState client = (ClientState)token;
boolean isPrivileged;
try {
@@ -2531,10 +2627,7 @@
if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
- // We don't support proxy long running ops (start/stop)
- featureOp.rejected(System.currentTimeMillis(), -1 /*proxyUid*/,
- null /*proxyPackage*/, null, uidState.state,
- AppOpsManager.OP_FLAG_SELF);
+ featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName,
uidState.state, AppOpsManager.OP_FLAG_SELF);
return uidMode;
@@ -2547,10 +2640,7 @@
if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
- // We don't support proxy long running ops (start/stop)
- featureOp.rejected(System.currentTimeMillis(), -1 /*proxyUid*/,
- null /*proxyPackage*/, null, uidState.state,
- AppOpsManager.OP_FLAG_SELF);
+ featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName,
uidState.state, AppOpsManager.OP_FLAG_SELF);
return mode;
@@ -2558,29 +2648,19 @@
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ " package " + resolvedPackageName);
- if (featureOp.startNesting == 0) {
- featureOp.startRealtime = SystemClock.elapsedRealtime();
- // We don't support proxy long running ops (start/stop)
- featureOp.started(System.currentTimeMillis(), uidState.state,
- AppOpsManager.OP_FLAG_SELF);
- mHistoricalRegistry.incrementOpAccessedCount(opCode, uid, packageName,
- uidState.state, AppOpsManager.OP_FLAG_SELF);
- // TODO moltmann: call back when a feature became inactive
- if (uidState.startNesting == 0) {
- scheduleOpActiveChangedIfNeededLocked(code, uid, packageName, true);
- }
+ try {
+ featureOp.started(clientId, uidState.state);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
}
- featureOp.startNesting++;
- uidState.startNesting++;
- client.mStartedOps.add(new Pair<>(op, featureId));
}
return AppOpsManager.MODE_ALLOWED;
}
@Override
- public void finishOperation(IBinder token, int code, int uid, String packageName,
+ public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String featureId) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
@@ -2588,10 +2668,6 @@
if (resolvedPackageName == null) {
return;
}
- if (!(token instanceof ClientState)) {
- return;
- }
- ClientState client = (ClientState) token;
boolean isPrivileged;
try {
@@ -2611,36 +2687,13 @@
return;
}
- if (client.mStartedOps.remove(new Pair<>(op, featureId))) {
- finishOperationLocked(op, featureId, /*finishNested*/ false);
-
- // TODO moltmann: call back when a feature became inactive
- if (op.uidState.startNesting <= 0) {
- scheduleOpActiveChangedIfNeededLocked(code, uid, packageName, false);
- }
-
- return;
+ try {
+ featureOp.finished(clientId);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Operation not started: uid=" + uid + " pkg="
+ + packageName + " op=" + AppOpsManager.opToName(code), e);
}
}
-
- // We finish ops when packages get removed to guarantee no dangling
- // started ops. However, some part of the system may asynchronously
- // finish ops for an already gone package. Hence, finishing an op
- // for a non existing package is fine and we don't log as a wtf.
- final long identity = Binder.clearCallingIdentity();
- try {
- if (LocalServices.getService(PackageManagerInternal.class).getPackageUid(
- resolvedPackageName, 0, UserHandle.getUserId(uid)) < 0) {
- Slog.i(TAG, "Finishing op=" + AppOpsManager.opToName(code)
- + " for non-existing package=" + resolvedPackageName
- + " in uid=" + uid);
- return;
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- Slog.wtf(TAG, "Operation not started: uid=" + uid + " pkg="
- + packageName + " op=" + AppOpsManager.opToName(code));
}
private void scheduleOpActiveChangedIfNeededLocked(int code, int uid, String packageName,
@@ -2761,38 +2814,6 @@
return permInfo.getProtection() == PROTECTION_DANGEROUS;
}
- void finishOperationLocked(@NonNull Op op, @Nullable String featureId, boolean finishNested) {
- final FeatureOp featureOp = op.mFeatures.get(featureId);
- final int opCode = featureOp.parent.op;
- final int uid = featureOp.parent.uidState.uid;
- if (featureOp.startNesting <= 1 || finishNested) {
- if (featureOp.startNesting == 1 || finishNested) {
- // We don't support proxy long running ops (start/stop)
- final long duration = SystemClock.elapsedRealtime() - featureOp.startRealtime;
- featureOp.finished(System.currentTimeMillis(), duration, op.uidState.state,
- AppOpsManager.OP_FLAG_SELF);
- mHistoricalRegistry.increaseOpAccessDuration(opCode, uid, op.packageName,
- op.uidState.state, AppOpsManager.OP_FLAG_SELF, duration);
- } else {
- final OpFeatureEntry entry = op.createSingleFeatureEntryLocked(
- featureId).getFeatures().get(featureId);
- Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg "
- + op.packageName + " code " + opCode + " time="
- + entry.getLastAccessTime(OP_FLAGS_ALL)
- + " duration=" + entry.getLastDuration(MAX_PRIORITY_UID_STATE,
- MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL) + " nesting="
- + featureOp.startNesting);
- }
- if (featureOp.startNesting >= 1) {
- op.uidState.startNesting -= featureOp.startNesting;
- }
- featureOp.startNesting = 0;
- } else {
- featureOp.startNesting--;
- op.uidState.startNesting--;
- }
- }
-
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -3056,7 +3077,7 @@
if (!edit) {
return null;
}
- op = new Op(ops.uidState, ops.packageName, code);
+ op = new Op(ops.uidState, ops.packageName, code, ops.uidState.uid);
ops.put(code, op);
}
if (edit) {
@@ -3200,7 +3221,7 @@
final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
if (op != null && op.mode != AppOpsManager.opToDefaultMode(op.op)) {
final Op copy = new Op(op.uidState, op.packageName,
- AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
copy.mode = op.mode;
ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
changed = true;
@@ -3328,25 +3349,22 @@
final FeatureOp featureOp = parent.getOrCreateFeature(parent, feature);
final long key = XmlUtils.readLongAttribute(parser, "n");
-
- final int flags = AppOpsManager.extractFlagsFromKey(key);
- final int state = AppOpsManager.extractUidStateFromKey(key);
+ final int uidState = extractUidStateFromKey(key);
+ final int opFlags = extractFlagsFromKey(key);
final long accessTime = XmlUtils.readLongAttribute(parser, "t", 0);
final long rejectTime = XmlUtils.readLongAttribute(parser, "r", 0);
- final long accessDuration = XmlUtils.readLongAttribute(parser, "d", 0);
+ final long accessDuration = XmlUtils.readLongAttribute(parser, "d", -1);
final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
- final int proxyUid = XmlUtils.readIntAttribute(parser, "pu", 0);
+ final int proxyUid = XmlUtils.readIntAttribute(parser, "pu", Process.INVALID_UID);
final String proxyFeatureId = XmlUtils.readStringAttribute(parser, "pc");
if (accessTime > 0) {
- featureOp.accessed(accessTime, proxyUid, proxyPkg, proxyFeatureId, state, flags);
+ featureOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg, proxyFeatureId,
+ uidState, opFlags);
}
if (rejectTime > 0) {
- featureOp.rejected(rejectTime, proxyUid, proxyPkg, proxyFeatureId, state, flags);
- }
- if (accessDuration > 0) {
- featureOp.running(accessTime, accessDuration, state, flags);
+ featureOp.rejected(rejectTime, uidState, opFlags);
}
}
@@ -3354,7 +3372,8 @@
@NonNull String pkgName, boolean isPrivileged) throws NumberFormatException,
XmlPullParserException, IOException {
Op op = new Op(uidState, pkgName,
- Integer.parseInt(parser.getAttributeValue(null, "n")));
+ Integer.parseInt(parser.getAttributeValue(null, "n")),
+ uidState.uid);
final int mode = XmlUtils.readIntAttribute(parser, "m",
AppOpsManager.opToDefaultMode(op.op));
@@ -3488,35 +3507,39 @@
final OpFeatureEntry feature = op.getFeatures().get(
featureId);
- final LongSparseArray keys = feature.collectKeys();
- if (keys == null || keys.size() <= 0) {
- continue;
- }
- final int keyCount = keys.size();
+ final ArraySet<Long> keys = feature.collectKeys();
+ final int keyCount = keys.size();
for (int k = 0; k < keyCount; k++) {
- final long key = keys.keyAt(k);
+ final long key = keys.valueAt(k);
final int uidState = AppOpsManager.extractUidStateFromKey(key);
final int flags = AppOpsManager.extractFlagsFromKey(key);
- final long accessTime = feature.getLastAccessTime(
- uidState, uidState, flags);
- final long rejectTime = feature.getLastRejectTime(
- uidState, uidState, flags);
- final long accessDuration = feature.getLastDuration(
- uidState, uidState, flags);
- final String proxyPkg = feature.getProxyPackageName(uidState,
- flags);
- final String proxyFeatureId = feature.getProxyFeatureId(
+ final long accessTime = feature.getLastAccessTime(uidState,
uidState, flags);
- final int proxyUid = feature.getProxyUid(uidState, flags);
+ final long rejectTime = feature.getLastRejectTime(uidState,
+ uidState, flags);
+ final long accessDuration = feature.getLastDuration(uidState,
+ uidState, flags);
+ // Proxy information for rejections is not backed up
+ final OpEventProxyInfo proxy = feature.getLastProxyInfo(
+ uidState, uidState, flags);
if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
- && proxyPkg == null && proxyUid < 0) {
+ && proxy == null) {
continue;
}
+ String proxyPkg = null;
+ String proxyFeatureId = null;
+ int proxyUid = Process.INVALID_UID;
+ if (proxy != null) {
+ proxyPkg = proxy.getPackageName();
+ proxyFeatureId = proxy.getFeatureId();
+ proxyUid = proxy.getUid();
+ }
+
out.startTag(null, "st");
if (featureId != null) {
out.attribute(null, "id", featureId);
@@ -3583,10 +3606,7 @@
Shell(IAppOpsService iface, AppOpsService internal) {
mInterface = iface;
mInternal = internal;
- try {
- mToken = mInterface.getToken(sBinder);
- } catch (RemoteException e) {
- }
+ mToken = AppOpsManager.getClientId();
}
@Override
@@ -3881,42 +3901,48 @@
pw.print(": ");
pw.print(AppOpsManager.modeToName(ent.getMode()));
if (shell.featureId == null) {
- if (ent.getTime() != 0) {
+ if (ent.getLastAccessTime(OP_FLAGS_ALL) != -1) {
pw.print("; time=");
- TimeUtils.formatDuration(now - ent.getTime(), pw);
+ TimeUtils.formatDuration(
+ now - ent.getLastAccessTime(OP_FLAGS_ALL), pw);
pw.print(" ago");
}
- if (ent.getRejectTime() != 0) {
+ if (ent.getLastRejectTime(OP_FLAGS_ALL) != -1) {
pw.print("; rejectTime=");
- TimeUtils.formatDuration(now - ent.getRejectTime(), pw);
+ TimeUtils.formatDuration(
+ now - ent.getLastRejectTime(OP_FLAGS_ALL), pw);
pw.print(" ago");
}
- if (ent.getDuration() == -1) {
+ if (ent.isRunning()) {
pw.print(" (running)");
- } else if (ent.getDuration() != 0) {
+ } else if (ent.getLastDuration(OP_FLAGS_ALL) != -1) {
pw.print("; duration=");
- TimeUtils.formatDuration(ent.getDuration(), pw);
+ TimeUtils.formatDuration(ent.getLastDuration(OP_FLAGS_ALL), pw);
}
} else {
final OpFeatureEntry featureEnt = ent.getFeatures().get(
shell.featureId);
if (featureEnt != null) {
- if (featureEnt.getTime() != 0) {
+ if (featureEnt.getLastAccessTime(OP_FLAGS_ALL) != -1) {
pw.print("; time=");
- TimeUtils.formatDuration(now - featureEnt.getTime(), pw);
+ TimeUtils.formatDuration(now - featureEnt.getLastAccessTime(
+ OP_FLAGS_ALL), pw);
pw.print(" ago");
}
- if (featureEnt.getRejectTime() != 0) {
+ if (featureEnt.getLastRejectTime(OP_FLAGS_ALL) != -1) {
pw.print("; rejectTime=");
- TimeUtils.formatDuration(now - featureEnt.getRejectTime(),
+ TimeUtils.formatDuration(
+ now - featureEnt.getLastRejectTime(OP_FLAGS_ALL),
pw);
pw.print(" ago");
}
- if (featureEnt.getDuration() == -1) {
+ if (featureEnt.isRunning()) {
pw.print(" (running)");
- } else if (featureEnt.getDuration() != 0) {
+ } else if (featureEnt.getLastDuration(OP_FLAGS_ALL)
+ != -1) {
pw.print("; duration=");
- TimeUtils.formatDuration(featureEnt.getDuration(), pw);
+ TimeUtils.formatDuration(
+ featureEnt.getLastDuration(OP_FLAGS_ALL), pw);
}
}
}
@@ -4087,27 +4113,28 @@
final OpFeatureEntry entry = op.createSingleFeatureEntryLocked(
featureId).getFeatures().get(featureId);
- final LongSparseArray keys = entry.collectKeys();
- if (keys == null || keys.size() <= 0) {
- return;
- }
+ final ArraySet<Long> keys = entry.collectKeys();
final int keyCount = keys.size();
for (int k = 0; k < keyCount; k++) {
- final long key = keys.keyAt(k);
+ final long key = keys.valueAt(k);
final int uidState = AppOpsManager.extractUidStateFromKey(key);
final int flags = AppOpsManager.extractFlagsFromKey(key);
- final long accessTime = entry.getLastAccessTime(
- uidState, uidState, flags);
- final long rejectTime = entry.getLastRejectTime(
- uidState, uidState, flags);
- final long accessDuration = entry.getLastDuration(
- uidState, uidState, flags);
- final String proxyPkg = entry.getProxyPackageName(uidState, flags);
- final String proxyFeatureId = entry.getProxyFeatureId(uidState, flags);
- final int proxyUid = entry.getProxyUid(uidState, flags);
+ final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
+ final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
+ final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
+ final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
+
+ String proxyPkg = null;
+ String proxyFeatureId = null;
+ int proxyUid = Process.INVALID_UID;
+ if (proxy != null) {
+ proxyPkg = proxy.getPackageName();
+ proxyFeatureId = proxy.getFeatureId();
+ proxyUid = proxy.getUid();
+ }
if (accessTime > 0) {
pw.print(prefix);
@@ -4160,14 +4187,25 @@
}
final FeatureOp featureOp = op.mFeatures.get(featureId);
- if (featureOp.running) {
+ if (featureOp.isRunning()) {
+ long earliestStartTime = Long.MAX_VALUE;
+ long maxNumStarts = 0;
+ int numInProgressEvents = featureOp.mInProgressEvents.size();
+ for (int i = 0; i < numInProgressEvents; i++) {
+ InProgressStartOpEvent event = featureOp.mInProgressEvents.valueAt(i);
+
+ earliestStartTime = Math.min(earliestStartTime, event.startTime);
+ maxNumStarts = Math.max(maxNumStarts, event.numUnfinishedStarts);
+ }
+
pw.print(prefix + "Running start at: ");
- TimeUtils.formatDuration(nowElapsed - featureOp.startRealtime, pw);
+ TimeUtils.formatDuration(nowElapsed - earliestStartTime, pw);
pw.println();
- }
- if (featureOp.startNesting != 0) {
- pw.print(prefix + "startNesting=");
- pw.println(featureOp.startNesting);
+
+ if (maxNumStarts > 1) {
+ pw.print(prefix + "startNesting=");
+ pw.println(maxNumStarts);
+ }
}
}
@@ -4415,44 +4453,6 @@
pw.println(cb);
}
}
- if (mClients.size() > 0 && dumpMode < 0 && !dumpWatchers && !dumpHistory) {
- needSep = true;
- boolean printedHeader = false;
- for (int i=0; i<mClients.size(); i++) {
- boolean printedClient = false;
- ClientState cs = mClients.valueAt(i);
- if (cs.mStartedOps.size() > 0) {
- boolean printedStarted = false;
- for (int j=0; j<cs.mStartedOps.size(); j++) {
- final Pair<Op, String> startedOp = cs.mStartedOps.get(j);
- final Op op = startedOp.first;
- if (dumpOp >= 0 && op.op != dumpOp) {
- continue;
- }
- if (dumpPackage != null && !dumpPackage.equals(op.packageName)) {
- continue;
- }
- if (!printedHeader) {
- pw.println(" Clients:");
- printedHeader = true;
- }
- if (!printedClient) {
- pw.print(" "); pw.print(mClients.keyAt(i)); pw.println(":");
- pw.print(" "); pw.println(cs);
- printedClient = true;
- }
- if (!printedStarted) {
- pw.println(" Started ops:");
- printedStarted = true;
- }
- pw.print(" "); pw.print("uid="); pw.print(op.uidState.uid);
- pw.print(" pkg="); pw.print(op.packageName);
- pw.print(" featureId="); pw.print(startedOp.second);
- pw.print(" op="); pw.println(AppOpsManager.opToName(op.op));
- }
- }
- }
- }
if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
&& dumpPackage != null && dumpMode < 0 && !dumpWatchers && !dumpWatchers) {
needSep = mAudioRestrictionManager.dump(pw) | needSep ;
@@ -4528,10 +4528,6 @@
TimeUtils.formatDuration(uidState.pendingStateCommitTime, nowElapsed, pw);
pw.println();
}
- if (uidState.startNesting != 0) {
- pw.print(" startNesting=");
- pw.println(uidState.startNesting);
- }
if (uidState.foregroundOps != null && (dumpMode < 0
|| dumpMode == AppOpsManager.MODE_FOREGROUND)) {
pw.println(" foregroundOps:");
@@ -4805,17 +4801,18 @@
}
// TODO moltmann: Allow to check for feature op activeness
synchronized (AppOpsService.this) {
- for (int i = mClients.size() - 1; i >= 0; i--) {
- final ClientState client = mClients.valueAt(i);
- for (int j = client.mStartedOps.size() - 1; j >= 0; j--) {
- final Pair<Op, String> startedOp = client.mStartedOps.get(j);
- if (startedOp.first.op == code && startedOp.first.uidState.uid == uid) {
- return true;
- }
- }
+ Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false, false);
+ if (pkgOps == null) {
+ return false;
}
+
+ Op op = pkgOps.get(code);
+ if (op == null) {
+ return false;
+ }
+
+ return op.isRunning();
}
- return false;
}
@Override
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 232bc08e..fc67e24 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -77,21 +77,28 @@
private static final String LOG_TAG = "AttentionManagerService";
private static final boolean DEBUG = false;
- /** Default value in absence of {@link DeviceConfig} override. */
- private static final boolean DEFAULT_SERVICE_ENABLED = true;
-
/** Service will unbind if connection is not used for that amount of time. */
private static final long CONNECTION_TTL_MILLIS = 60_000;
- /** If the check attention called within that period - cached value will be returned. */
- private static final long STALE_AFTER_MILLIS = 5_000;
+ /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
+ private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+ /** Default value in absence of {@link DeviceConfig} override. */
+ private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
+ /**
+ * DeviceConfig flag name, describes how much time we consider a result fresh; if the check
+ * attention called within that period - cached value will be returned.
+ */
+ @VisibleForTesting static final String KEY_STALE_AFTER_MILLIS = "stale_after_millis";
+
+ /** Default value in absence of {@link DeviceConfig} override. */
+ @VisibleForTesting static final long DEFAULT_STALE_AFTER_MILLIS = 1_000;
/** The size of the buffer that stores recent attention check results. */
@VisibleForTesting
protected static final int ATTENTION_CACHE_BUFFER_SIZE = 5;
- /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
- private static final String SERVICE_ENABLED = "service_enabled";
private static String sTestAttentionServicePackage;
private final Context mContext;
private final PowerManager mPowerManager;
@@ -160,11 +167,29 @@
@VisibleForTesting
protected boolean isServiceEnabled() {
- return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, SERVICE_ENABLED,
+ return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_SERVICE_ENABLED,
DEFAULT_SERVICE_ENABLED);
}
/**
+ * How much time we consider a result fresh; if the check attention called within that period -
+ * cached value will be returned.
+ */
+ @VisibleForTesting
+ protected long getStaleAfterMillis() {
+ final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS,
+ DEFAULT_STALE_AFTER_MILLIS);
+
+ if (millis < 0 || millis > 10_000) {
+ Slog.w(LOG_TAG, "Bad flag value supplied for: " + KEY_STALE_AFTER_MILLIS);
+ return DEFAULT_STALE_AFTER_MILLIS;
+ }
+
+ return millis;
+ }
+
+ /**
* Checks whether user attention is at the screen and calls in the provided callback.
*
* Calling this multiple times quickly in a row will result in either a) returning a cached
@@ -199,7 +224,7 @@
// throttle frequent requests
final AttentionCheckCache cache = userState.mAttentionCheckCacheBuffer == null ? null
: userState.mAttentionCheckCacheBuffer.getLast();
- if (cache != null && now < cache.mLastComputed + STALE_AFTER_MILLIS) {
+ if (cache != null && now < cache.mLastComputed + getStaleAfterMillis()) {
callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
return true;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 6010b1dc..60f420e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -24,6 +24,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.media.AudioDeviceAddress;
import android.media.AudioManager;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
@@ -400,6 +401,15 @@
}
}
+ /*package*/ int setPreferredDeviceForStrategySync(int strategy,
+ @NonNull AudioDeviceAddress device) {
+ return mDeviceInventory.setPreferredDeviceForStrategySync(strategy, device);
+ }
+
+ /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
+ return mDeviceInventory.removePreferredDeviceForStrategySync(strategy);
+ }
+
//---------------------------------------------------------------------
// Communication with (to) AudioService
//TODO check whether the AudioService methods are candidates to move here
@@ -533,6 +543,15 @@
sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
}
+ /*package*/ void postSaveSetPreferredDeviceForStrategy(int strategy, AudioDeviceAddress device)
+ {
+ sendILMsgNoDelay(MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy, device);
+ }
+
+ /*package*/ void postSaveRemovePreferredDeviceForStrategy(int strategy) {
+ sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY, SENDMSG_QUEUE, strategy);
+ }
+
//---------------------------------------------------------------------
// Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
// only call from a "handle"* method or "on"* method
@@ -581,19 +600,11 @@
sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
}
- /*package*/ void cancelA2dpDockTimeout() {
- mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
- }
-
/*package*/ void postA2dpActiveDeviceChange(
@NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
}
- /*package*/ boolean hasScheduledA2dpDockTimeout() {
- return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
- }
-
// must be called synchronized on mConnectedDevices
/*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
@@ -602,8 +613,8 @@
new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
}
- /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
- sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
+ /*package*/ void setA2dpTimeout(String address, int a2dpCodec, int delayMs) {
+ sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
}
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
@@ -631,6 +642,7 @@
} else {
pw.println("Message handler is null");
}
+ mDeviceInventory.dump(pw, prefix);
}
//---------------------------------------------------------------------
@@ -761,7 +773,7 @@
}
}
break;
- case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_IL_BTA2DP_TIMEOUT:
// msg.obj == address of BTA2DP device
synchronized (mDeviceStateLock) {
mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
@@ -890,6 +902,15 @@
info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
}
} break;
+ case MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY: {
+ final int strategy = msg.arg1;
+ final AudioDeviceAddress device = (AudioDeviceAddress) msg.obj;
+ mDeviceInventory.onSaveSetPreferredDevice(strategy, device);
+ } break;
+ case MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY: {
+ final int strategy = msg.arg1;
+ mDeviceInventory.onSaveRemovePreferredDevice(strategy);
+ } break;
default:
Log.wtf(TAG, "Invalid message " + msg.what);
}
@@ -916,7 +937,7 @@
private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
- private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10;
+ private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
private static final int MSG_REPORT_NEW_ROUTES = 13;
@@ -941,6 +962,8 @@
private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31;
// a ScoClient died in BtHelper
private static final int MSG_L_SCOCLIENT_DIED = 32;
+ private static final int MSG_IL_SAVE_PREF_DEVICE_FOR_STRATEGY = 33;
+ private static final int MSG_I_SAVE_REMOVE_PREF_DEVICE_FOR_STRATEGY = 34;
private static boolean isMessageHandledUnderWakelock(int msgId) {
@@ -950,7 +973,7 @@
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
- case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_IL_BTA2DP_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
case MSG_TOGGLE_HDMI:
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
@@ -1040,7 +1063,7 @@
case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
- case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+ case MSG_IL_BTA2DP_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
if (sLastDeviceConnectMsgTime >= time) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 37add3d..00fc6d0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -23,6 +23,7 @@
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
+import android.media.AudioDeviceAddress;
import android.media.AudioDevicePort;
import android.media.AudioFormat;
import android.media.AudioManager;
@@ -35,6 +36,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -42,6 +44,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -56,14 +59,25 @@
private static final String TAG = "AS.AudioDeviceInventory";
- // Actual list of connected devices
+ // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
+ private final Object mDevicesLock = new Object();
+
+ // List of connected devices
// Key for map created from DeviceInfo.makeDeviceListKey()
+ @GuardedBy("mDevicesLock")
private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>();
- private @NonNull AudioDeviceBroker mDeviceBroker;
+ // List of devices actually connected to AudioPolicy (through AudioSystem), only one
+ // by device type, which is used as the key, value is the DeviceInfo generated key.
+ // For the moment only for A2DP sink devices.
+ // TODO: extend to all device types
+ @GuardedBy("mDevicesLock")
+ private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>();
- // cache of the address of the last dock the device was connected to
- private String mDockAddress;
+ // List of preferred devices for strategies
+ private final ArrayMap<Integer, AudioDeviceAddress> mPreferredDevices = new ArrayMap<>();
+
+ private @NonNull AudioDeviceBroker mDeviceBroker;
// Monitoring of audio routes. Protected by mAudioRoutes.
final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
@@ -91,30 +105,35 @@
*/
private static class DeviceInfo {
final int mDeviceType;
- final String mDeviceName;
- final String mDeviceAddress;
+ final @NonNull String mDeviceName;
+ final @NonNull String mDeviceAddress;
int mDeviceCodecFormat;
DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
mDeviceType = deviceType;
- mDeviceName = deviceName;
- mDeviceAddress = deviceAddress;
+ mDeviceName = deviceName == null ? "" : deviceName;
+ mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
mDeviceCodecFormat = deviceCodecFormat;
}
@Override
public String toString() {
return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
- + " name:" + mDeviceName
+ + " (" + AudioSystem.getDeviceName(mDeviceType)
+ + ") name:" + mDeviceName
+ " addr:" + mDeviceAddress
+ " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
}
+ @NonNull String getKey() {
+ return makeDeviceListKey(mDeviceType, mDeviceAddress);
+ }
+
/**
* Generate a unique key for the mConnectedDevices List by composing the device "type"
* and the "address" associated with a specific instance of that device type
*/
- private static String makeDeviceListKey(int device, String deviceAddress) {
+ @NonNull private static String makeDeviceListKey(int device, String deviceAddress) {
return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
}
}
@@ -140,14 +159,31 @@
}
//------------------------------------------------------------
+ /*package*/ void dump(PrintWriter pw, String prefix) {
+ pw.println("\n" + prefix + "Preferred devices for strategy:");
+ mPreferredDevices.forEach((strategy, device) -> {
+ pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); });
+ pw.println("\n" + prefix + "Connected devices:");
+ mConnectedDevices.forEach((key, deviceInfo) -> {
+ pw.println(" " + prefix + deviceInfo.toString()); });
+ pw.println("\n" + prefix + "APM Connected device (A2DP sink only):");
+ mApmConnectedDevices.forEach((keyType, valueAddress) -> {
+ pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType)
+ + " (" + AudioSystem.getDeviceName(keyType)
+ + ") addr:" + valueAddress); });
+ }
+
+ //------------------------------------------------------------
// Message handling from AudioDeviceBroker
/**
* Restore previously connected devices. Use in case of audio server crash
* (see AudioService.onAudioServerDied() method)
*/
+ // Always executed on AudioDeviceBroker message queue
/*package*/ void onRestoreDevices() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
+ //TODO iterate on mApmConnectedDevices instead once it handles all device types
for (DeviceInfo di : mConnectedDevices.values()) {
AudioSystem.setDeviceConnectionState(
di.mDeviceType,
@@ -157,6 +193,10 @@
di.mDeviceCodecFormat);
}
}
+ synchronized (mPreferredDevices) {
+ mPreferredDevices.forEach((strategy, device) -> {
+ AudioSystem.setPreferredDeviceForStrategy(strategy, device); });
+ }
}
// only public for mocking/spying
@@ -168,9 +208,12 @@
int a2dpVolume = btInfo.getVolume();
if (AudioService.DEBUG_DEVICES) {
Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
- + state + " is dock=" + btDevice.isBluetoothDock() + " vol=" + a2dpVolume);
+ + state + " vol=" + a2dpVolume);
}
String address = btDevice.getAddress();
+ if (address == null) {
+ address = "";
+ }
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = "";
}
@@ -182,7 +225,7 @@
+ " codec=" + a2dpCodec
+ " vol=" + a2dpVolume));
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
btDevice.getAddress());
final DeviceInfo di = mConnectedDevices.get(key);
@@ -196,33 +239,10 @@
mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice);
}
} else {
- if (btDevice.isBluetoothDock()) {
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- // introduction of a delay for transient disconnections of docks when
- // power is rapidly turned off/on, this message will be canceled if
- // we reconnect the dock under a preset delay
- makeA2dpDeviceUnavailableLater(address,
- AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
- // the next time isConnected is evaluated, it will be false for the dock
- }
- } else {
- makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
- }
+ makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
}
- } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
- if (btDevice.isBluetoothDock()) {
- // this could be a reconnection after a transient disconnection
- mDeviceBroker.cancelA2dpDockTimeout();
- mDockAddress = address;
- } else {
- // this could be a connection of another A2DP device before the timeout of
- // a dock: cancel the dock timeout, and make the dock unavailable now
- if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
- mDeviceBroker.cancelA2dpDockTimeout();
- makeA2dpDeviceUnavailableNow(mDockAddress,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
- }
- }
+ } else if (state == BluetoothProfile.STATE_CONNECTED) {
+ // device is not already connected
if (a2dpVolume != -1) {
mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
// convert index to internal representation in VolumeStreamState
@@ -247,7 +267,7 @@
address = "";
}
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final String key = DeviceInfo.makeDeviceListKey(
AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
final DeviceInfo di = mConnectedDevices.get(key);
@@ -270,7 +290,7 @@
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
"onSetHearingAidConnectionState addr=" + address));
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
btDevice.getAddress());
final DeviceInfo di = mConnectedDevices.get(key);
@@ -306,7 +326,7 @@
"onBluetoothA2dpActiveDeviceChange addr=" + address
+ " event=" + BtHelper.a2dpDeviceEventToString(event)));
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
"A2dp config change ignored (scheduled connection change)"));
@@ -335,8 +355,15 @@
mConnectedDevices.replace(key, di);
}
}
- if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
- BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
+ final int res = AudioSystem.handleDeviceConfigChange(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+ BtHelper.getName(btDevice), a2dpCodec);
+
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM handleDeviceConfigChange failed for A2DP device addr="
+ + address + " codec=" + a2dpCodec).printLog(TAG));
+
int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
// force A2DP device disconnection in case of error so that AudioService state is
// consistent with audio policy manager state
@@ -344,12 +371,16 @@
btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
false /* suppressNoisyIntent */, musicDevice,
-1 /* a2dpVolume */);
+ } else {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM handleDeviceConfigChange success for A2DP device addr="
+ + address + " codec=" + a2dpCodec).printLog(TAG));
}
}
}
/*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
makeA2dpDeviceUnavailableNow(address, a2dpCodec);
}
}
@@ -386,7 +417,7 @@
AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
&& DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
mDeviceBroker.setBluetoothA2dpOnInt(true,
@@ -414,7 +445,7 @@
}
/*package*/ void onToggleHdmi() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
// Is HDMI connected?
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
final DeviceInfo di = mConnectedDevices.get(key);
@@ -431,9 +462,41 @@
"android"); // reconnect
}
}
+
+ /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAddress device) {
+ mPreferredDevices.put(strategy, device);
+ }
+
+ /*package*/ void onSaveRemovePreferredDevice(int strategy) {
+ mPreferredDevices.remove(strategy);
+ }
+
//------------------------------------------------------------
//
+ /*package*/ int setPreferredDeviceForStrategySync(int strategy,
+ @NonNull AudioDeviceAddress device) {
+ final long identity = Binder.clearCallingIdentity();
+ final int status = AudioSystem.setPreferredDeviceForStrategy(strategy, device);
+ Binder.restoreCallingIdentity(identity);
+
+ if (status == AudioSystem.SUCCESS) {
+ mDeviceBroker.postSaveSetPreferredDeviceForStrategy(strategy, device);
+ }
+ return status;
+ }
+
+ /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
+ final long identity = Binder.clearCallingIdentity();
+ final int status = AudioSystem.removePreferredDeviceForStrategy(strategy);
+ Binder.restoreCallingIdentity(identity);
+
+ if (status == AudioSystem.SUCCESS) {
+ mDeviceBroker.postSaveRemovePreferredDeviceForStrategy(strategy);
+ }
+ return status;
+ }
+
/**
* Implements the communication with AudioSystem to (dis)connect a device in the native layers
* @param connect true if connection
@@ -449,7 +512,7 @@
+ Integer.toHexString(device) + " address:" + address
+ " name:" + deviceName + ")");
}
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
if (AudioService.DEBUG_DEVICES) {
Slog.i(TAG, "deviceKey:" + deviceKey);
@@ -488,7 +551,7 @@
/*package*/ void disconnectA2dp() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final ArraySet<String> toRemove = new ArraySet<>();
// Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
mConnectedDevices.values().forEach(deviceInfo -> {
@@ -508,7 +571,7 @@
}
/*package*/ void disconnectA2dpSink() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final ArraySet<String> toRemove = new ArraySet<>();
// Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
mConnectedDevices.values().forEach(deviceInfo -> {
@@ -521,7 +584,7 @@
}
/*package*/ void disconnectHearingAid() {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
final ArraySet<String> toRemove = new ArraySet<>();
// Disconnect ALL DEVICE_OUT_HEARING_AID devices
mConnectedDevices.values().forEach(deviceInfo -> {
@@ -545,7 +608,7 @@
// from AudioSystem
/*package*/ int checkSendBecomingNoisyIntent(int device,
@AudioService.ConnectionState int state, int musicDevice) {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
}
}
@@ -572,7 +635,7 @@
if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
throw new IllegalArgumentException("invalid profile " + profile);
}
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
@AudioService.ConnectionState int asState =
(state == BluetoothA2dp.STATE_CONNECTED)
@@ -612,7 +675,7 @@
/*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
String address, String name, String caller) {
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
mDeviceBroker.postSetWiredDeviceConnectionState(
new WiredDeviceConnectionState(type, state, address, name, caller),
@@ -625,7 +688,7 @@
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
boolean suppressNoisyIntent, int musicDevice) {
int delay;
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
if (!suppressNoisyIntent) {
int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
@@ -642,42 +705,81 @@
//-------------------------------------------------------------------
// Internal utilities
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
int a2dpCodec) {
// enable A2DP before notifying A2DP connection to avoid unnecessary processing in
// audio policy manager
mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ // at this point there could be another A2DP device already connected in APM, but it
+ // doesn't matter as this new one will overwrite the previous one
+ final int res = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
+
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM failed to make available A2DP device addr=" + address
+ + " error=" + res).printLog(TAG));
+ // TODO: connection failed, stop here
+ // TODO: return;
+ } else {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2DP device addr=" + address + " now available").printLog(TAG));
+ }
+
// Reset A2DP suspend state each time a new sink is connected
AudioSystem.setParameters("A2dpSuspended=false");
- mConnectedDevices.put(
- DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
- new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, a2dpCodec));
+
+ final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
+ address, a2dpCodec);
+ final String diKey = di.getKey();
+ mConnectedDevices.put(diKey, di);
+ // on a connection always overwrite the device seen by AudioPolicy, see comment above when
+ // calling AudioSystem
+ mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey);
+
mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
setCurrentAudioRouteNameIfPossible(name);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
if (address == null) {
return;
}
+ final String deviceToRemoveKey =
+ DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+
+ mConnectedDevices.remove(deviceToRemoveKey);
+ if (!deviceToRemoveKey
+ .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
+ // removing A2DP device not currently used by AudioPolicy, log but don't act on it
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
+ return;
+ }
+
+ // device to remove was visible by APM, update APM
mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ final int res = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
- mConnectedDevices.remove(
- DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM failed to make unavailable A2DP device addr=" + address
+ + " error=" + res).printLog(TAG));
+ // TODO: failed to disconnect, stop here
+ // TODO: return;
+ } else {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
+ }
+ mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
// Remove A2DP routes as well
setCurrentAudioRouteNameIfPossible(null);
- if (mDockAddress == address) {
- mDockAddress = null;
- }
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
// prevent any activity on the A2DP audio output to avoid unwanted
// reconnection of the sink.
@@ -691,11 +793,11 @@
// the device will be made unavailable later, so consider it disconnected right away
mConnectedDevices.remove(deviceKey);
// send the delayed message to make the device unavailable later
- mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
+ mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpSrcAvailable(String address) {
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
@@ -706,7 +808,7 @@
address, AudioSystem.AUDIO_FORMAT_DEFAULT));
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeA2dpSrcUnavailable(String address) {
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
@@ -715,7 +817,7 @@
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeHearingAidDeviceAvailable(
String address, String name, int streamType, String eventSource) {
final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
@@ -735,7 +837,7 @@
setCurrentAudioRouteNameIfPossible(name);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void makeHearingAidDeviceUnavailable(String address) {
AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
@@ -746,7 +848,7 @@
setCurrentAudioRouteNameIfPossible(null);
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private void setCurrentAudioRouteNameIfPossible(String name) {
synchronized (mCurAudioRoutes) {
if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
@@ -759,7 +861,7 @@
}
}
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private boolean isCurrentDeviceConnected() {
return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
@@ -787,7 +889,7 @@
// must be called before removing the device from mConnectedDevices
// musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
// from AudioSystem
- @GuardedBy("mConnectedDevices")
+ @GuardedBy("mDevicesLock")
private int checkSendBecomingNoisyIntentInt(int device,
@AudioService.ConnectionState int state, int musicDevice) {
if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
@@ -995,7 +1097,7 @@
public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
device.getAddress());
- synchronized (mConnectedDevices) {
+ synchronized (mDevicesLock) {
return (mConnectedDevices.get(key) != null);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 4bf1de6..21cecc2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -63,6 +63,8 @@
import android.hardware.usb.UsbManager;
import android.hidl.manager.V1_0.IServiceManager;
import android.media.AudioAttributes;
+import android.media.AudioDeviceAddress;
+import android.media.AudioDeviceInfo;
import android.media.AudioFocusInfo;
import android.media.AudioFocusRequest;
import android.media.AudioFormat;
@@ -308,7 +310,8 @@
7, // STREAM_SYSTEM_ENFORCED
15, // STREAM_DTMF
15, // STREAM_TTS
- 15 // STREAM_ACCESSIBILITY
+ 15, // STREAM_ACCESSIBILITY
+ 15 // STREAM_ASSISTANT
};
/** Minimum volume index values for audio streams */
@@ -323,7 +326,8 @@
0, // STREAM_SYSTEM_ENFORCED
0, // STREAM_DTMF
0, // STREAM_TTS
- 1 // STREAM_ACCESSIBILITY
+ 1, // STREAM_ACCESSIBILITY
+ 0 // STREAM_ASSISTANT
};
/* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings
@@ -346,7 +350,8 @@
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_RING, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
- AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
+ AudioSystem.STREAM_MUSIC, // STREAM_ACCESSIBILITY
+ AudioSystem.STREAM_MUSIC // STREAM_ASSISTANT
};
private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
AudioSystem.STREAM_MUSIC, // STREAM_VOICE_CALL
@@ -359,7 +364,8 @@
AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_MUSIC, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
- AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
+ AudioSystem.STREAM_MUSIC, // STREAM_ACCESSIBILITY
+ AudioSystem.STREAM_MUSIC // STREAM_ASSISTANT
};
private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL
@@ -372,7 +378,8 @@
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED
AudioSystem.STREAM_RING, // STREAM_DTMF
AudioSystem.STREAM_MUSIC, // STREAM_TTS
- AudioSystem.STREAM_MUSIC // STREAM_ACCESSIBILITY
+ AudioSystem.STREAM_MUSIC, // STREAM_ACCESSIBILITY
+ AudioSystem.STREAM_MUSIC // STREAM_ASSISTANT
};
protected static int[] mStreamVolumeAlias;
@@ -392,6 +399,7 @@
AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_DTMF
AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_TTS
AppOpsManager.OP_AUDIO_ACCESSIBILITY_VOLUME, // STREAM_ACCESSIBILITY
+ AppOpsManager.OP_AUDIO_MEDIA_VOLUME // STREAM_ASSISTANT
};
private final boolean mUseFixedVolume;
@@ -1251,6 +1259,9 @@
int dtmfStreamAlias;
final int a11yStreamAlias = sIndependentA11yVolume ?
AudioSystem.STREAM_ACCESSIBILITY : AudioSystem.STREAM_MUSIC;
+ final int assistantStreamAlias = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_useAssistantVolume) ?
+ AudioSystem.STREAM_ASSISTANT : AudioSystem.STREAM_MUSIC;
if (mIsSingleVolume) {
mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
@@ -1280,6 +1291,7 @@
mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias;
+ mStreamVolumeAlias[AudioSystem.STREAM_ASSISTANT] = assistantStreamAlias;
if (updateVolumes && mStreamStates != null) {
updateDefaultVolumes();
@@ -1633,6 +1645,60 @@
///////////////////////////////////////////////////////////////////////////
// IPC methods
///////////////////////////////////////////////////////////////////////////
+ /** @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceInfo) */
+ public int setPreferredDeviceForStrategy(int strategy, AudioDeviceAddress device) {
+ if (device == null) {
+ return AudioSystem.ERROR;
+ }
+ enforceModifyAudioRoutingPermission();
+ final String logString = String.format(
+ "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
+ Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
+ sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
+ if (device.getRole() == AudioDeviceAddress.ROLE_INPUT) {
+ Log.e(TAG, "Unsupported input routing in " + logString);
+ return AudioSystem.ERROR;
+ }
+
+ final int status = mDeviceBroker.setPreferredDeviceForStrategySync(strategy, device);
+ if (status != AudioSystem.SUCCESS) {
+ Log.e(TAG, String.format("Error %d in %s)", status, logString));
+ }
+
+ return status;
+ }
+
+ /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */
+ public int removePreferredDeviceForStrategy(int strategy) {
+ enforceModifyAudioRoutingPermission();
+ final String logString =
+ String.format("removePreferredDeviceForStrategy strat:%d", strategy);
+ sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG));
+
+ final int status = mDeviceBroker.removePreferredDeviceForStrategySync(strategy);
+ if (status != AudioSystem.SUCCESS) {
+ Log.e(TAG, String.format("Error %d in %s)", status, logString));
+ }
+ return status;
+ }
+
+ /** @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy) */
+ public AudioDeviceAddress getPreferredDeviceForStrategy(int strategy) {
+ enforceModifyAudioRoutingPermission();
+ AudioDeviceAddress[] devices = new AudioDeviceAddress[1];
+ final long identity = Binder.clearCallingIdentity();
+ final int status = AudioSystem.getPreferredDeviceForStrategy(strategy, devices);
+ Binder.restoreCallingIdentity(identity);
+ if (status != AudioSystem.SUCCESS) {
+ Log.e(TAG, String.format("Error %d in getPreferredDeviceForStrategy(%d)",
+ status, strategy));
+ return null;
+ } else {
+ return devices[0];
+ }
+ }
+
+
/** @see AudioManager#adjustVolume(int, int) */
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
@@ -1752,6 +1818,17 @@
return;
}
+ // If the stream is STREAM_ASSISTANT,
+ // make sure that the calling app have the MODIFY_AUDIO_ROUTING permission.
+ if (streamType == AudioSystem.STREAM_ASSISTANT &&
+ mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
// use stream type alias here so that streams with same alias have the same behavior,
// including with regard to silent mode control (e.g the use of STREAM_RING below and in
// checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
@@ -2188,6 +2265,14 @@
+ " MODIFY_PHONE_STATE callingPackage=" + callingPackage);
return;
}
+ if ((streamType == AudioManager.STREAM_ASSISTANT)
+ && (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ != PackageManager.PERMISSION_GRANTED)) {
+ Log.w(TAG, "Trying to call setStreamVolume() for STREAM_ASSISTANT without"
+ + " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage);
+ return;
+ }
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
index/*val1*/, flags/*val2*/, callingPackage));
setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
@@ -4432,12 +4517,13 @@
}
if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+ && !isStreamMutedByRingerOrZenMode(AudioSystem.STREAM_MUSIC)
&& DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.contains(newDevice)
&& mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
&& mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
&& (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) {
if (DEBUG_VOL) {
- Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
+ Log.i(TAG, String.format("onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
newDevice, AudioSystem.getOutputDeviceName(newDevice)));
}
mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
@@ -6111,6 +6197,7 @@
pw.println("\nRinger mode: ");
pw.println("- mode (internal) = " + RINGER_MODE_NAMES[mRingerMode]);
pw.println("- mode (external) = " + RINGER_MODE_NAMES[mRingerModeExternal]);
+ pw.println("- zen mode:" + Settings.Global.zenModeToString(mNm.getZenMode()));
dumpRingerModeStreams(pw, "affected", mRingerModeAffectedStreams);
dumpRingerModeStreams(pw, "muted", mRingerAndZenModeMutedStreams);
pw.print("- delegate = "); pw.println(mRingerModeDelegate);
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 9f1a6bd..36332c0 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -295,6 +295,7 @@
&& mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
+ broadcast = false;
break;
default:
// do not broadcast CONNECTING or invalid state
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 22cb507..0d88388 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -26,10 +26,12 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IAuthService;
+import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -43,6 +45,7 @@
import android.os.UserHandle;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
import com.android.server.biometrics.face.FaceAuthenticator;
@@ -57,9 +60,6 @@
private static final String TAG = "AuthService";
private static final boolean DEBUG = false;
- private final boolean mHasFeatureFace;
- private final boolean mHasFeatureFingerprint;
- private final boolean mHasFeatureIris;
private final Injector mInjector;
private IBiometricService mBiometricService;
@@ -89,6 +89,16 @@
public void publishBinderService(AuthService service, IAuthService.Stub impl) {
service.publishBinderService(Context.AUTH_SERVICE, impl);
}
+
+ /**
+ * Allows to test with various device sensor configurations.
+ * @param context
+ * @return
+ */
+ @VisibleForTesting
+ public String[] getConfiguration(Context context) {
+ return context.getResources().getStringArray(R.array.config_biometric_sensors);
+ }
}
private final class AuthServiceImpl extends IAuthService.Stub {
@@ -119,17 +129,17 @@
}
@Override
- public int canAuthenticate(String opPackageName, int userId) throws RemoteException {
+ public int canAuthenticate(String opPackageName, int userId,
+ @Authenticators.Types int authenticators) throws RemoteException {
final int callingUserId = UserHandle.getCallingUserId();
- Slog.d(TAG, "canAuthenticate, userId: " + userId
- + ", callingUserId: " + callingUserId);
-
+ Slog.d(TAG, "canAuthenticate, userId: " + userId + ", callingUserId: " + callingUserId
+ + ", authenticators: " + authenticators);
if (userId != callingUserId) {
checkInternalPermission();
} else {
checkPermission();
}
- return mBiometricService.canAuthenticate(opPackageName, userId);
+ return mBiometricService.canAuthenticate(opPackageName, userId, authenticators);
}
@Override
@@ -169,47 +179,56 @@
mInjector = injector;
mImpl = new AuthServiceImpl();
final PackageManager pm = context.getPackageManager();
- mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
- mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
- mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
+ }
+
+ private void registerAuthenticator(SensorConfig config) throws RemoteException {
+
+ Slog.d(TAG, "Registering ID: " + config.mId
+ + " Modality: " + config.mModality
+ + " Strength: " + config.mStrength);
+
+ final IBiometricAuthenticator.Stub authenticator;
+
+ switch (config.mModality) {
+ case TYPE_FINGERPRINT:
+ authenticator = new FingerprintAuthenticator(IFingerprintService.Stub.asInterface(
+ ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
+ break;
+
+ case TYPE_FACE:
+ authenticator = new FaceAuthenticator(IFaceService.Stub.asInterface(
+ ServiceManager.getService(Context.FACE_SERVICE)));
+ break;
+
+ case TYPE_IRIS:
+ authenticator = new IrisAuthenticator(IIrisService.Stub.asInterface(
+ ServiceManager.getService(Context.IRIS_SERVICE)));
+ break;
+
+ default:
+ Slog.e(TAG, "Unknown modality: " + config.mModality);
+ return;
+ }
+
+ mBiometricService.registerAuthenticator(config.mId, config.mModality, config.mStrength,
+ authenticator);
}
@Override
public void onStart() {
mBiometricService = mInjector.getBiometricService();
- if (mHasFeatureFace) {
- final FaceAuthenticator faceAuthenticator = new FaceAuthenticator(
- IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE)));
+ final String[] configs = mInjector.getConfiguration(getContext());
+
+ for (int i = 0; i < configs.length; i++) {
try {
- // TODO(b/141025588): Pass down the real id, strength, and modality.
- mBiometricService.registerAuthenticator(0, 0, TYPE_FACE, faceAuthenticator);
+ registerAuthenticator(new SensorConfig(configs[i]));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
+
}
- if (mHasFeatureFingerprint) {
- final FingerprintAuthenticator fingerprintAuthenticator = new FingerprintAuthenticator(
- IFingerprintService.Stub.asInterface(
- ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
- try {
- // TODO(b/141025588): Pass down the real id, strength, and modality.
- mBiometricService.registerAuthenticator(1, 0, TYPE_FINGERPRINT,
- fingerprintAuthenticator);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
- if (mHasFeatureIris) {
- final IrisAuthenticator irisAuthenticator = new IrisAuthenticator(
- IIrisService.Stub.asInterface(ServiceManager.getService(Context.IRIS_SERVICE)));
- try {
- // TODO(b/141025588): Pass down the real id, strength, and modality.
- mBiometricService.registerAuthenticator(2, 0, TYPE_IRIS, irisAuthenticator);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
+
mInjector.publishBinderService(this, mImpl);
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 5d36793..e1a9f3b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -23,15 +23,17 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.UserSwitchObserver;
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -78,7 +80,7 @@
*/
public class BiometricService extends SystemService {
- private static final String TAG = "BiometricService";
+ static final String TAG = "BiometricService";
private static final boolean DEBUG = true;
private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
@@ -209,6 +211,7 @@
}
private final Injector mInjector;
+ private final DevicePolicyManager mDevicePolicyManager;
@VisibleForTesting
final IBiometricService.Stub mImpl;
@VisibleForTesting
@@ -220,11 +223,15 @@
IStatusBarService mStatusBarService;
@VisibleForTesting
KeyStore mKeyStore;
+ @VisibleForTesting
+ ITrustManager mTrustManager;
// Get and cache the available authenticator (manager) classes. Used since aidl doesn't support
// polymorphism :/
final ArrayList<AuthenticatorWrapper> mAuthenticators = new ArrayList<>();
+ BiometricStrengthController mBiometricStrengthController;
+
// The current authentication session, null if idle/done. We need to track both the current
// and pending sessions since errors may be sent to either.
@VisibleForTesting
@@ -345,17 +352,50 @@
@VisibleForTesting
public static final class AuthenticatorWrapper {
public final int id;
- public final int strength;
+ public final int OEMStrength; // strength as configured by the OEM
+ private int updatedStrength; // strength updated by BiometricStrengthController
public final int modality;
public final IBiometricAuthenticator impl;
- AuthenticatorWrapper(int id, int strength, int modality,
+ AuthenticatorWrapper(int id, int modality, int strength,
IBiometricAuthenticator impl) {
this.id = id;
- this.strength = strength;
this.modality = modality;
+ this.OEMStrength = strength;
+ this.updatedStrength = strength;
this.impl = impl;
}
+
+ /**
+ * Returns the actual strength, taking any updated strengths into effect. Since more bits
+ * means lower strength, the resulting strength is never stronger than the OEM's configured
+ * strength.
+ * @return a bitfield, see {@link Authenticators}
+ */
+ public int getActualStrength() {
+ return OEMStrength | updatedStrength;
+ }
+
+ /**
+ * Stores the updated strength, which takes effect whenever {@link #getActualStrength()}
+ * is checked.
+ * @param newStrength
+ */
+ public void updateStrength(int newStrength) {
+ String log = "updateStrength: Before(" + toString() + ")";
+ updatedStrength = newStrength;
+ log += " After(" + toString() + ")";
+ Slog.d(TAG, log);
+ }
+
+ @Override
+ public String toString() {
+ return "ID(" + id + ")"
+ + " OEMStrength: " + OEMStrength
+ + " updatedStrength: " + updatedStrength
+ + " modality " + modality
+ + " authenticator: " + impl;
+ }
}
@VisibleForTesting
@@ -606,14 +646,18 @@
return;
}
- if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) {
+ if (!Utils.isValidAuthenticatorConfig(bundle)) {
+ throw new SecurityException("Invalid authenticator configuration");
+ }
+
+ if (bundle.getBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS)) {
checkInternalPermission();
}
Utils.combineAuthenticatorBundles(bundle);
- // Check the usage of this in system server. Need to remove this check if it becomes
- // a public API.
+ // Check the usage of this in system server. Need to remove this check if it becomes a
+ // public API.
final boolean useDefaultTitle =
bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false);
if (useDefaultTitle) {
@@ -651,9 +695,11 @@
}
@Override // Binder call
- public int canAuthenticate(String opPackageName, int userId) {
+ public int canAuthenticate(String opPackageName, int userId,
+ @Authenticators.Types int authenticators) {
Slog.d(TAG, "canAuthenticate: User=" + userId
- + ", Caller=" + UserHandle.getCallingUserId());
+ + ", Caller=" + UserHandle.getCallingUserId()
+ + ", Authenticators=" + authenticators);
if (userId != UserHandle.getCallingUserId()) {
checkInternalPermission();
@@ -661,16 +707,39 @@
checkPermission();
}
+
+ if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+ throw new SecurityException("Invalid authenticator configuration");
+ }
+
+ final Bundle bundle = new Bundle();
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+
+ int biometricConstantsResult = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
final long ident = Binder.clearCallingIdentity();
- int error;
try {
- final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
- opPackageName);
- error = result.second;
+ biometricConstantsResult = checkAndGetAuthenticators(userId, bundle, opPackageName,
+ false /* checkDevicePolicyManager */).second;
+ if (biometricConstantsResult != BiometricConstants.BIOMETRIC_SUCCESS
+ && Utils.isDeviceCredentialAllowed(bundle)) {
+ // If there's an issue with biometrics, but device credential is allowed and
+ // set up, return SUCCESS. If device credential isn't set up either, return
+ // ERROR_NO_DEVICE_CREDENTIAL.
+ if (mTrustManager.isDeviceSecure(userId)) {
+ biometricConstantsResult = BiometricConstants.BIOMETRIC_SUCCESS;
+ } else {
+ biometricConstantsResult =
+ BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL;
+ }
+ }
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
} finally {
Binder.restoreCallingIdentity(ident);
}
- return error;
+
+ return Utils.biometricConstantsToBiometricManager(biometricConstantsResult);
}
@Override
@@ -693,11 +762,46 @@
}
@Override
- public void registerAuthenticator(int id, int strength, int modality,
+ public void registerAuthenticator(int id, int modality, int strength,
IBiometricAuthenticator authenticator) {
checkInternalPermission();
- mAuthenticators.add(new AuthenticatorWrapper(id, strength, modality, authenticator));
+ Slog.d(TAG, "Registering ID: " + id
+ + " Modality: " + modality
+ + " Strength: " + strength);
+
+ if (authenticator == null) {
+ throw new IllegalArgumentException("Authenticator must not be null."
+ + " Did you forget to modify the core/res/res/values/xml overlay for"
+ + " config_biometric_sensors?");
+ }
+
+ if (strength != Authenticators.BIOMETRIC_STRONG
+ && strength != Authenticators.BIOMETRIC_WEAK) {
+ throw new IllegalStateException("Unsupported strength");
+ }
+
+ for (AuthenticatorWrapper wrapper : mAuthenticators) {
+ if (wrapper.id == id) {
+ throw new IllegalStateException("Cannot register duplicate authenticator");
+ }
+ }
+
+ // This happens infrequently enough, not worth caching.
+ final String[] configs = mInjector.getConfiguration(getContext());
+ boolean idFound = false;
+ for (int i = 0; i < configs.length; i++) {
+ SensorConfig config = new SensorConfig(configs[i]);
+ if (config.mId == id) {
+ idFound = true;
+ break;
+ }
+ }
+ if (!idFound) {
+ throw new IllegalStateException("Cannot register unknown id");
+ }
+
+ mAuthenticators.add(new AuthenticatorWrapper(id, modality, strength, authenticator));
}
@Override // Binder call
@@ -771,6 +875,11 @@
}
@VisibleForTesting
+ public ITrustManager getTrustManager() {
+ return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE));
+ }
+
+ @VisibleForTesting
public IStatusBarService getStatusBarService() {
return IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -805,6 +914,25 @@
public void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
service.publishBinderService(Context.BIOMETRIC_SERVICE, impl);
}
+
+ /**
+ * Allows to mock BiometricStrengthController for testing.
+ */
+ @VisibleForTesting
+ public BiometricStrengthController getBiometricStrengthController(
+ BiometricService service) {
+ return new BiometricStrengthController(service);
+ }
+
+ /**
+ * Allows to test with various device sensor configurations.
+ * @param context System Server context
+ * @return the sensor configuration from core/res/res/values/config.xml
+ */
+ @VisibleForTesting
+ public String[] getConfiguration(Context context) {
+ return context.getResources().getStringArray(R.array.config_biometric_sensors);
+ }
}
/**
@@ -825,6 +953,8 @@
super(context);
mInjector = injector;
+ mDevicePolicyManager = (DevicePolicyManager) context
+ .getSystemService(context.DEVICE_POLICY_SERVICE);
mImpl = new BiometricServiceWrapper();
mEnabledOnKeyguardCallbacks = new ArrayList<>();
mSettingObserver = mInjector.getSettingObserver(context, mHandler,
@@ -849,7 +979,46 @@
public void onStart() {
mKeyStore = mInjector.getKeyStore();
mStatusBarService = mInjector.getStatusBarService();
+ mTrustManager = mInjector.getTrustManager();
mInjector.publishBinderService(this, mImpl);
+ mBiometricStrengthController = mInjector.getBiometricStrengthController(this);
+ mBiometricStrengthController.startListening();
+ }
+
+ /**
+ * @param modality one of {@link BiometricAuthenticator#TYPE_FINGERPRINT},
+ * {@link BiometricAuthenticator#TYPE_IRIS} or {@link BiometricAuthenticator#TYPE_FACE}
+ * @return
+ */
+ private int mapModalityToDevicePolicyType(int modality) {
+ switch (modality) {
+ case TYPE_FINGERPRINT:
+ return DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
+ case TYPE_IRIS:
+ return DevicePolicyManager.KEYGUARD_DISABLE_IRIS;
+ case TYPE_FACE:
+ return DevicePolicyManager.KEYGUARD_DISABLE_FACE;
+ default:
+ Slog.e(TAG, "Error modality=" + modality);
+ return DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+ }
+ }
+
+ // TODO(joshmccloskey): Update this to throw an error if a new modality is added and this
+ // logic is not updated.
+ private boolean isBiometricDisabledByDevicePolicy(int modality, int effectiveUserId) {
+ final int biometricToCheck = mapModalityToDevicePolicyType(modality);
+ if (biometricToCheck == DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE) {
+ Slog.e(TAG, "Allowing unknown modality " + modality + " to pass Device Policy check");
+ return false;
+ }
+ final int devicePolicyDisabledFeatures =
+ mDevicePolicyManager.getKeyguardDisabledFeatures(null, effectiveUserId);
+ final boolean isBiometricDisabled =
+ (biometricToCheck & devicePolicyDisabledFeatures) != 0;
+ Slog.w(TAG, "isBiometricDisabledByDevicePolicy(" + modality + "," + effectiveUserId
+ + ")=" + isBiometricDisabled);
+ return isBiometricDisabled;
}
/**
@@ -857,25 +1026,36 @@
* returns errors through the callback (no biometric feature, hardware not detected, no
* templates enrolled, etc). This service must not start authentication if errors are sent.
*
- * @Returns A pair [Modality, Error] with Modality being one of
+ * @param userId the user to check for
+ * @param bundle passed from {@link BiometricPrompt}
+ * @param opPackageName see {@link android.app.AppOpsManager}
+ *
+ * @return A pair [Modality, Error] with Modality being one of
* {@link BiometricAuthenticator#TYPE_NONE},
* {@link BiometricAuthenticator#TYPE_FINGERPRINT},
* {@link BiometricAuthenticator#TYPE_IRIS},
* {@link BiometricAuthenticator#TYPE_FACE}
* and the error containing one of the {@link BiometricConstants} errors.
+ *
+ * TODO(kchyn): Update this to handle DEVICE_CREDENTIAL better, reduce duplicate code in callers
*/
- private Pair<Integer, Integer> checkAndGetBiometricModality(int userId, String opPackageName) {
- // No biometric features, send error
- if (mAuthenticators.isEmpty()) {
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
+ private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle,
+ String opPackageName, boolean checkDevicePolicyManager) throws RemoteException {
+ if (!Utils.isBiometricAllowed(bundle)
+ && Utils.isDeviceCredentialAllowed(bundle)
+ && !mTrustManager.isDeviceSecure(userId)) {
+ // If only device credential is being checked, and the user doesn't have one set up
+ return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL);
}
// Assuming that authenticators are listed in priority-order, the rest of this function
- // will go through and find the first authenticator that's available, enrolled, and enabled.
- // The tricky part is returning the correct error. Error strings that are modality-specific
- // should also respect the priority-order.
+ // will attempt to find the first authenticator that's as strong or stronger than the
+ // requested strength, available, enrolled, and enabled. The tricky part is returning the
+ // correct error. Error strings that are modality-specific should also respect the
+ // priority-order.
- // Find first authenticator that's detected, enrolled, and enabled.
+ // Find first authenticator that's strong enough, detected, enrolled, and enabled.
+ boolean hasSufficientStrength = false;
boolean isHardwareDetected = false;
boolean hasTemplatesEnrolled = false;
boolean enabledForApps = false;
@@ -883,35 +1063,44 @@
int modality = TYPE_NONE;
int firstHwAvailable = TYPE_NONE;
for (AuthenticatorWrapper authenticator : mAuthenticators) {
- modality = authenticator.modality;
- try {
+ final int actualStrength = authenticator.getActualStrength();
+ final int requestedStrength = Utils.getPublicBiometricStrength(bundle);
+ if (Utils.isAtLeastStrength(actualStrength, requestedStrength)) {
+ hasSufficientStrength = true;
+ modality = authenticator.modality;
if (authenticator.impl.isHardwareDetected(opPackageName)) {
isHardwareDetected = true;
if (firstHwAvailable == TYPE_NONE) {
- // Store the first one since we want to return the error in correct priority
- // order.
+ // Store the first one since we want to return the error in correct
+ // priority order.
firstHwAvailable = modality;
}
if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
hasTemplatesEnrolled = true;
+ // If the device policy manager disables a specific biometric, skip it.
+ if (checkDevicePolicyManager &&
+ isBiometricDisabledByDevicePolicy(modality, userId)) {
+ continue;
+ }
if (isEnabledForApp(modality, userId)) {
enabledForApps = true;
break;
}
}
}
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
}
}
- Slog.d(TAG, "checkAndGetBiometricModality: user=" + userId
+ Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId
+ + " checkDevicePolicyManager=" + checkDevicePolicyManager
+ " isHardwareDetected=" + isHardwareDetected
+ " hasTemplatesEnrolled=" + hasTemplatesEnrolled
+ " enabledForApps=" + enabledForApps);
// Check error conditions
- if (!isHardwareDetected) {
+ if (!hasSufficientStrength) {
+ return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
+ } else if (!isHardwareDetected) {
return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
} else if (!hasTemplatesEnrolled) {
// Return the modality here so the correct error string can be sent. This error is
@@ -1107,13 +1296,16 @@
// SystemUI handles transition from biometric to device credential.
mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mStatusBarService.onBiometricError(modality, error, vendorCode);
+ } else if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+ mStatusBarService.hideAuthenticationDialog();
+ // TODO: If multiple authenticators are simultaneously running, this will
+ // need to be modified. Send the error to the client here, instead of doing
+ // a round trip to SystemUI.
+ mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode);
+ mCurrentAuthSession = null;
} else {
mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
- if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
- mStatusBarService.hideAuthenticationDialog();
- } else {
- mStatusBarService.onBiometricError(modality, error, vendorCode);
- }
+ mStatusBarService.onBiometricError(modality, error, vendorCode);
}
} else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
// In the "try again" state, we should forward canceled errors to
@@ -1135,10 +1327,11 @@
// If any error is received while preparing the auth session (lockout, etc),
// and if device credential is allowed, just show the credential UI.
if (mPendingAuthSession.isAllowDeviceCredential()) {
- int authenticators = mPendingAuthSession.mBundle
- .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
+ @Authenticators.Types int authenticators =
+ mPendingAuthSession.mBundle.getInt(
+ BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
// Disallow biometric and notify SystemUI to show the authentication prompt.
- authenticators &= ~Authenticator.TYPE_BIOMETRIC;
+ authenticators &= ~Authenticators.BIOMETRIC_WEAK;
mPendingAuthSession.mBundle.putInt(
BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
authenticators);
@@ -1355,31 +1548,45 @@
int callingUid, int callingPid, int callingUserId) {
mHandler.post(() -> {
- final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
- opPackageName);
- final int modality = result.first;
- final int error = result.second;
+ int modality = TYPE_NONE;
+ int result;
- final boolean credentialAllowed = Utils.isDeviceCredentialAllowed(bundle);
-
- if (error != BiometricConstants.BIOMETRIC_SUCCESS && credentialAllowed) {
- // If there's a problem but device credential is allowed, only show credential UI.
- bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
- Authenticator.TYPE_CREDENTIAL);
- } else if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
- // Check for errors, notify callback, and return
- try {
- receiver.onError(modality, error, 0 /* vendorCode */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to send error", e);
- }
- return;
+ try {
+ final boolean checkDevicePolicyManager = bundle.getBoolean(
+ BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false);
+ final Pair<Integer, Integer> pair = checkAndGetAuthenticators(userId, bundle,
+ opPackageName, checkDevicePolicyManager);
+ modality = pair.first;
+ result = pair.second;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ result = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
}
- // Start preparing for authentication. Authentication starts when
- // all modalities requested have invoked onReadyForAuthentication.
- authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
- callingUid, callingPid, callingUserId, modality);
+ try {
+ if (result == BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL) {
+ // If the app allowed device credential but the user hasn't set it up yet,
+ // return this error.
+ receiver.onError(modality, result, 0 /* vendorCode */);
+ } else if (result != BiometricConstants.BIOMETRIC_SUCCESS) {
+ if (Utils.isDeviceCredentialAllowed(bundle)) {
+ // If there's a problem with biometrics but device credential is allowed,
+ // only show credential UI.
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
+ Authenticators.DEVICE_CREDENTIAL);
+ authenticateInternal(token, sessionId, userId, receiver, opPackageName,
+ bundle, callingUid, callingPid, callingUserId, modality);
+ } else {
+ receiver.onError(modality, result, 0 /* vendorCode */);
+ }
+ } else {
+ // BIOMETRIC_SUCCESS, proceed to authentication
+ authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
+ callingUid, callingPid, callingUserId, modality);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
});
}
@@ -1407,7 +1614,8 @@
// with the cookie. Once all cookies are received, we can show the prompt
// and let the services start authenticating. The cookie should be non-zero.
final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
- final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
+ final @Authenticators.Types int authenticators = bundle.getInt(
+ BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
Slog.d(TAG, "Creating auth session. Modality: " + modality
+ ", cookie: " + cookie
+ ", authenticators: " + authenticators);
@@ -1415,7 +1623,7 @@
// If it's only device credential, we don't need to wait - LockSettingsService is
// always ready to check credential (SystemUI invokes that path).
- if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
+ if ((authenticators & ~Authenticators.DEVICE_CREDENTIAL) != 0) {
modalities.put(modality, cookie);
}
mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
@@ -1423,7 +1631,7 @@
modality, requireConfirmation);
try {
- if (authenticators == Authenticator.TYPE_CREDENTIAL) {
+ if (authenticators == Authenticators.DEVICE_CREDENTIAL) {
mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
mCurrentAuthSession = mPendingAuthSession;
mPendingAuthSession = null;
@@ -1438,9 +1646,13 @@
} else {
mPendingAuthSession.mState = STATE_AUTH_CALLED;
for (AuthenticatorWrapper authenticator : mAuthenticators) {
- authenticator.impl.prepareForAuthentication(requireConfirmation, token,
- sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid,
- callingPid, callingUserId);
+ // TODO(b/141025588): use ids instead of modalities to avoid ambiguity.
+ if (authenticator.modality == modality) {
+ authenticator.impl.prepareForAuthentication(requireConfirmation, token,
+ sessionId, userId, mInternalReceiver, opPackageName, cookie,
+ callingUid, callingPid, callingUserId);
+ break;
+ }
}
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 2de18c3..60f0e8e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import android.app.ActivityManager;
@@ -1013,8 +1014,13 @@
private boolean isForegroundActivity(int uid, int pid) {
try {
- List<ActivityManager.RunningAppProcessInfo> procs =
+ final List<ActivityManager.RunningAppProcessInfo> procs =
ActivityManager.getService().getRunningAppProcesses();
+ if (procs == null) {
+ Slog.e(getTag(), "Processes null, defaulting to true");
+ return true;
+ }
+
int N = procs.size();
for (int i = 0; i < N; i++) {
ActivityManager.RunningAppProcessInfo proc = procs.get(i);
@@ -1206,6 +1212,11 @@
* @return authenticator id for the calling user
*/
protected long getAuthenticatorId(String opPackageName) {
+ if (isKeyguard(opPackageName)) {
+ // If an app tells us it's keyguard, check that it actually is.
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ }
+
final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
return mAuthenticatorIds.getOrDefault(userId, 0L);
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricStrengthController.java b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
new file mode 100644
index 0000000..4e16189
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
@@ -0,0 +1,119 @@
+/*
+ * 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.biometrics;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class for maintaining and updating the strengths for biometric sensors. Strengths can only
+ * be downgraded from the device's default, and never upgraded.
+ */
+public class BiometricStrengthController implements DeviceConfig.OnPropertiesChangedListener {
+ private static final String TAG = "BiometricStrengthController";
+
+ private final BiometricService mService;
+
+ /**
+ * Flag stored in the DeviceConfig API: biometric modality strengths to downgrade.
+ * This is encoded as a key:value list, separated by comma, e.g.
+ *
+ * "id1:strength1,id2:strength2,id3:strength3"
+ *
+ * where strength is one of the values defined in
+ * {@link android.hardware.biometrics.Authenticators}
+ *
+ * Both id and strength should be int, otherwise Exception will be thrown when parsing and the
+ * downgrade will fail.
+ */
+ private static final String KEY_BIOMETRIC_STRENGTHS = "biometric_strengths";
+
+ /**
+ * Default (no-op) value of the flag KEY_BIOMETRIC_STRENGTHS
+ */
+ public static final String DEFAULT_BIOMETRIC_STRENGTHS = null;
+
+ BiometricStrengthController(@NonNull BiometricService service) {
+ mService = service;
+ }
+
+ void startListening() {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BIOMETRICS,
+ BackgroundThread.getExecutor(), this);
+ updateStrengths();
+ }
+
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ for (String name : properties.getKeyset()) {
+ if (KEY_BIOMETRIC_STRENGTHS.equals(name)) {
+ updateStrengths();
+ }
+ }
+ }
+
+ /**
+ * Updates the strengths of authenticators in BiometricService if a matching ID's configuration
+ * has been changed.
+ */
+ private void updateStrengths() {
+ final Map<Integer, Integer> idToStrength = getIdToStrengthMap();
+ if (idToStrength == null) {
+ return;
+ }
+
+ for (BiometricService.AuthenticatorWrapper authenticator : mService.mAuthenticators) {
+ final int id = authenticator.id;
+ if (idToStrength.containsKey(id)) {
+ final int newStrength = idToStrength.get(id);
+ authenticator.updateStrength(newStrength);
+ }
+ }
+ }
+
+ /**
+ * @return a map of <ID, Strength>
+ */
+ private Map<Integer, Integer> getIdToStrengthMap() {
+ final String flags = DeviceConfig.getString(DeviceConfig.NAMESPACE_BIOMETRICS,
+ KEY_BIOMETRIC_STRENGTHS, DEFAULT_BIOMETRIC_STRENGTHS);
+ if (flags == null || flags.isEmpty()) {
+ Slog.d(TAG, "Flags are null or empty");
+ return null;
+ }
+
+ Map<Integer, Integer> map = new HashMap<>();
+ try {
+ for (String item : flags.split(",")) {
+ String[] elems = item.split(":");
+ final int id = Integer.parseInt(elems[0]);
+ final int strength = Integer.parseInt(elems[1]);
+ map.put(id, strength);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Can't parse flag: " + flags);
+ map = null;
+ }
+ return map;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/OWNERS b/services/core/java/com/android/server/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/services/core/java/com/android/server/biometrics/SensorConfig.java b/services/core/java/com/android/server/biometrics/SensorConfig.java
new file mode 100644
index 0000000..9eda6da
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/SensorConfig.java
@@ -0,0 +1,33 @@
+/*
+ * 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.biometrics;
+
+/**
+ * Parsed sensor config. See core/res/res/values/config.xml config_biometric_sensors
+ */
+class SensorConfig {
+ final int mId;
+ final int mModality;
+ final int mStrength;
+
+ public SensorConfig(String config) {
+ String[] elems = config.split(":");
+ mId = Integer.parseInt(elems[0]);
+ mModality = Integer.parseInt(elems[1]);
+ mStrength = Integer.parseInt(elems[2]);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index ed5f9de..19f5358 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -16,15 +16,17 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import android.content.Context;
-import android.hardware.biometrics.Authenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
-
-import com.android.internal.annotations.VisibleForTesting;
+import android.util.Slog;
public class Utils {
public static boolean isDebugEnabled(Context context, int targetUserId) {
@@ -45,41 +47,167 @@
}
/**
- * Combine {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with
- * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible
- * enough.
+ * Combines {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with
+ * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible enough.
*/
public static void combineAuthenticatorBundles(Bundle bundle) {
- boolean biometricEnabled = true; // enabled by default
- boolean credentialEnabled = bundle.getBoolean(
- BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
- if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) {
- final int authenticatorFlags =
- bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
- biometricEnabled = (authenticatorFlags & Authenticator.TYPE_BIOMETRIC) != 0;
- // Using both KEY_ALLOW_DEVICE_CREDENTIAL and KEY_AUTHENTICATORS_ALLOWED together
- // is not supported. Default to overwriting.
- credentialEnabled = (authenticatorFlags & Authenticator.TYPE_CREDENTIAL) != 0;
- }
-
+ // Cache and remove explicit ALLOW_DEVICE_CREDENTIAL boolean flag from the bundle.
+ final boolean deviceCredentialAllowed =
+ bundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
bundle.remove(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);
- int authenticators = 0;
- if (biometricEnabled) {
- authenticators |= Authenticator.TYPE_BIOMETRIC;
+ final @Authenticators.Types int authenticators;
+ if (bundle.containsKey(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)) {
+ // Ignore ALLOW_DEVICE_CREDENTIAL flag if AUTH_TYPES_ALLOWED is defined.
+ authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
+ } else {
+ // Otherwise, use ALLOW_DEVICE_CREDENTIAL flag along with Weak+ biometrics by default.
+ authenticators = deviceCredentialAllowed
+ ? Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK
+ : Authenticators.BIOMETRIC_WEAK;
}
- if (credentialEnabled) {
- authenticators |= Authenticator.TYPE_CREDENTIAL;
- }
+
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
}
/**
+ * @param authenticators composed of one or more values from {@link Authenticators}
+ * @return true if device credential is allowed.
+ */
+ public static boolean isDeviceCredentialAllowed(@Authenticators.Types int authenticators) {
+ return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
+ }
+
+ /**
* @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
- * @return true if device credential allowed.
+ * @return true if device credential is allowed.
*/
public static boolean isDeviceCredentialAllowed(Bundle bundle) {
+ return isDeviceCredentialAllowed(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ }
+
+ /**
+ * Checks if any of the publicly defined strengths are set.
+ *
+ * @param authenticators composed of one or more values from {@link Authenticators}
+ * @return minimal allowed biometric strength or 0 if biometric authentication is not allowed.
+ */
+ public static int getPublicBiometricStrength(@Authenticators.Types int authenticators) {
+ // Only biometrics WEAK and above are allowed to integrate with the public APIs.
+ return authenticators & Authenticators.BIOMETRIC_WEAK;
+ }
+
+ /**
+ * Checks if any of the publicly defined strengths are set.
+ *
+ * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
+ * @return minimal allowed biometric strength or 0 if biometric authentication is not allowed.
+ */
+ public static int getPublicBiometricStrength(Bundle bundle) {
+ return getPublicBiometricStrength(
+ bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ }
+
+ /**
+ * Checks if any of the publicly defined strengths are set.
+ *
+ * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
+ * @return true if biometric authentication is allowed.
+ */
+ public static boolean isBiometricAllowed(Bundle bundle) {
+ return getPublicBiometricStrength(bundle) != 0;
+ }
+
+ /**
+ * @param sensorStrength the strength of the sensor
+ * @param requestedStrength the strength that it must meet
+ * @return true only if the sensor is at least as strong as the requested strength
+ */
+ public static boolean isAtLeastStrength(int sensorStrength, int requestedStrength) {
+ // If the authenticator contains bits outside of the requested strength, it is too weak.
+ return (~requestedStrength & sensorStrength) == 0;
+ }
+
+ /**
+ * Checks if the authenticator configuration is a valid combination of the public APIs
+ * @param bundle
+ * @return
+ */
+ public static boolean isValidAuthenticatorConfig(Bundle bundle) {
final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
- return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
+ return isValidAuthenticatorConfig(authenticators);
+ }
+
+ /**
+ * Checks if the authenticator configuration is a valid combination of the public APIs
+ * @param authenticators
+ * @return
+ */
+ public static boolean isValidAuthenticatorConfig(int authenticators) {
+ // The caller is not required to set the authenticators. But if they do, check the below.
+ if (authenticators == 0) {
+ return true;
+ }
+
+ // Check if any of the non-biometric and non-credential bits are set. If so, this is
+ // invalid.
+ final int testBits = ~(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_MIN_STRENGTH);
+ if ((authenticators & testBits) != 0) {
+ Slog.e(BiometricService.TAG, "Non-biometric, non-credential bits found."
+ + " Authenticators: " + authenticators);
+ return false;
+ }
+
+ // Check that biometrics bits are either NONE, WEAK, or STRONG. If NONE, DEVICE_CREDENTIAL
+ // should be set.
+ final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH;
+ if (biometricBits == Authenticators.EMPTY_SET
+ && isDeviceCredentialAllowed(authenticators)) {
+ return true;
+ } else if (biometricBits == Authenticators.BIOMETRIC_STRONG) {
+ return true;
+ } else if (biometricBits == Authenticators.BIOMETRIC_WEAK) {
+ return true;
+ }
+
+ Slog.e(BiometricService.TAG, "Unsupported biometric flags. Authenticators: "
+ + authenticators);
+ // Non-supported biometric flags are being used
+ return false;
+ }
+
+ /**
+ * Converts error codes from BiometricConstants, which are used in most of the internal plumbing
+ * and eventually returned to {@link BiometricPrompt.AuthenticationCallback} to public
+ * {@link BiometricManager} constants, which are used by APIs such as
+ * {@link BiometricManager#canAuthenticate(int)}
+ *
+ * @param biometricConstantsCode see {@link BiometricConstants}
+ * @return see {@link BiometricManager}
+ */
+ public static int biometricConstantsToBiometricManager(int biometricConstantsCode) {
+ final int biometricManagerCode;
+
+ switch (biometricConstantsCode) {
+ case BiometricConstants.BIOMETRIC_SUCCESS:
+ biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
+ break;
+ case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
+ case BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED;
+ break;
+ case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ break;
+ case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
+ break;
+ default:
+ Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ break;
+ }
+ return biometricManagerCode;
}
}
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 27f11ff..1b1c546 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -31,8 +31,6 @@
import android.util.Log;
import com.android.internal.app.IBatteryStats;
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.server.am.BatteryStatsService;
public class DataConnectionStats extends BroadcastReceiver {
@@ -44,7 +42,7 @@
private final Handler mListenerHandler;
private final PhoneStateListener mPhoneStateListener;
- private IccCardConstants.State mSimState = IccCardConstants.State.READY;
+ private int mSimState = TelephonyManager.SIM_STATE_READY;
private SignalStrength mSignalStrength;
private ServiceState mServiceState;
private int mDataState = TelephonyManager.DATA_DISCONNECTED;
@@ -66,7 +64,7 @@
| PhoneStateListener.LISTEN_DATA_ACTIVITY);
IntentFilter filter = new IntentFilter();
- filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+ filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
mContext.registerReceiver(this, filter, null /* broadcastPermission */, mListenerHandler);
@@ -75,7 +73,7 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
+ if (action.equals(Intent.ACTION_SIM_STATE_CHANGED)) {
updateSimState(intent);
notePhoneDataConnectionState();
} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
@@ -88,8 +86,8 @@
if (mServiceState == null) {
return;
}
- boolean simReadyOrUnknown = mSimState == IccCardConstants.State.READY
- || mSimState == IccCardConstants.State.UNKNOWN;
+ boolean simReadyOrUnknown = mSimState == TelephonyManager.SIM_STATE_READY
+ || mSimState == TelephonyManager.SIM_STATE_UNKNOWN;
boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
&& hasService()
&& mDataState == TelephonyManager.DATA_CONNECTED;
@@ -105,23 +103,23 @@
}
private final void updateSimState(Intent intent) {
- String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
- if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
- mSimState = IccCardConstants.State.ABSENT;
- } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
- mSimState = IccCardConstants.State.READY;
- } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
+ String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE);
+ if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) {
+ mSimState = TelephonyManager.SIM_STATE_ABSENT;
+ } else if (Intent.SIM_STATE_READY.equals(stateExtra)) {
+ mSimState = TelephonyManager.SIM_STATE_READY;
+ } else if (Intent.SIM_STATE_LOCKED.equals(stateExtra)) {
final String lockedReason =
- intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
- if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
- mSimState = IccCardConstants.State.PIN_REQUIRED;
- } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
- mSimState = IccCardConstants.State.PUK_REQUIRED;
+ intent.getStringExtra(Intent.EXTRA_SIM_LOCKED_REASON);
+ if (Intent.SIM_LOCKED_ON_PIN.equals(lockedReason)) {
+ mSimState = TelephonyManager.SIM_STATE_PIN_REQUIRED;
+ } else if (Intent.SIM_LOCKED_ON_PUK.equals(lockedReason)) {
+ mSimState = TelephonyManager.SIM_STATE_PUK_REQUIRED;
} else {
- mSimState = IccCardConstants.State.NETWORK_LOCKED;
+ mSimState = TelephonyManager.SIM_STATE_NETWORK_LOCKED;
}
} else {
- mSimState = IccCardConstants.State.UNKNOWN;
+ mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
}
}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 9bae902..af8a366 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -39,11 +39,11 @@
import android.annotation.Nullable;
import android.content.Context;
import android.net.ISocketKeepaliveCallback;
+import android.net.InvalidPacketException;
import android.net.KeepalivePacketData;
import android.net.NattKeepalivePacketData;
import android.net.NetworkAgent;
import android.net.NetworkUtils;
-import android.net.SocketKeepalive.InvalidPacketException;
import android.net.SocketKeepalive.InvalidSocketException;
import android.net.TcpKeepalivePacketData;
import android.net.util.IpUtils;
@@ -657,7 +657,10 @@
final TcpKeepalivePacketData packet;
try {
packet = TcpKeepaliveController.getTcpKeepalivePacket(fd);
- } catch (InvalidPacketException | InvalidSocketException e) {
+ } catch (InvalidSocketException e) {
+ notifyErrorCallback(cb, e.error);
+ return;
+ } catch (InvalidPacketException e) {
notifyErrorCallback(cb, e.error);
return;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index bb7f862..5e085ca 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -291,13 +291,18 @@
*
* <p>If {@link NetworkMonitor#notifyNetworkCapabilitiesChanged(NetworkCapabilities)} fails,
* the exception is logged but not reported to callers.
+ *
+ * @return the old capabilities of this network.
*/
- public void setNetworkCapabilities(NetworkCapabilities nc) {
+ public synchronized NetworkCapabilities getAndSetNetworkCapabilities(
+ @NonNull final NetworkCapabilities nc) {
+ final NetworkCapabilities oldNc = networkCapabilities;
networkCapabilities = nc;
final NetworkMonitorManager nm = mNetworkMonitor;
if (nm != null) {
nm.notifyNetworkCapabilitiesChanged(nc);
}
+ return oldNc;
}
public ConnectivityService connService() {
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index e570ef1e..1129899 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -30,8 +30,8 @@
import static android.system.OsConstants.TIOCOUTQ;
import android.annotation.NonNull;
+import android.net.InvalidPacketException;
import android.net.NetworkUtils;
-import android.net.SocketKeepalive.InvalidPacketException;
import android.net.SocketKeepalive.InvalidSocketException;
import android.net.TcpKeepalivePacketData;
import android.net.TcpKeepalivePacketDataParcelable;
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 31632dc..177e2d8 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -41,6 +41,7 @@
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.EventLogTags;
@@ -215,7 +216,9 @@
private IActivityTaskManager mActivityTaskManager;
private PackageManager mPackageManager;
- public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
+ private final Injector mInjector;
+
+ AutomaticBrightnessController(Callbacks callbacks, Looper looper,
SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
@@ -223,6 +226,24 @@
HysteresisLevels ambientBrightnessThresholds,
HysteresisLevels screenBrightnessThresholds, long shortTermModelTimeout,
PackageManager packageManager) {
+ this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
+ lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
+ lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
+ darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
+ ambientBrightnessThresholds, screenBrightnessThresholds, shortTermModelTimeout,
+ packageManager);
+ }
+
+ @VisibleForTesting
+ AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper,
+ SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
+ int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
+ int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
+ long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds, long shortTermModelTimeout,
+ PackageManager packageManager) {
+ mInjector = injector;
mCallbacks = callbacks;
mSensorManager = sensorManager;
mBrightnessMapper = mapper;
@@ -725,8 +746,8 @@
float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
mForegroundAppCategory);
- int newScreenAutoBrightness =
- clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
+ int newScreenAutoBrightness = Math.round(clampScreenBrightness(
+ value * PowerManager.BRIGHTNESS_ON));
// If screenAutoBrightness is set, we should have screen{Brightening,Darkening}Threshold,
// in which case we ignore the new screen brightness if it doesn't differ enough from the
@@ -750,10 +771,10 @@
}
mScreenAutoBrightness = newScreenAutoBrightness;
- mScreenBrighteningThreshold =
- mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness);
- mScreenDarkeningThreshold =
- mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness);
+ mScreenBrighteningThreshold = clampScreenBrightness(
+ mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
+ mScreenDarkeningThreshold = clampScreenBrightness(
+ mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));
if (sendUpdate) {
mCallbacks.updateBrightness();
@@ -761,7 +782,7 @@
}
}
- private int clampScreenBrightness(int value) {
+ private float clampScreenBrightness(float value) {
return MathUtils.constrain(value,
mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
}
@@ -839,7 +860,7 @@
}
// The ActivityTaskManager's lock tends to get contended, so this is done in a background
// thread and applied via this thread's handler synchronously.
- BackgroundThread.getHandler().post(new Runnable() {
+ mInjector.getBackgroundThreadHandler().post(new Runnable() {
public void run() {
try {
// The foreground app is the top activity of the focused tasks stack.
@@ -965,6 +986,9 @@
private int mCount;
public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
+ if (lightSensorRate <= 0) {
+ throw new IllegalArgumentException("lightSensorRate must be above 0");
+ }
mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
mRingLux = new float[mCapacity];
mRingTime = new long[mCapacity];
@@ -1076,4 +1100,10 @@
return index;
}
}
+
+ public static class Injector {
+ public Handler getBackgroundThreadHandler() {
+ return BackgroundThread.getHandler();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 7ce63c5..d57ce53 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -138,18 +138,13 @@
}
/**
- * Sets the refresh ranges, and display modes that the system is allowed to switch between.
- * Display modes are roughly ordered by preference.
+ * Sets the display mode specs.
*
* Not all display devices will automatically switch between modes, so it's important that the
- * most-desired modes are at the beginning of the allowed array.
- *
- * @param defaultModeId is used, if the device does not support multiple refresh
- * rates, and to navigate other parameters.
+ * default modeId is set correctly.
*/
- public void setDesiredDisplayConfigSpecs(int defaultModeId, float minRefreshRate,
- float maxRefreshRate, int[] modes) {
- }
+ public void setDesiredDisplayModeSpecsLocked(
+ DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {}
/**
* Sets the requested color mode.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d20191d..ea03131 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -431,7 +431,8 @@
recordTopInsetLocked(mLogicalDisplays.get(Display.DEFAULT_DISPLAY));
}
- mDisplayModeDirector.setDisplayModeListener(new AllowedDisplayModeObserver());
+ mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
+ new DesiredDisplayModeSpecsObserver());
mDisplayModeDirector.start(mSensorManager);
mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
@@ -1327,19 +1328,24 @@
return SurfaceControl.getDisplayedContentSample(token, maxFrames, timestamp);
}
- private void onAllowedDisplayModesChangedInternal() {
+ private void onDesiredDisplayModeSpecsChangedInternal() {
boolean changed = false;
synchronized (mSyncRoot) {
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
LogicalDisplay display = mLogicalDisplays.valueAt(i);
int displayId = mLogicalDisplays.keyAt(i);
- int[] allowedModes = mDisplayModeDirector.getAllowedModes(displayId);
- // Note that order is important here since not all display devices are capable of
- // automatically switching, so we do actually want to check for equality and not
- // just equivalent contents (regardless of order).
- if (!Arrays.equals(allowedModes, display.getAllowedDisplayModesLocked())) {
- display.setAllowedDisplayModesLocked(allowedModes);
+ DisplayModeDirector.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
+ mDisplayModeDirector.getDesiredDisplayModeSpecs(displayId);
+ DisplayModeDirector.DesiredDisplayModeSpecs existingDesiredDisplayModeSpecs =
+ display.getDesiredDisplayModeSpecsLocked();
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Comparing display specs: " + desiredDisplayModeSpecs
+ + ", existing: " + existingDesiredDisplayModeSpecs);
+ }
+ if (!desiredDisplayModeSpecs.equals(existingDesiredDisplayModeSpecs)) {
+ display.setDesiredDisplayModeSpecsLocked(desiredDisplayModeSpecs);
changed = true;
}
}
@@ -2488,9 +2494,10 @@
}
- class AllowedDisplayModeObserver implements DisplayModeDirector.DisplayModeListener {
- public void onAllowedDisplayModesChanged() {
- onAllowedDisplayModesChangedInternal();
+ class DesiredDisplayModeSpecsObserver
+ implements DisplayModeDirector.DesiredDisplayModeSpecsListener {
+ public void onDesiredDisplayModeSpecsChanged() {
+ onDesiredDisplayModeSpecsChangedInternal();
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 2df682f..ad728c1 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -63,7 +63,7 @@
private static final String TAG = "DisplayModeDirector";
private static final boolean DEBUG = false;
- private static final int MSG_ALLOWED_MODES_CHANGED = 1;
+ private static final int MSG_REFRESH_RATE_RANGE_CHANGED = 1;
private static final int MSG_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
private static final int MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED = 3;
private static final int MSG_REFRESH_RATE_IN_ZONE_CHANGED = 4;
@@ -95,7 +95,7 @@
private final BrightnessObserver mBrightnessObserver;
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
- private DisplayModeListener mDisplayModeListener;
+ private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
mContext = context;
@@ -125,23 +125,11 @@
synchronized (mLock) {
// We may have a listener already registered before the call to start, so go ahead and
// notify them to pick up our newly initialized state.
- notifyAllowedModesChangedLocked();
+ notifyDesiredDisplayModeSpecsChangedLocked();
}
}
- /**
- * Calculates the modes the system is allowed to freely switch between based on global and
- * display-specific constraints.
- *
- * @param displayId The display to query for.
- * @return The IDs of the modes the system is allowed to freely switch between.
- */
- @NonNull
- public int[] getAllowedModes(int displayId) {
- return getDesiredDisplayConfigSpecs(displayId).allowedConfigs;
- }
-
@NonNull
private SparseArray<Vote> getVotesLocked(int displayId) {
SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
@@ -173,16 +161,16 @@
* system is allowed to switch between.
*/
@NonNull
- public DesiredDisplayConfigSpecs getDesiredDisplayConfigSpecs(int displayId) {
+ public DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(int displayId) {
synchronized (mLock) {
SparseArray<Vote> votes = getVotesLocked(displayId);
Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
if (modes == null || defaultMode == null) {
- Slog.e(TAG, "Asked about unknown display, returning empty desired configs!"
- + "(id=" + displayId + ")");
- return new DesiredDisplayConfigSpecs(displayId, new RefreshRateRange(60, 60),
- new int[0]);
+ Slog.e(TAG,
+ "Asked about unknown display, returning empty display mode specs!"
+ + "(id=" + displayId + ")");
+ return new DesiredDisplayModeSpecs();
}
int[] availableModes = new int[]{defaultMode.getModeId()};
@@ -255,9 +243,9 @@
}
// 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 the first and only element in available modes array.
- return new DesiredDisplayConfigSpecs(defaultModeId,
- new RefreshRateRange(minRefreshRate, maxRefreshRate), availableModes);
+ // stored as defaultModeId.
+ return new DesiredDisplayModeSpecs(
+ defaultModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate));
}
}
@@ -311,11 +299,13 @@
}
/**
- * Sets the modeListener for changes to allowed display modes.
+ * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate
+ * ranges.
*/
- public void setDisplayModeListener(@Nullable DisplayModeListener displayModeListener) {
+ public void setDesiredDisplayModeSpecsListener(
+ @Nullable DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener) {
synchronized (mLock) {
- mDisplayModeListener = displayModeListener;
+ mDesiredDisplayModeSpecsListener = desiredDisplayModeSpecsListener;
}
}
@@ -389,16 +379,18 @@
mVotesByDisplay.remove(displayId);
}
- notifyAllowedModesChangedLocked();
+ notifyDesiredDisplayModeSpecsChangedLocked();
}
- private void notifyAllowedModesChangedLocked() {
- if (mDisplayModeListener != null && !mHandler.hasMessages(MSG_ALLOWED_MODES_CHANGED)) {
+ private void notifyDesiredDisplayModeSpecsChangedLocked() {
+ if (mDesiredDisplayModeSpecsListener != null
+ && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) {
// We need to post this to a handler to avoid calling out while holding the lock
// since we know there are things that both listen for changes as well as provide
- // information. If we did call out while holding the lock, then there's no guaranteed
- // lock order and we run the real of risk deadlock.
- Message msg = mHandler.obtainMessage(MSG_ALLOWED_MODES_CHANGED, mDisplayModeListener);
+ // information. If we did call out while holding the lock, then there's no
+ // guaranteed lock order and we run the real of risk deadlock.
+ Message msg = mHandler.obtainMessage(
+ MSG_REFRESH_RATE_RANGE_CHANGED, mDesiredDisplayModeSpecsListener);
msg.sendToTarget();
}
}
@@ -430,13 +422,13 @@
}
/**
- * Listens for changes to display mode coordination.
+ * Listens for changes refresh rate coordination.
*/
- public interface DisplayModeListener {
+ public interface DesiredDisplayModeSpecsListener {
/**
- * Called when the allowed display modes may have changed.
+ * Called when the refresh rate range may have changed.
*/
- void onAllowedDisplayModesChanged();
+ void onDesiredDisplayModeSpecsChanged();
}
private final class DisplayModeDirectorHandler extends Handler {
@@ -447,11 +439,6 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_ALLOWED_MODES_CHANGED:
- DisplayModeListener displayModeListener = (DisplayModeListener) msg.obj;
- displayModeListener.onAllowedDisplayModesChanged();
- break;
-
case MSG_BRIGHTNESS_THRESHOLDS_CHANGED:
Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
@@ -474,6 +461,12 @@
mBrightnessObserver.onDeviceConfigRefreshRateInZoneChanged(
refreshRateInZone);
break;
+
+ case MSG_REFRESH_RATE_RANGE_CHANGED:
+ DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener =
+ (DesiredDisplayModeSpecsListener) msg.obj;
+ desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged();
+ break;
}
}
}
@@ -485,11 +478,13 @@
/**
* The lowest desired refresh rate.
*/
- public final float min;
+ public float min;
/**
* The highest desired refresh rate.
*/
- public final float max;
+ public float max;
+
+ public RefreshRateRange() {}
public RefreshRateRange(float min, float max) {
if (min < 0 || max < 0 || min > max) {
@@ -531,32 +526,32 @@
}
/**
- * Information about the desired configuration to be set by the system. Includes the default
- * configuration ID, refresh rate range, and the list of policy decisions that influenced the
- * choice.
+ * Information about the desired display mode to be set by the system. Includes the default
+ * mode ID and refresh rate range.
+ *
+ * We have this class in addition to SurfaceControl.DesiredDisplayConfigSpecs to make clear the
+ * distinction between the config ID / physical index that
+ * SurfaceControl.DesiredDisplayConfigSpecs uses, and the mode ID used here.
*/
- public static final class DesiredDisplayConfigSpecs {
+ public static final class DesiredDisplayModeSpecs {
/**
- * Default configuration ID. This is what system defaults to for all other settings, or
+ * Default mode ID. This is what system defaults to for all other settings, or
* if the refresh rate range is not available.
*/
- public final int defaultModeId;
+ public int defaultModeId;
/**
* The refresh rate range.
*/
public final RefreshRateRange refreshRateRange;
- /**
- * For legacy reasons, keep a list of allowed configs.
- * TODO(b/142507213): Re-assess whether the list of allowed configs is still necessary.
- */
- public final int[] allowedConfigs;
- public DesiredDisplayConfigSpecs(int defaultModeId,
- @NonNull RefreshRateRange refreshRateRange,
- @NonNull int[] allowedConfigs) {
+ public DesiredDisplayModeSpecs() {
+ refreshRateRange = new RefreshRateRange();
+ }
+
+ public DesiredDisplayModeSpecs(
+ int defaultModeId, @NonNull RefreshRateRange refreshRateRange) {
this.defaultModeId = defaultModeId;
this.refreshRateRange = refreshRateRange;
- this.allowedConfigs = allowedConfigs;
}
/**
@@ -564,9 +559,8 @@
*/
@Override
public String toString() {
- return "DesiredDisplayConfigSpecs(defaultModeId=" + defaultModeId
- + ", refreshRateRange=" + refreshRateRange.toString()
- + ", allowedConfigs=" + Arrays.toString(allowedConfigs) + ")";
+ return String.format("defaultModeId=%d min=%.0f max=%.0f", defaultModeId,
+ refreshRateRange.min, refreshRateRange.max);
}
/**
* Checks whether the two objects have the same values.
@@ -577,17 +571,16 @@
return true;
}
- if (!(other instanceof DesiredDisplayConfigSpecs)) {
+ if (!(other instanceof DesiredDisplayModeSpecs)) {
return false;
}
- DesiredDisplayConfigSpecs desiredDisplayConfigSpecs =
- (DesiredDisplayConfigSpecs) other;
+ DesiredDisplayModeSpecs desiredDisplayModeSpecs = (DesiredDisplayModeSpecs) other;
- if (defaultModeId != desiredDisplayConfigSpecs.defaultModeId) {
+ if (defaultModeId != desiredDisplayModeSpecs.defaultModeId) {
return false;
}
- if (!refreshRateRange.equals(desiredDisplayConfigSpecs.refreshRateRange)) {
+ if (!refreshRateRange.equals(desiredDisplayModeSpecs.refreshRateRange)) {
return false;
}
return true;
@@ -597,6 +590,15 @@
public int hashCode() {
return Objects.hash(defaultModeId, refreshRateRange);
}
+
+ /**
+ * Copy values from the other object.
+ */
+ public void copyFrom(DesiredDisplayModeSpecs other) {
+ defaultModeId = other.defaultModeId;
+ refreshRateRange.min = other.refreshRateRange.min;
+ refreshRateRange.max = other.refreshRateRange.max;
+ }
}
@VisibleForTesting
@@ -932,7 +934,7 @@
mDefaultModeByDisplay.put(displayId, info.getDefaultMode());
}
if (changed) {
- notifyAllowedModesChangedLocked();
+ notifyDesiredDisplayModeSpecsChangedLocked();
}
}
}
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 2db1d03..f0a505d 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -18,13 +18,16 @@
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.PrintWriter;
import java.util.Arrays;
/**
* A helper class for handling access to illuminance hysteresis level values.
*/
-final class HysteresisLevels {
+@VisibleForTesting
+public class HysteresisLevels {
private static final String TAG = "HysteresisLevels";
// Default hysteresis constraints for brightening or darkening.
@@ -60,7 +63,7 @@
/**
* Return the brightening hysteresis threshold for the given value level.
*/
- float getBrighteningThreshold(float value) {
+ public float getBrighteningThreshold(float value) {
float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
float brightThreshold = value * (1.0f + brightConstant);
if (DEBUG) {
@@ -73,7 +76,7 @@
/**
* Return the darkening hysteresis threshold for the given value level.
*/
- float getDarkeningThreshold(float value) {
+ public float getDarkeningThreshold(float value) {
float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
float darkThreshold = value * (1.0f - darkConstant);
if (DEBUG) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 1d7c942..bf58efe 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -112,18 +112,19 @@
activeColorMode = Display.COLOR_MODE_INVALID;
}
int[] colorModes = SurfaceControl.getDisplayColorModes(displayToken);
- int[] allowedConfigs = SurfaceControl.getAllowedDisplayConfigs(displayToken);
+ SurfaceControl.DesiredDisplayConfigSpecs desiredDisplayConfigSpecs =
+ SurfaceControl.getDesiredDisplayConfigSpecs(displayToken);
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
if (device == null) {
// Display was added.
final boolean isInternal = mDevices.size() == 0;
- device = new LocalDisplayDevice(displayToken, physicalDisplayId,
- configs, activeConfig, allowedConfigs, colorModes, activeColorMode,
+ device = new LocalDisplayDevice(displayToken, physicalDisplayId, configs,
+ activeConfig, desiredDisplayConfigSpecs, colorModes, activeColorMode,
isInternal);
mDevices.put(physicalDisplayId, device);
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
} else if (device.updatePhysicalDisplayInfoLocked(configs, activeConfig,
- allowedConfigs, colorModes, activeColorMode)) {
+ desiredDisplayConfigSpecs, colorModes, activeColorMode)) {
// Display properties changed.
sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
}
@@ -172,12 +173,10 @@
private int mDefaultModeId;
private int mActiveModeId;
private boolean mActiveModeInvalid;
- private int[] mAllowedModeIds;
- private float mMinRefreshRate;
- private float mMaxRefreshRate;
- private boolean mAllowedModeIdsInvalid;
+ private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =
+ new DisplayModeDirector.DesiredDisplayModeSpecs();
+ private boolean mDisplayModeSpecsInvalid;
private int mActivePhysIndex;
- private int[] mAllowedPhysIndexes;
private int mActiveColorMode;
private boolean mActiveColorModeInvalid;
private Display.HdrCapabilities mHdrCapabilities;
@@ -188,13 +187,13 @@
LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,
SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
- int[] allowedDisplayInfos, int[] colorModes, int activeColorMode,
- boolean isInternal) {
+ SurfaceControl.DesiredDisplayConfigSpecs physicalDisplayConfigSpecs,
+ int[] colorModes, int activeColorMode, boolean isInternal) {
super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId);
mPhysicalDisplayId = physicalDisplayId;
mIsInternal = isInternal;
updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo,
- allowedDisplayInfos, colorModes, activeColorMode);
+ physicalDisplayConfigSpecs, colorModes, activeColorMode);
updateColorModesLocked(colorModes, activeColorMode);
mSidekickInternal = LocalServices.getService(SidekickInternal.class);
if (mIsInternal) {
@@ -213,10 +212,10 @@
public boolean updatePhysicalDisplayInfoLocked(
SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo,
- int[] allowedDisplayInfos, int[] colorModes, int activeColorMode) {
+ SurfaceControl.DesiredDisplayConfigSpecs physicalDisplayConfigSpecs,
+ int[] colorModes, int activeColorMode) {
mDisplayInfos = Arrays.copyOf(physicalDisplayInfos, physicalDisplayInfos.length);
mActivePhysIndex = activeDisplayInfo;
- mAllowedPhysIndexes = Arrays.copyOf(allowedDisplayInfos, allowedDisplayInfos.length);
// Build an updated list of all existing modes.
ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
boolean modesAdded = false;
@@ -264,6 +263,26 @@
sendTraversalRequestLocked();
}
+ // 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 =
+ 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
+ || mDisplayModeSpecs.refreshRateRange.min
+ != physicalDisplayConfigSpecs.minRefreshRate
+ || mDisplayModeSpecs.refreshRateRange.max
+ != physicalDisplayConfigSpecs.maxRefreshRate) {
+ mDisplayModeSpecsInvalid = true;
+ sendTraversalRequestLocked();
+ }
+ }
+ }
+
boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
// If the records haven't changed then we're done here.
if (!recordsChanged) {
@@ -286,6 +305,17 @@
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) {
+ Slog.w(TAG,
+ "DisplayModeSpecs default mode no longer available, using currently"
+ + " active mode as default.");
+ }
+ mDisplayModeSpecs.defaultModeId = activeRecord.mMode.getModeId();
+ mDisplayModeSpecsInvalid = true;
+ }
+
// Determine whether the active mode is still there.
if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
if (mActiveModeId != 0) {
@@ -296,21 +326,6 @@
mActiveModeInvalid = true;
}
- // Determine what the currently allowed modes are
- mAllowedModeIds = new int[] { mActiveModeId };
- int[] allowedModeIds = new int[mAllowedPhysIndexes.length];
- int size = 0;
- for (int physIndex : mAllowedPhysIndexes) {
- int modeId = findMatchingModeIdLocked(physIndex);
- if (modeId > 0) {
- allowedModeIds[size++] = modeId;
- }
- }
-
- // If this is different from our desired allowed modes, then mark our current set as
- // invalid so we correct this on the next traversal.
- mAllowedModeIdsInvalid = !Arrays.equals(allowedModeIds, mAllowedModeIds);
-
// Schedule traversals so that we apply pending changes.
sendTraversalRequestLocked();
return true;
@@ -410,6 +425,9 @@
final DisplayAddress.Physical physicalAddress =
DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId);
mInfo.address = physicalAddress;
+ mInfo.densityDpi = (int) (phys.density * 160 + 0.5f);
+ mInfo.xDpi = phys.xDpi;
+ mInfo.yDpi = phys.yDpi;
// Assume that all built-in displays that have secure output (eg. HDCP) also
// support compositing from gralloc protected buffers.
@@ -436,9 +454,6 @@
mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
mInfo.width, mInfo.height);
mInfo.type = Display.TYPE_BUILT_IN;
- mInfo.densityDpi = (int)(phys.density * 160 + 0.5f);
- mInfo.xDpi = phys.xDpi;
- mInfo.yDpi = phys.yDpi;
mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
} else {
mInfo.displayCutout = null;
@@ -447,7 +462,6 @@
mInfo.name = getContext().getResources().getString(
com.android.internal.R.string.display_manager_hdmi_display_name);
mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
- mInfo.setAssumedDensityForExternalDisplay(phys.width, phys.height);
// For demonstration purposes, allow rotation of the external display.
// In the future we might allow the user to configure this directly.
@@ -625,10 +639,40 @@
}
@Override
- public void setDesiredDisplayConfigSpecs(int defaultModeId, float minRefreshRate,
- float maxRefreshRate, int[] modes) {
- updateDesiredDisplayConfigSpecs(defaultModeId, minRefreshRate, maxRefreshRate);
- updateAllowedModesLocked(modes);
+ public void setDesiredDisplayModeSpecsLocked(
+ DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
+ if (displayModeSpecs.defaultModeId == 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) {
+ // 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
+ // possible we'll get called with a stale mode id that no longer represents a valid
+ // 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);
+ updateDeviceInfoLocked();
+ return;
+ }
+ if (mDisplayModeSpecsInvalid || !displayModeSpecs.equals(mDisplayModeSpecs)) {
+ mDisplayModeSpecsInvalid = false;
+ mDisplayModeSpecs.copyFrom(displayModeSpecs);
+ final IBinder token = getDisplayTokenLocked();
+ SurfaceControl.setDesiredDisplayConfigSpecs(token,
+ new SurfaceControl.DesiredDisplayConfigSpecs(defaultPhysIndex,
+ mDisplayModeSpecs.refreshRateRange.min,
+ mDisplayModeSpecs.refreshRateRange.max));
+ int activePhysIndex = SurfaceControl.getActiveConfig(token);
+ if (updateActiveModeLocked(activePhysIndex)) {
+ updateDeviceInfoLocked();
+ }
+ }
}
@Override
@@ -651,107 +695,11 @@
mActiveModeInvalid = mActiveModeId == 0;
if (mActiveModeInvalid) {
Slog.w(TAG, "In unknown mode after setting allowed configs"
- + ": allowedPhysIndexes=" + mAllowedPhysIndexes
+ ", activePhysIndex=" + mActivePhysIndex);
}
return true;
}
- // TODO(b/142507213): Remove once refresh rates are plummed through to kernel.
- public void updateAllowedModesLocked(int[] allowedModes) {
- if (Arrays.equals(allowedModes, mAllowedModeIds) && !mAllowedModeIdsInvalid) {
- return;
- }
- if (updateAllowedModesInternalLocked(allowedModes)) {
- updateDeviceInfoLocked();
- }
- }
-
- public void updateDesiredDisplayConfigSpecs(int defaultModeId, float minRefreshRate,
- float maxRefreshRate) {
- if (minRefreshRate == mMinRefreshRate
- && maxRefreshRate == mMaxRefreshRate
- && defaultModeId == mDefaultModeId) {
- return;
- }
- if (updateDesiredDisplayConfigSpecsInternalLocked(defaultModeId, minRefreshRate,
- maxRefreshRate)) {
- updateDeviceInfoLocked();
- }
- }
-
- public boolean updateDesiredDisplayConfigSpecsInternalLocked(int defaultModeId,
- float minRefreshRate, float maxRefreshRate) {
- if (DEBUG) {
- Slog.w(TAG, "updateDesiredDisplayConfigSpecsInternalLocked("
- + "defaultModeId="
- + Integer.toString(defaultModeId)
- + ", minRefreshRate="
- + Float.toString(minRefreshRate)
- + ", maxRefreshRate="
- + Float.toString(minRefreshRate));
- }
-
- final IBinder token = getDisplayTokenLocked();
- SurfaceControl.setDesiredDisplayConfigSpecs(token,
- new SurfaceControl.DesiredDisplayConfigSpecs(
- defaultModeId, minRefreshRate, maxRefreshRate));
- int activePhysIndex = SurfaceControl.getActiveConfig(token);
- return updateActiveModeLocked(activePhysIndex);
- }
-
- public boolean updateAllowedModesInternalLocked(int[] allowedModes) {
- if (DEBUG) {
- Slog.w(TAG, "updateAllowedModesInternalLocked(allowedModes="
- + Arrays.toString(allowedModes) + ")");
- }
- int[] allowedPhysIndexes = new int[allowedModes.length];
- int size = 0;
- for (int modeId : allowedModes) {
- int physIndex = findDisplayInfoIndexLocked(modeId);
- if (physIndex < 0) {
- Slog.w(TAG, "Requested mode ID " + modeId + " not available,"
- + " dropping from allowed set.");
- } else {
- allowedPhysIndexes[size++] = physIndex;
- }
- }
-
- // If we couldn't find one or more of the suggested allowed modes then we need to
- // shrink the array to its actual size.
- if (size != allowedModes.length) {
- allowedPhysIndexes = Arrays.copyOf(allowedPhysIndexes, size);
- }
-
- // If we found no suitable modes, then we try again with the default mode which we
- // assume has a suitable physical config.
- if (size == 0) {
- if (DEBUG) {
- Slog.w(TAG, "No valid modes allowed, falling back to default mode (id="
- + mDefaultModeId + ")");
- }
- allowedModes = new int[] { mDefaultModeId };
- allowedPhysIndexes = new int[] { findDisplayInfoIndexLocked(mDefaultModeId) };
- }
-
- mAllowedModeIds = allowedModes;
- mAllowedModeIdsInvalid = false;
-
- if (Arrays.equals(mAllowedPhysIndexes, allowedPhysIndexes)) {
- return false;
- }
- mAllowedPhysIndexes = allowedPhysIndexes;
-
- if (DEBUG) {
- Slog.w(TAG, "Setting allowed physical configs: allowedPhysIndexes="
- + Arrays.toString(allowedPhysIndexes));
- }
-
- SurfaceControl.setAllowedDisplayConfigs(getDisplayTokenLocked(), allowedPhysIndexes);
- int activePhysIndex = SurfaceControl.getActiveConfig(getDisplayTokenLocked());
- return updateActiveModeLocked(activePhysIndex);
- }
-
public boolean requestColorModeLocked(int colorMode) {
if (mActiveColorMode == colorMode) {
return false;
@@ -771,11 +719,8 @@
public void dumpLocked(PrintWriter pw) {
super.dumpLocked(pw);
pw.println("mPhysicalDisplayId=" + mPhysicalDisplayId);
- pw.println("mAllowedPhysIndexes=" + Arrays.toString(mAllowedPhysIndexes));
- pw.println("mAllowedModeIds=" + Arrays.toString(mAllowedModeIds));
- pw.println("mMinRefreshRate=" + mMinRefreshRate);
- pw.println("mMaxRefreshRate=" + mMaxRefreshRate);
- pw.println("mAllowedModeIdsInvalid=" + mAllowedModeIdsInvalid);
+ pw.println("mDisplayModeSpecs={" + mDisplayModeSpecs + "}");
+ pw.println("mDisplayModeSpecsInvalid=" + mDisplayModeSpecsInvalid);
pw.println("mActivePhysIndex=" + mActivePhysIndex);
pw.println("mActiveModeId=" + mActiveModeId);
pw.println("mActiveColorMode=" + mActiveColorMode);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index f4b2dc8..b649a50 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -87,9 +87,11 @@
// True if the logical display has unique content.
private boolean mHasContent;
- private int[] mAllowedDisplayModes = new int[0];
private int mRequestedColorMode;
+ private DisplayModeDirector.DesiredDisplayModeSpecs mDesiredDisplayModeSpecs =
+ new DisplayModeDirector.DesiredDisplayModeSpecs();
+
// The display offsets to apply to the display projection.
private int mDisplayOffsetX;
private int mDisplayOffsetY;
@@ -352,12 +354,12 @@
// Set the color mode and allowed display mode.
if (device == mPrimaryDisplayDevice) {
- // See ag/9588196 for correct values.
- device.setDesiredDisplayConfigSpecs(0, 60, 60, mAllowedDisplayModes);
+ device.setDesiredDisplayModeSpecsLocked(mDesiredDisplayModeSpecs);
device.setRequestedColorModeLocked(mRequestedColorMode);
} else {
// Reset to default for non primary displays
- device.setDesiredDisplayConfigSpecs(0, 60, 60, new int[] {0});
+ device.setDesiredDisplayModeSpecsLocked(
+ new DisplayModeDirector.DesiredDisplayModeSpecs());
device.setRequestedColorModeLocked(0);
}
@@ -462,17 +464,18 @@
}
/**
- * Sets the display modes the system is free to switch between.
+ * Sets the display configs the system can use.
*/
- public void setAllowedDisplayModesLocked(int[] modes) {
- mAllowedDisplayModes = modes;
+ public void setDesiredDisplayModeSpecsLocked(
+ DisplayModeDirector.DesiredDisplayModeSpecs specs) {
+ mDesiredDisplayModeSpecs = specs;
}
/**
- * Returns the display modes the system is free to switch between.
+ * Returns the display configs the system can choose.
*/
- public int[] getAllowedDisplayModesLocked() {
- return mAllowedDisplayModes;
+ public DisplayModeDirector.DesiredDisplayModeSpecs getDesiredDisplayModeSpecsLocked() {
+ return mDesiredDisplayModeSpecs;
}
/**
@@ -531,7 +534,7 @@
pw.println("mDisplayId=" + mDisplayId);
pw.println("mLayerStack=" + mLayerStack);
pw.println("mHasContent=" + mHasContent);
- pw.println("mAllowedDisplayModes=" + Arrays.toString(mAllowedDisplayModes));
+ pw.println("mDesiredDisplayModeSpecs={" + mDesiredDisplayModeSpecs + "}");
pw.println("mRequestedColorMode=" + mRequestedColorMode);
pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")");
pw.println("mDisplayScalingDisabled=" + mDisplayScalingDisabled);
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 739dd64..b6255d1 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -315,9 +315,9 @@
}
@Override
- public void setDesiredDisplayConfigSpecs(int defaultModeId, float minRefreshRate,
- float maxRefreshRate, int[] modes) {
- final int id = defaultModeId;
+ public void setDesiredDisplayModeSpecsLocked(
+ DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
+ final int id = displayModeSpecs.defaultModeId;
int index = -1;
if (id == 0) {
// Use the default.
diff --git a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
index a91fe77..1cf27ff 100644
--- a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
+++ b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
@@ -231,7 +231,8 @@
// a Sim with a different mcc code was found
neededNow = false;
}
- String simOperator = mTelephonyManager.getSimOperator(info.getSubscriptionId());
+ String simOperator = mTelephonyManager
+ .createForSubscriptionId(info.getSubscriptionId()).getSimOperator();
mcc = 0;
if (simOperator != null && simOperator.length() >= 3) {
mcc = Integer.parseInt(simOperator.substring(0, 3));
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
index 52cede2..23b5c14 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java
@@ -250,7 +250,11 @@
new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_UP),
// No Android keycode defined for CEC_KEYCODE_LEFT_DOWN
new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_DOWN),
+ // Both KEYCODE_HOME and KEYCODE_MENU keys are sent as CEC_KEYCODE_ROOT_MENU
+ // NOTE that the HOME key is not usually forwarded.
+ // When CEC_KEYCODE_ROOT_MENU is received, it is translated to KEYCODE_HOME
new KeycodeEntry(KeyEvent.KEYCODE_HOME, CEC_KEYCODE_ROOT_MENU),
+ new KeycodeEntry(KeyEvent.KEYCODE_MENU, CEC_KEYCODE_ROOT_MENU),
new KeycodeEntry(KeyEvent.KEYCODE_SETTINGS, CEC_KEYCODE_SETUP_MENU),
new KeycodeEntry(KeyEvent.KEYCODE_TV_CONTENTS_MENU, CEC_KEYCODE_CONTENTS_MENU, false),
// No Android keycode defined for CEC_KEYCODE_FAVORITE_MENU
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
new file mode 100644
index 0000000..d673ec8
--- /dev/null
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
@@ -0,0 +1,161 @@
+/*
+ * 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.incremental;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.DataLoaderManager;
+import android.content.pm.DataLoaderParamsParcel;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.IDataLoader;
+import android.content.pm.IDataLoaderStatusListener;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.incremental.IIncrementalManager;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+
+/**
+ * This service has the following purposes:
+ * 1) Starts the IIncrementalManager binder service.
+ * 1) Starts the native IIncrementalManagerService binder service.
+ * 2) Handles shell commands for "incremental" service.
+ * 3) Handles binder calls from the native IIncrementalManagerService binder service and pass
+ * them to a data loader binder service.
+ */
+
+public class IncrementalManagerService extends IIncrementalManager.Stub {
+ private static final String TAG = "IncrementalManagerService";
+ private static final String BINDER_SERVICE_NAME = "incremental";
+ // DataLoaderManagerService should have been started before us
+ private @NonNull DataLoaderManager mDataLoaderManager;
+ private long mNativeInstance;
+ private final @NonNull Context mContext;
+
+ /**
+ * Starts IIncrementalManager binder service and register to Service Manager.
+ * Starts the native IIncrementalManagerNative binder service.
+ */
+ public static IncrementalManagerService start(Context context) {
+ IncrementalManagerService self = new IncrementalManagerService(context);
+ if (self.mNativeInstance == 0) {
+ return null;
+ }
+ return self;
+ }
+
+ private IncrementalManagerService(Context context) {
+ mContext = context;
+ mDataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
+ ServiceManager.addService(BINDER_SERVICE_NAME, this);
+ // Starts and register IIncrementalManagerNative service
+ mNativeInstance = nativeStartService();
+ }
+
+ /**
+ * Notifies native IIncrementalManager service that system is ready.
+ */
+ public void systemReady() {
+ nativeSystemReady(mNativeInstance);
+ }
+
+ /**
+ * Finds data loader service provider and binds to it. This requires PackageManager.
+ */
+ @Override
+ public boolean prepareDataLoader(int mountId, FileSystemControlParcel control,
+ DataLoaderParamsParcel params,
+ IDataLoaderStatusListener listener) {
+ Bundle dataLoaderParams = new Bundle();
+ dataLoaderParams.putParcelable("componentName",
+ new ComponentName(params.packageName, params.className));
+ dataLoaderParams.putParcelable("control", control);
+ dataLoaderParams.putParcelable("params", params);
+ DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
+ if (dataLoaderManager == null) {
+ Slog.e(TAG, "Failed to find data loader manager service");
+ return false;
+ }
+ if (!dataLoaderManager.initializeDataLoader(mountId, dataLoaderParams, listener)) {
+ Slog.e(TAG, "Failed to initialize data loader");
+ return false;
+ }
+ return true;
+ }
+
+
+ @Override
+ public boolean startDataLoader(int mountId) {
+ IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
+ if (dataLoader == null) {
+ Slog.e(TAG, "Start failed to retrieve data loader for ID=" + mountId);
+ return false;
+ }
+ try {
+ dataLoader.start();
+ return true;
+ } catch (RemoteException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public void destroyDataLoader(int mountId) {
+ IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
+ if (dataLoader == null) {
+ Slog.e(TAG, "Destroy failed to retrieve data loader for ID=" + mountId);
+ return;
+ }
+ try {
+ dataLoader.destroy();
+ } catch (RemoteException ex) {
+ return;
+ }
+ }
+
+ // TODO: remove this
+ @Override
+ public void newFileForDataLoader(int mountId, long inode, byte[] metadata) {
+ IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
+ if (dataLoader == null) {
+ Slog.e(TAG, "Failed to retrieve data loader for ID=" + mountId);
+ return;
+ }
+ }
+
+ @Override
+ public void showHealthBlockedUI(int mountId) {
+ // TODO(b/136132412): implement this
+ }
+
+ @Override
+ public void onShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
+ FileDescriptor err, @NonNull String[] args, ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ (new IncrementalManagerShellCommand(mContext)).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private static native long nativeStartService();
+
+ private static native void nativeSystemReady(long nativeInstance);
+}
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
new file mode 100644
index 0000000..5c18f58
--- /dev/null
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -0,0 +1,299 @@
+/*
+ * 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.incremental;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.InstallationFile;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Defines actions to handle adb commands like "adb abb incremental ...".
+ */
+public final class IncrementalManagerShellCommand extends ShellCommand {
+ private static final String TAG = "IncrementalShellCommand";
+ // Assuming the adb data loader is always installed on the device
+ private static final String LOADER_PACKAGE_NAME = "com.android.incremental.nativeadb";
+ private static final String LOADER_CLASS_NAME =
+ LOADER_PACKAGE_NAME + ".NativeAdbDataLoaderService";
+ private final @NonNull Context mContext;
+
+ private static final int ERROR_INVALID_ARGUMENTS = -1;
+ private static final int ERROR_DATA_LOADER_INIT = -2;
+ private static final int ERROR_COMMAND_EXECUTION = -3;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ERROR_INVALID_ARGUMENTS, ERROR_DATA_LOADER_INIT, ERROR_COMMAND_EXECUTION})
+ public @interface IncrementalShellCommandErrorCode {
+ }
+
+ IncrementalManagerShellCommand(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public int onCommand(@Nullable String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(null);
+ }
+ switch (cmd) {
+ case "install-start":
+ return runInstallStart();
+ case "install-finish":
+ return runInstallFinish();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Incremental Service Commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" install-start");
+ pw.println(" Opens an installation session");
+ pw.println(" install-finish SESSION_ID --file NAME:SIZE:INDEX --file NAME:SIZE:INDEX ...");
+ pw.println(" Commits an installation session specified by session ID for an APK ");
+ pw.println(" or a bundle of splits. Configures lib dirs or OBB files if specified.");
+ }
+
+ private int runInstallStart() {
+ final PrintWriter pw = getOutPrintWriter();
+ final PackageInstaller packageInstaller =
+ mContext.getPackageManager().getPackageInstaller();
+ if (packageInstaller == null) {
+ pw.println("Failed to get PackageInstaller.");
+ return ERROR_COMMAND_EXECUTION;
+ }
+
+ final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = getDataLoaderDynamicArgs();
+ if (dataLoaderDynamicArgs == null) {
+ pw.println("File names and sizes don't match.");
+ return ERROR_DATA_LOADER_INIT;
+ }
+ final DataLoaderParams params = DataLoaderParams.forIncremental(
+ new ComponentName(LOADER_PACKAGE_NAME, LOADER_CLASS_NAME), "",
+ dataLoaderDynamicArgs);
+ PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ sessionParams.installFlags |= PackageManager.INSTALL_ALL_USERS;
+ // Replace existing if same package is already installed
+ sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+ sessionParams.setDataLoaderParams(params);
+
+ try {
+ int sessionId = packageInstaller.createSession(sessionParams);
+ pw.println("Successfully opened session: sessionId = " + sessionId);
+ } catch (Exception ex) {
+ pw.println("Failed to create session.");
+ return ERROR_COMMAND_EXECUTION;
+ } finally {
+ try {
+ for (Map.Entry<String, ParcelFileDescriptor> nfd
+ : dataLoaderDynamicArgs.entrySet()) {
+ nfd.getValue().close();
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ return 0;
+ }
+
+ private int runInstallFinish() {
+ final PrintWriter pw = getOutPrintWriter();
+ final int sessionId = parseInt(getNextArgRequired());
+ final List<InstallationFile> installationFiles = parseFileArgs(pw);
+ if (installationFiles == null) {
+ pw.println("Must specify at least one file to install.");
+ return ERROR_INVALID_ARGUMENTS;
+ }
+ final int numFiles = installationFiles.size();
+ if (numFiles == 0) {
+ pw.println("Must specify at least one file to install.");
+ return ERROR_INVALID_ARGUMENTS;
+ }
+
+ final PackageInstaller packageInstaller = mContext.getPackageManager()
+ .getPackageInstaller();
+ if (packageInstaller == null) {
+ pw.println("Failed to get PackageInstaller.");
+ return ERROR_COMMAND_EXECUTION;
+ }
+
+ final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
+ boolean success = false;
+
+ PackageInstaller.Session session = null;
+ try {
+ session = packageInstaller.openSession(sessionId);
+ for (int i = 0; i < numFiles; i++) {
+ InstallationFile file = installationFiles.get(i);
+ session.addFile(file.getName(), file.getSize(), file.getMetadata());
+ }
+ session.commit(localReceiver.getIntentSender());
+ final Intent result = localReceiver.getResult();
+ final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ success = true;
+ pw.println("Success");
+ return 0;
+ } else {
+ pw.println("Failure ["
+ + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+ return ERROR_COMMAND_EXECUTION;
+ }
+ } catch (Exception e) {
+ e.printStackTrace(pw);
+ return ERROR_COMMAND_EXECUTION;
+ } finally {
+ if (!success) {
+ try {
+ if (session != null) {
+ session.abandon();
+ }
+ } catch (Exception ignore) {
+ }
+ }
+ }
+ }
+
+ private static class LocalIntentReceiver {
+ private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
+
+ private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+ @Override
+ public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+ IIntentReceiver finishedReceiver, String requiredPermission,
+ Bundle options) {
+ try {
+ mResult.offer(intent, 5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+ public IntentSender getIntentSender() {
+ return new IntentSender((IIntentSender) mLocalSender);
+ }
+
+ public Intent getResult() {
+ try {
+ return mResult.take();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /** Helpers. */
+ private Map<String, ParcelFileDescriptor> getDataLoaderDynamicArgs() {
+ Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>();
+ final FileDescriptor outFd = getOutFileDescriptor();
+ final FileDescriptor inFd = getInFileDescriptor();
+ try {
+ dataLoaderDynamicArgs.put("inFd", ParcelFileDescriptor.dup(inFd));
+ dataLoaderDynamicArgs.put("outFd", ParcelFileDescriptor.dup(outFd));
+ return dataLoaderDynamicArgs;
+ } catch (Exception ex) {
+ Slog.e(TAG, "Failed to dup FDs");
+ return null;
+ }
+ }
+
+ private long parseLong(String arg) {
+ long result = -1;
+ try {
+ result = Long.parseLong(arg);
+ } catch (NumberFormatException e) {
+ }
+ return result;
+ }
+
+ private int parseInt(String arg) {
+ int result = -1;
+ try {
+ result = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ }
+ return result;
+ }
+
+ private List<InstallationFile> parseFileArgs(PrintWriter pw) {
+ List<InstallationFile> fileList = new ArrayList<>();
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--file": {
+ final String fileArgs = getNextArgRequired();
+ final String[] args = fileArgs.split(":");
+ if (args.length != 3) {
+ pw.println("Invalid file args: " + fileArgs);
+ return null;
+ }
+ final String name = args[0];
+ final long size = parseLong(args[1]);
+ if (size < 0) {
+ pw.println("Invalid file size in: " + fileArgs);
+ return null;
+ }
+ final long index = parseLong(args[2]);
+ if (index < 0) {
+ pw.println("Invalid file index in: " + fileArgs);
+ return null;
+ }
+ final byte[] metadata = String.valueOf(index).getBytes(StandardCharsets.UTF_8);
+ fileList.add(new InstallationFile(name, size, metadata));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return fileList;
+ }
+}
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index d71ffb7..58f6ba2 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -762,6 +762,7 @@
if (mUpdatingPackageNames != null) {
pw.print("Packages being updated: "); pw.println(mUpdatingPackageNames);
}
+ dumpSupportedUsers(pw, prefix);
if (mServiceNameResolver != null) {
pw.print(prefix); pw.print("Name resolver: ");
mServiceNameResolver.dumpShort(pw); pw.println();
diff --git a/services/core/java/com/android/server/input/ConfigurationProcessor.java b/services/core/java/com/android/server/input/ConfigurationProcessor.java
index 970e86a..3888b1b 100644
--- a/services/core/java/com/android/server/input/ConfigurationProcessor.java
+++ b/services/core/java/com/android/server/input/ConfigurationProcessor.java
@@ -17,7 +17,6 @@
package com.android.server.input;
import android.text.TextUtils;
-import android.util.Pair;
import android.util.Slog;
import android.util.Xml;
@@ -29,7 +28,9 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
class ConfigurationProcessor {
@@ -86,9 +87,9 @@
* the second item in the pair is the display port.
*/
@VisibleForTesting
- static List<Pair<String, String>> processInputPortAssociations(InputStream xml)
+ static Map<String, Integer> processInputPortAssociations(InputStream xml)
throws Exception {
- List<Pair<String, String>> associations = new ArrayList<>();
+ Map<String, Integer> associations = new HashMap<String, Integer>();
try (InputStreamReader confReader = new InputStreamReader(xml)) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(confReader);
@@ -101,19 +102,18 @@
break;
}
String inputPort = parser.getAttributeValue(null, "input");
- String displayPort = parser.getAttributeValue(null, "display");
- if (TextUtils.isEmpty(inputPort) || TextUtils.isEmpty(displayPort)) {
+ String displayPortStr = parser.getAttributeValue(null, "display");
+ if (TextUtils.isEmpty(inputPort) || TextUtils.isEmpty(displayPortStr)) {
// This is likely an error by an OEM during device configuration
Slog.wtf(TAG, "Ignoring incomplete entry");
continue;
}
try {
- Integer.parseUnsignedInt(displayPort);
+ int displayPort = Integer.parseUnsignedInt(displayPortStr);
+ associations.put(inputPort, displayPort);
} catch (NumberFormatException e) {
Slog.wtf(TAG, "Display port should be an integer");
- continue;
}
- associations.add(new Pair<>(inputPort, displayPort));
}
}
return associations;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 16b7d99..bf73aa3 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -64,7 +64,6 @@
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -113,8 +112,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
-
/*
* Wraps the C++ InputManager and provides its callbacks.
*/
@@ -184,6 +183,10 @@
IInputFilter mInputFilter; // guarded by mInputFilterLock
InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
+ // 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 static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
private static native void nativeStart(long ptr);
@@ -314,6 +317,7 @@
this.mContext = context;
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
+ mStaticAssociations = loadStaticInputPortAssociations();
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
@@ -1727,6 +1731,17 @@
String dumpStr = nativeDump(mPtr);
if (dumpStr != null) {
pw.println(dumpStr);
+ dumpAssociations(pw);
+ }
+ }
+
+ private void dumpAssociations(PrintWriter pw) {
+ if (!mStaticAssociations.isEmpty()) {
+ pw.println("Static Associations:");
+ mStaticAssociations.forEach((k, v) -> {
+ pw.print(" port: " + k);
+ pw.println(" display: " + v);
+ });
}
}
@@ -1910,15 +1925,16 @@
}
/**
- * Flatten a list of pairs into a list, with value positioned directly next to the key
+ * Flatten a map into a string list, with value positioned directly next to the
+ * key.
* @return Flattened list
*/
- private static <T> List<T> flatten(@NonNull List<Pair<T, T>> pairs) {
- List<T> list = new ArrayList<>(pairs.size() * 2);
- for (Pair<T, T> pair : pairs) {
- list.add(pair.first);
- list.add(pair.second);
- }
+ private static List<String> flatten(@NonNull Map<String, Integer> map) {
+ List<String> list = new ArrayList<>(map.size() * 2);
+ map.forEach((k, v)-> {
+ list.add(k);
+ list.add(v.toString());
+ });
return list;
}
@@ -1926,23 +1942,26 @@
* Ports are highly platform-specific, so only allow these to be specified in the vendor
* directory.
*/
- // Native callback
- private static String[] getInputPortAssociations() {
+ private static Map<String, Integer> loadStaticInputPortAssociations() {
File baseDir = Environment.getVendorDirectory();
File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
try {
InputStream stream = new FileInputStream(confFile);
- List<Pair<String, String>> associations =
- ConfigurationProcessor.processInputPortAssociations(stream);
- List<String> associationList = flatten(associations);
- return associationList.toArray(new String[0]);
+ return ConfigurationProcessor.processInputPortAssociations(stream);
} catch (FileNotFoundException e) {
// Most of the time, file will not exist, which is expected.
} catch (Exception e) {
Slog.e(TAG, "Could not parse '" + confFile.getAbsolutePath() + "'", e);
}
- return new String[0];
+
+ return new HashMap<>();
+ }
+
+ // Native callback
+ private String[] getInputPortAssociations() {
+ List<String> associationList = flatten(mStaticAssociations);
+ 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 944a95d..44c8971 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,8 +18,14 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputMethodInfo;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.server.LocalServices;
import java.util.Collections;
@@ -57,6 +63,17 @@
public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
/**
+ * Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from
+ * the input method.
+ *
+ * @param componentName {@link ComponentName} of current app/activity.
+ * @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);
+
+ /**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
*/
private static final InputMethodManagerInternal NOP =
@@ -78,6 +95,17 @@
public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
return Collections.emptyList();
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("IMManagerInternal", "RemoteException calling"
+ + " onInlineSuggestionsUnsupported: " + e);
+ }
+ }
};
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 471fa72..5865dc4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -109,6 +109,7 @@
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -140,6 +141,7 @@
import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodClient;
@@ -211,6 +213,8 @@
static final int MSG_SYSTEM_UNLOCK_USER = 5000;
+ static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000;
+
static final long TIME_TO_RECONNECT = 3 * 1000;
static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
@@ -1785,6 +1789,16 @@
return settings.getEnabledInputMethodListLocked();
}
+ @GuardedBy("mMethodMap")
+ private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+ if (mCurMethod != null) {
+ executeOrSendMessage(mCurMethod,
+ mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod,
+ componentName, autofillId, callback));
+ }
+ }
+
/**
* @param imiId if null, returns enabled subtypes for the current imi
* @return enabled subtypes of the specified imi
@@ -3874,6 +3888,21 @@
final int userId = msg.arg1;
onUnlockUser(userId);
return true;
+
+ // ---------------------------------------------------------------
+ case MSG_INLINE_SUGGESTIONS_REQUEST:
+ args = (SomeArgs) msg.obj;
+ final ComponentName componentName = (ComponentName) args.arg2;
+ final AutofillId autofillId = (AutofillId) args.arg3;
+ final IInlineSuggestionsRequestCallback callback =
+ (IInlineSuggestionsRequestCallback) args.arg4;
+ try {
+ ((IInputMethod) args.arg1).onCreateInlineSuggestionsRequest(componentName,
+ autofillId, callback);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e);
+ }
+ return true;
}
return false;
}
@@ -4434,6 +4463,13 @@
}
}
+ private void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+ synchronized (mMethodMap) {
+ onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback);
+ }
+ }
+
private static final class LocalServiceImpl extends InputMethodManagerInternal {
@NonNull
private final InputMethodManagerService mService;
@@ -4464,6 +4500,12 @@
public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
return mService.getEnabledInputMethodListAsUser(userId);
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ }
}
@BinderThread
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 02e29e0..c13d55a 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -59,10 +59,12 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.InputChannel;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
import android.view.inputmethod.InputMethodInfo;
@@ -82,6 +84,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
@@ -187,6 +190,18 @@
@UserIdInt int userId) {
return userIdToInputMethodInfoMapper.getAsList(userId);
}
+
+ @Override
+ public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ try {
+ //TODO(b/137800469): support multi client IMEs.
+ cb.onInlineSuggestionsUnsupported();
+ } catch (RemoteException e) {
+ Log.w("MultiClientIMManager", "RemoteException calling"
+ + " onInlineSuggestionsUnsupported: " + e);
+ }
+ }
});
}
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerService.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerService.java
new file mode 100644
index 0000000..3762ebb
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerService.java
@@ -0,0 +1,43 @@
+/*
+ * 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.integrity;
+
+import android.content.Context;
+
+import com.android.server.SystemService;
+
+/**
+ * Service that manages app integrity rules and verifications.
+ *
+ * @hide
+ */
+public class AppIntegrityManagerService extends SystemService {
+
+ private Context mContext;
+ private AppIntegrityManagerServiceImpl mService;
+
+ public AppIntegrityManagerService(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void onStart() {
+ mService = AppIntegrityManagerServiceImpl.create(mContext);
+ publishBinderService(Context.APP_INTEGRITY_SERVICE, mService);
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
new file mode 100644
index 0000000..6c80a88
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -0,0 +1,558 @@
+/*
+ * 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.integrity;
+
+import static android.content.Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION;
+import static android.content.Intent.EXTRA_ORIGINATING_UID;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_VERSION_CODE;
+import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
+import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
+import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.IAppIntegrityManager;
+import android.content.integrity.Rule;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.Signature;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.integrity.engine.RuleEvaluationEngine;
+import com.android.server.integrity.model.IntegrityCheckResult;
+import com.android.server.integrity.model.RuleMetadata;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Implementation of {@link AppIntegrityManagerService}. */
+public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
+ private static final String TAG = "AppIntegrityManagerServiceImpl";
+
+ private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+ private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+ private static final String PACKAGE_INSTALLER = "com.google.android.packageinstaller";
+ private static final String BASE_APK_FILE = "base.apk";
+ private static final String ALLOWED_INSTALLERS_METADATA_NAME = "allowed-installers";
+ private static final String ALLOWED_INSTALLER_DELIMITER = ",";
+ private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
+
+ private static final String ADB_INSTALLER = "adb";
+ private static final String UNKNOWN_INSTALLER = "";
+ private static final String INSTALLER_CERT_NOT_APPLICABLE = "";
+
+ // Access to files inside mRulesDir is protected by mRulesLock;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final PackageManagerInternal mPackageManagerInternal;
+ private final RuleEvaluationEngine mEvaluationEngine;
+ private final IntegrityFileManager mIntegrityFileManager;
+
+ /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */
+ public static AppIntegrityManagerServiceImpl create(Context context) {
+ HandlerThread handlerThread = new HandlerThread("AppIntegrityManagerServiceHandler");
+ handlerThread.start();
+
+ return new AppIntegrityManagerServiceImpl(
+ context,
+ LocalServices.getService(PackageManagerInternal.class),
+ RuleEvaluationEngine.getRuleEvaluationEngine(),
+ IntegrityFileManager.getInstance(),
+ handlerThread.getThreadHandler());
+ }
+
+ @VisibleForTesting
+ AppIntegrityManagerServiceImpl(
+ Context context,
+ PackageManagerInternal packageManagerInternal,
+ RuleEvaluationEngine evaluationEngine,
+ IntegrityFileManager integrityFileManager,
+ Handler handler) {
+ mContext = context;
+ mPackageManagerInternal = packageManagerInternal;
+ mEvaluationEngine = evaluationEngine;
+ mIntegrityFileManager = integrityFileManager;
+ mHandler = handler;
+
+ IntentFilter integrityVerificationFilter = new IntentFilter();
+ integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
+ try {
+ integrityVerificationFilter.addDataType(PACKAGE_MIME_TYPE);
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ throw new RuntimeException("Mime type malformed: should never happen.", e);
+ }
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION.equals(
+ intent.getAction())) {
+ return;
+ }
+ mHandler.post(() -> handleIntegrityVerification(intent));
+ }
+ },
+ integrityVerificationFilter,
+ /* broadcastPermission= */ null,
+ mHandler);
+ }
+
+ @Override
+ public void updateRuleSet(
+ String version, ParceledListSlice<Rule> rules, IntentSender statusReceiver)
+ throws RemoteException {
+ String ruleProvider = getCallerPackageNameOrThrow();
+
+ mHandler.post(
+ () -> {
+ boolean success = true;
+ try {
+ mIntegrityFileManager.writeRules(version, ruleProvider, rules.getList());
+ } catch (Exception e) {
+ Slog.e(TAG, "Error writing rules.", e);
+ success = false;
+ }
+
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE);
+ try {
+ statusReceiver.sendIntent(
+ mContext,
+ /* code= */ 0,
+ intent,
+ /* onFinished= */ null,
+ /* handler= */ null);
+ } catch (IntentSender.SendIntentException e) {
+ Slog.e(TAG, "Error sending status feedback.", e);
+ }
+ });
+ }
+
+ @Override
+ public String getCurrentRuleSetVersion() throws RemoteException {
+ getCallerPackageNameOrThrow();
+
+ RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
+ return (ruleMetadata != null && ruleMetadata.getVersion() != null)
+ ? ruleMetadata.getVersion()
+ : "";
+ }
+
+ @Override
+ public String getCurrentRuleSetProvider() throws RemoteException {
+ getCallerPackageNameOrThrow();
+
+ RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
+ return (ruleMetadata != null && ruleMetadata.getRuleProvider() != null)
+ ? ruleMetadata.getRuleProvider()
+ : "";
+ }
+
+ private void handleIntegrityVerification(Intent intent) {
+ int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+ try {
+ Slog.i(TAG, "Received integrity verification intent " + intent.toString());
+ Slog.i(TAG, "Extras " + intent.getExtras());
+
+ String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+
+ PackageInfo packageInfo = getPackageArchiveInfo(intent.getData());
+ if (packageInfo == null) {
+ Slog.w(TAG, "Cannot parse package " + packageName);
+ // We can't parse the package.
+ mPackageManagerInternal.setIntegrityVerificationResult(
+ verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+ return;
+ }
+
+ String installerPackageName = getInstallerPackageName(intent);
+ String appCert = getCertificateFingerprint(packageInfo);
+
+ AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
+
+ builder.setPackageName(getPackageNameNormalized(packageName));
+ builder.setAppCertificate(appCert == null ? "" : appCert);
+ builder.setVersionCode(intent.getIntExtra(EXTRA_VERSION_CODE, -1));
+ builder.setInstallerName(getPackageNameNormalized(installerPackageName));
+ builder.setInstallerCertificate(
+ getInstallerCertificateFingerprint(installerPackageName));
+ builder.setIsPreInstalled(isSystemApp(packageName));
+
+ AppInstallMetadata appInstallMetadata = builder.build();
+
+ Slog.i(TAG, "To be verified: " + appInstallMetadata);
+ IntegrityCheckResult result =
+ mEvaluationEngine.evaluate(
+ appInstallMetadata, getAllowedInstallers(packageInfo));
+ Slog.i(
+ TAG,
+ "Integrity check result: "
+ + result.getEffect()
+ + " due to "
+ + result.getRule());
+ mPackageManagerInternal.setIntegrityVerificationResult(
+ verificationId,
+ result.getEffect() == IntegrityCheckResult.Effect.ALLOW
+ ? PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW
+ : PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
+ } catch (IllegalArgumentException e) {
+ // This exception indicates something is wrong with the input passed by package manager.
+ // e.g., someone trying to trick the system. We block installs in this case.
+ Slog.e(TAG, "Invalid input to integrity verification", e);
+ mPackageManagerInternal.setIntegrityVerificationResult(
+ verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
+ } catch (Exception e) {
+ // Other exceptions indicate an error within the integrity component implementation and
+ // we allow them.
+ Slog.e(TAG, "Error handling integrity verification", e);
+ mPackageManagerInternal.setIntegrityVerificationResult(
+ verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+ }
+ }
+
+ /**
+ * Verify the UID and return the installer package name.
+ *
+ * @return the package name of the installer, or null if it cannot be determined or it is
+ * installed via adb.
+ */
+ @Nullable
+ private String getInstallerPackageName(Intent intent) {
+ String installer =
+ intent.getStringExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE);
+ if (installer == null) {
+ return ADB_INSTALLER;
+ }
+ int installerUid = intent.getIntExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID, -1);
+ if (installerUid < 0) {
+ Slog.e(
+ TAG,
+ "Installer cannot be determined: installer: "
+ + installer
+ + " installer UID: "
+ + installerUid);
+ return UNKNOWN_INSTALLER;
+ }
+
+ try {
+ int actualInstallerUid =
+ mContext.getPackageManager().getPackageUid(installer, /* flags= */ 0);
+ if (actualInstallerUid != installerUid) {
+ // Installer package name can be faked but the installerUid cannot.
+ Slog.e(
+ TAG,
+ "Installer "
+ + installer
+ + " has UID "
+ + actualInstallerUid
+ + " which doesn't match alleged installer UID "
+ + installerUid);
+ return UNKNOWN_INSTALLER;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Installer package " + installer + " not found.");
+ return UNKNOWN_INSTALLER;
+ }
+
+ // At this time we can trust "installer".
+
+ // A common way for apps to install packages is to send an intent to PackageInstaller. In
+ // that case, the installer will always show up as PackageInstaller which is not what we
+ // want.
+ if (installer.equals(PACKAGE_INSTALLER)) {
+ int originatingUid = intent.getIntExtra(EXTRA_ORIGINATING_UID, -1);
+ if (originatingUid < 0) {
+ Slog.e(TAG, "Installer is package installer but originating UID not found.");
+ return UNKNOWN_INSTALLER;
+ }
+ String[] installerPackages =
+ mContext.getPackageManager().getPackagesForUid(originatingUid);
+ if (installerPackages == null || installerPackages.length == 0) {
+ Slog.e(TAG, "No package found associated with originating UID " + originatingUid);
+ return UNKNOWN_INSTALLER;
+ }
+ // In the case of multiple package sharing a UID, we just return the first one.
+ return installerPackages[0];
+ }
+
+ return installer;
+ }
+
+ /** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */
+ private String getPackageNameNormalized(String packageName) {
+ if (packageName.length() <= 32) {
+ return packageName;
+ }
+
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+ byte[] hashBytes = messageDigest.digest(packageName.getBytes(StandardCharsets.UTF_8));
+ return toHexString(hashBytes);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("SHA-256 algorithm not found", e);
+ }
+ }
+
+ private String getCertificateFingerprint(@NonNull PackageInfo packageInfo) {
+ return getFingerprint(getSignature(packageInfo));
+ }
+
+ private String getInstallerCertificateFingerprint(String installer) {
+ if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) {
+ return INSTALLER_CERT_NOT_APPLICABLE;
+ }
+ try {
+ PackageInfo installerInfo =
+ mContext.getPackageManager()
+ .getPackageInfo(installer, PackageManager.GET_SIGNATURES);
+ return getCertificateFingerprint(installerInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Installer package " + installer + " not found.");
+ return "";
+ }
+ }
+
+ /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */
+ private Map<String, String> getAllowedInstallers(@NonNull PackageInfo packageInfo) {
+ Map<String, String> packageCertMap = new HashMap<>();
+ if (packageInfo.applicationInfo != null && packageInfo.applicationInfo.metaData != null) {
+ Bundle metaData = packageInfo.applicationInfo.metaData;
+ String allowedInstallers = metaData.getString(ALLOWED_INSTALLERS_METADATA_NAME);
+ if (allowedInstallers != null) {
+ // parse the metadata for certs.
+ String[] installerCertPairs = allowedInstallers.split(ALLOWED_INSTALLER_DELIMITER);
+ for (String packageCertPair : installerCertPairs) {
+ String[] packageAndCert =
+ packageCertPair.split(INSTALLER_PACKAGE_CERT_DELIMITER);
+ if (packageAndCert.length == 2) {
+ String packageName = packageAndCert[0];
+ String cert = packageAndCert[1];
+ packageCertMap.put(packageName, cert);
+ }
+ }
+ }
+ }
+
+ Slog.i("DEBUG", "allowed installers map " + packageCertMap);
+ return packageCertMap;
+ }
+
+ private boolean getPreInstalled(String packageName) {
+ try {
+ PackageInfo existingPackageInfo =
+ mContext.getPackageManager().getPackageInfo(packageName, 0);
+ return existingPackageInfo.applicationInfo != null
+ && existingPackageInfo.applicationInfo.isSystemApp();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private static Signature getSignature(@NonNull PackageInfo packageInfo) {
+ if (packageInfo.signatures == null || packageInfo.signatures.length < 1) {
+ throw new IllegalArgumentException("Package signature not found in " + packageInfo);
+ }
+ // Only the first element is guaranteed to be present.
+ return packageInfo.signatures[0];
+ }
+
+ private static String getFingerprint(Signature cert) {
+ InputStream input = new ByteArrayInputStream(cert.toByteArray());
+
+ CertificateFactory factory;
+ try {
+ factory = CertificateFactory.getInstance("X509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("Error getting CertificateFactory", e);
+ }
+ X509Certificate certificate = null;
+ try {
+ if (factory != null) {
+ certificate = (X509Certificate) factory.generateCertificate(input);
+ }
+ } catch (CertificateException e) {
+ throw new RuntimeException("Error getting X509Certificate", e);
+ }
+
+ if (certificate == null) {
+ throw new RuntimeException("X509 Certificate not found");
+ }
+
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] publicKey = digest.digest(certificate.getEncoded());
+ return toHexString(publicKey);
+ } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
+ throw new IllegalArgumentException("Error error computing fingerprint", e);
+ }
+ }
+
+ private static String toHexString(byte[] bytes) {
+ // each byte is represented by two hex chars
+ StringBuffer hexString = new StringBuffer(bytes.length * 2);
+ for (int i = 0; i < bytes.length; i++) {
+ hexString.append(String.format("%02X", bytes[i]));
+ }
+ return new String(hexString);
+ }
+
+ private PackageInfo getPackageArchiveInfo(Uri dataUri) {
+ File installationPath = getInstallationPath(dataUri);
+ if (installationPath == null) {
+ throw new IllegalArgumentException("Installation path is null, package not found");
+ }
+ PackageInfo packageInfo;
+ try {
+ // The installation path will be a directory for a multi-apk install on L+
+ if (installationPath.isDirectory()) {
+ packageInfo = getMultiApkInfo(installationPath);
+ } else {
+ packageInfo =
+ mContext.getPackageManager()
+ .getPackageArchiveInfo(
+ installationPath.getPath(),
+ PackageManager.GET_SIGNATURES
+ | PackageManager.GET_META_DATA);
+ }
+ return packageInfo;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Exception reading " + dataUri, e);
+ }
+ }
+
+ private PackageInfo getMultiApkInfo(File multiApkDirectory) {
+ // The base apk will normally be called base.apk
+ File baseFile = new File(multiApkDirectory, BASE_APK_FILE);
+ PackageInfo basePackageInfo =
+ mContext.getPackageManager()
+ .getPackageArchiveInfo(
+ baseFile.getAbsolutePath(), PackageManager.GET_SIGNATURES);
+
+ if (basePackageInfo == null) {
+ for (File apkFile : multiApkDirectory.listFiles()) {
+ if (apkFile.isDirectory()) {
+ continue;
+ }
+
+ // If we didn't find a base.apk, then try to parse each apk until we find the one
+ // that succeeds.
+ basePackageInfo =
+ mContext.getPackageManager()
+ .getPackageArchiveInfo(
+ apkFile.getAbsolutePath(),
+ PackageManager.GET_SIGNING_CERTIFICATES);
+ if (basePackageInfo != null) {
+ Slog.i(TAG, "Found package info from " + apkFile);
+ break;
+ }
+ }
+ }
+
+ if (basePackageInfo == null) {
+ throw new IllegalArgumentException(
+ "Base package info cannot be found from installation directory");
+ }
+
+ return basePackageInfo;
+ }
+
+ private File getInstallationPath(Uri dataUri) {
+ if (dataUri == null) {
+ throw new IllegalArgumentException("Null data uri");
+ }
+
+ String scheme = dataUri.getScheme();
+ if (!"file".equalsIgnoreCase(scheme)) {
+ throw new IllegalArgumentException("Unsupported scheme for " + dataUri);
+ }
+
+ File installationPath = new File(dataUri.getPath());
+ if (!installationPath.exists()) {
+ throw new IllegalArgumentException("Cannot find file for " + dataUri);
+ }
+ if (!installationPath.canRead()) {
+ throw new IllegalArgumentException("Cannot read file for " + dataUri);
+ }
+ return installationPath;
+ }
+
+ private String getCallerPackageNameOrThrow() {
+ final String[] allowedRuleProviders =
+ mContext.getResources()
+ .getStringArray(R.array.config_integrityRuleProviderPackages);
+ for (String packageName : allowedRuleProviders) {
+ try {
+ // At least in tests, getPackageUid gives "NameNotFound" but getPackagesFromUid
+ // give the correct package name.
+ int uid = mContext.getPackageManager().getPackageUid(packageName, 0);
+ if (uid == Binder.getCallingUid()) {
+ // Caller is allowed in the config.
+ if (isSystemApp(packageName)) {
+ return packageName;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore the exception. We don't expect the app to be necessarily installed.
+ Slog.i(TAG, "Rule provider package " + packageName + " not installed.");
+ }
+ }
+ throw new SecurityException(
+ "Only system packages specified in config_integrityRuleProviderPackages are"
+ + " allowed to call this method.");
+ }
+
+ private boolean isSystemApp(String packageName) {
+ try {
+ PackageInfo existingPackageInfo =
+ mContext.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
+ return existingPackageInfo.applicationInfo != null
+ && existingPackageInfo.applicationInfo.isSystemApp();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
new file mode 100644
index 0000000..bdf0279
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -0,0 +1,169 @@
+/*
+ * 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.integrity;
+
+import android.annotation.Nullable;
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.Rule;
+import android.os.Environment;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.integrity.model.RuleMetadata;
+import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.parser.RuleMetadataParser;
+import com.android.server.integrity.parser.RuleParseException;
+import com.android.server.integrity.parser.RuleParser;
+import com.android.server.integrity.serializer.RuleBinarySerializer;
+import com.android.server.integrity.serializer.RuleMetadataSerializer;
+import com.android.server.integrity.serializer.RuleSerializeException;
+import com.android.server.integrity.serializer.RuleSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+/** Abstraction over the underlying storage of rules and other metadata. */
+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 Object RULES_LOCK = new Object();
+
+ private static IntegrityFileManager sInstance = null;
+
+ private final RuleParser mRuleParser;
+ private final RuleSerializer mRuleSerializer;
+
+ // mRulesDir contains data of the actual rules currently stored.
+ private final File mRulesDir;
+ // mStagingDir is used to store the temporary rules / metadata during updating, since we want to
+ // update rules atomically.
+ private final File mStagingDir;
+
+ @Nullable private RuleMetadata mRuleMetadataCache;
+
+ /** Get the singleton instance of this class. */
+ public static synchronized IntegrityFileManager getInstance() {
+ if (sInstance == null) {
+ sInstance = new IntegrityFileManager();
+ }
+ return sInstance;
+ }
+
+ private IntegrityFileManager() {
+ this(
+ new RuleBinaryParser(),
+ new RuleBinarySerializer(),
+ Environment.getDataSystemDirectory());
+ }
+
+ @VisibleForTesting
+ IntegrityFileManager(RuleParser ruleParser, RuleSerializer ruleSerializer, File dataDir) {
+ mRuleParser = ruleParser;
+ mRuleSerializer = ruleSerializer;
+
+ mRulesDir = new File(dataDir, "integrity_rules");
+ mStagingDir = new File(dataDir, "integrity_staging");
+
+ if (!mStagingDir.mkdirs() && mRulesDir.mkdirs()) {
+ Slog.e(TAG, "Error creating staging and rules directory");
+ // TODO: maybe throw an exception?
+ }
+
+ File metadataFile = new File(mRulesDir, METADATA_FILE);
+ if (metadataFile.exists()) {
+ try (FileInputStream inputStream = new FileInputStream(metadataFile)) {
+ mRuleMetadataCache = RuleMetadataParser.parse(inputStream);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading metadata file.", e);
+ }
+ }
+ }
+
+ /** Write rules to persistent storage. */
+ public void writeRules(String version, String ruleProvider, List<Rule> rules)
+ throws IOException, RuleSerializeException {
+ try {
+ writeMetadata(mStagingDir, ruleProvider, version);
+ } catch (IOException e) {
+ Slog.e(TAG, "Error writing metadata.", e);
+ // 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);
+ }
+
+ switchStagingRulesDir();
+ }
+
+ /**
+ * Read rules from persistent storage.
+ *
+ * @param appInstallMetadata information about the install used to select rules to read
+ */
+ 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;
+ }
+ }
+ }
+
+ /** Read the metadata of the current rules in storage. */
+ @Nullable
+ public RuleMetadata readMetadata() {
+ return mRuleMetadataCache;
+ }
+
+ private void switchStagingRulesDir() throws IOException {
+ synchronized (RULES_LOCK) {
+ File tmpDir = new File(Environment.getDataSystemDirectory(), "temp");
+
+ if (!(mRulesDir.renameTo(tmpDir)
+ && mStagingDir.renameTo(mRulesDir)
+ && tmpDir.renameTo(mStagingDir))) {
+ throw new IOException("Error switching staging/rules directory");
+ }
+ }
+ }
+
+ private void writeMetadata(File directory, String ruleProvider, String version)
+ throws IOException {
+ mRuleMetadataCache = new RuleMetadata(ruleProvider, version);
+
+ File metadataFile = new File(directory, METADATA_FILE);
+
+ try (FileOutputStream outputStream = new FileOutputStream(metadataFile)) {
+ RuleMetadataSerializer.serialize(mRuleMetadataCache, outputStream);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/OWNERS b/services/core/java/com/android/server/integrity/OWNERS
index 019aa4f..55a4e40 100644
--- a/services/core/java/com/android/server/integrity/OWNERS
+++ b/services/core/java/com/android/server/integrity/OWNERS
@@ -3,4 +3,3 @@
mdchurchill@google.com
sturla@google.com
songpan@google.com
-bjy@google.com
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
index b8202b6..0ea6efc 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
@@ -18,11 +18,14 @@
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.Rule;
+import android.util.Slog;
+import com.android.server.integrity.IntegrityFileManager;
import com.android.server.integrity.model.IntegrityCheckResult;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* The engine used to evaluate rules against app installs.
@@ -30,17 +33,23 @@
* <p>Every app install is evaluated against rules (pushed by the verifier) by the evaluation engine
* to allow/block that install.
*/
-public final class RuleEvaluationEngine {
+public class RuleEvaluationEngine {
private static final String TAG = "RuleEvaluation";
// The engine for loading rules, retrieving metadata for app installs, and evaluating app
// installs against rules.
private static RuleEvaluationEngine sRuleEvaluationEngine;
+ private final IntegrityFileManager mIntegrityFileManager;
+
+ private RuleEvaluationEngine(IntegrityFileManager integrityFileManager) {
+ mIntegrityFileManager = integrityFileManager;
+ }
+
/** Provide a singleton instance of the rule evaluation engine. */
public static synchronized RuleEvaluationEngine getRuleEvaluationEngine() {
if (sRuleEvaluationEngine == null) {
- return new RuleEvaluationEngine();
+ return new RuleEvaluationEngine(IntegrityFileManager.getInstance());
}
return sRuleEvaluationEngine;
}
@@ -52,13 +61,18 @@
* against.
* @return result of the integrity check
*/
- public IntegrityCheckResult evaluate(AppInstallMetadata appInstallMetadata) {
+ public IntegrityCheckResult evaluate(
+ AppInstallMetadata appInstallMetadata, Map<String, String> allowedInstallers) {
List<Rule> rules = loadRules(appInstallMetadata);
return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
}
private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
- // TODO: Load rules
- return new ArrayList<>();
+ try {
+ return mIntegrityFileManager.readRules(appInstallMetadata);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error loading rules.", e);
+ return new ArrayList<>();
+ }
}
}
diff --git a/services/core/java/com/android/server/integrity/model/RuleMetadata.java b/services/core/java/com/android/server/integrity/model/RuleMetadata.java
new file mode 100644
index 0000000..6b582ae
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/RuleMetadata.java
@@ -0,0 +1,41 @@
+/*
+ * 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.integrity.model;
+
+import android.annotation.Nullable;
+
+/** Data class containing relevant metadata associated with a rule set. */
+public class RuleMetadata {
+
+ private final String mRuleProvider;
+ private final String mVersion;
+
+ public RuleMetadata(String ruleProvider, String version) {
+ mRuleProvider = ruleProvider;
+ mVersion = version;
+ }
+
+ @Nullable
+ public String getRuleProvider() {
+ return mRuleProvider;
+ }
+
+ @Nullable
+ public String getVersion() {
+ return mVersion;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
new file mode 100644
index 0000000..28d2e69
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.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 com.android.server.integrity.parser;
+
+import android.annotation.Nullable;
+import android.util.Xml;
+
+import com.android.server.integrity.model.RuleMetadata;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/** Helper class for parsing rule metadata. */
+public class RuleMetadataParser {
+
+ public static final String RULE_PROVIDER_TAG = "P";
+ public static final String VERSION_TAG = "V";
+
+ /** Parse the rule metadata from an input stream. */
+ @Nullable
+ public static RuleMetadata parse(InputStream inputStream)
+ throws XmlPullParserException, IOException {
+
+ String ruleProvider = "";
+ String version = "";
+
+ XmlPullParser xmlPullParser = Xml.newPullParser();
+ xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+ int eventType;
+ while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ String tag = xmlPullParser.getName();
+ switch (tag) {
+ case RULE_PROVIDER_TAG:
+ ruleProvider = xmlPullParser.nextText();
+ break;
+ case VERSION_TAG:
+ version = xmlPullParser.nextText();
+ break;
+ default:
+ throw new IllegalStateException("Unknown tag in metadata: " + tag);
+ }
+ }
+ }
+
+ return new RuleMetadata(ruleProvider, version);
+ }
+}
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 fdbb7d9..73a815a 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.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;
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
@@ -36,36 +39,16 @@
import com.android.server.integrity.model.BitOutputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
public class RuleBinarySerializer implements RuleSerializer {
- // 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 outputStream)
- throws RuleSerializeException {
- try {
- BitOutputStream bitOutputStream = new BitOutputStream();
-
- int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
- bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
- outputStream.write(bitOutputStream.toByteArray());
-
- for (Rule rule : rules) {
- bitOutputStream.clear();
- serializeRule(rule, bitOutputStream);
- outputStream.write(bitOutputStream.toByteArray());
- }
- } catch (Exception e) {
- throw new RuleSerializeException(e.getMessage(), e);
- }
- }
-
// Get the byte representation for a list of rules.
@Override
public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
@@ -79,6 +62,45 @@
}
}
+ // 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 outputStream)
+ throws RuleSerializeException {
+ try {
+ // Determine the indexing groups and the order of the rules within each indexed group.
+ Map<Integer, List<Rule>> indexedRules =
+ RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
+
+ serializeRuleFileMetadata(formatVersion, outputStream);
+
+ serializeIndexedRules(indexedRules.get(PACKAGE_NAME_INDEXED), outputStream);
+ serializeIndexedRules(indexedRules.get(APP_CERTIFICATE_INDEXED), outputStream);
+ serializeIndexedRules(indexedRules.get(NOT_INDEXED), outputStream);
+ } catch (Exception e) {
+ throw new RuleSerializeException(e.getMessage(), e);
+ }
+ }
+
+ private void serializeRuleFileMetadata(
+ Optional<Integer> formatVersion, OutputStream outputStream) throws IOException {
+ int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
+
+ BitOutputStream bitOutputStream = new BitOutputStream();
+ bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
+ outputStream.write(bitOutputStream.toByteArray());
+ }
+
+ private void serializeIndexedRules(List<Rule> rules, OutputStream outputStream)
+ throws IOException {
+ BitOutputStream bitOutputStream = new BitOutputStream();
+ for (Rule rule : rules) {
+ bitOutputStream.clear();
+ serializeRule(rule, bitOutputStream);
+ outputStream.write(bitOutputStream.toByteArray());
+ }
+ }
+
private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
if (rule == null) {
throw new IllegalArgumentException("Null rule can not be serialized");
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
new file mode 100644
index 0000000..dd871e2
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.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 com.android.server.integrity.serializer;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Holds the indexing type and indexing key of a given formula. */
+class RuleIndexingDetails {
+
+ static final int NOT_INDEXED = 0;
+ static final int PACKAGE_NAME_INDEXED = 1;
+ static final int APP_CERTIFICATE_INDEXED = 2;
+
+ /** Represents which indexed file the rule should be located. */
+ @IntDef(
+ value = {
+ NOT_INDEXED,
+ PACKAGE_NAME_INDEXED,
+ APP_CERTIFICATE_INDEXED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface IndexType {
+ }
+
+ private @IndexType int mIndexType;
+ private String mRuleKey;
+
+ /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
+ RuleIndexingDetails(@IndexType int indexType) {
+ this.mIndexType = indexType;
+ this.mRuleKey = null;
+ }
+
+ /** Constructor with a ruleKey for indexed rules. */
+ RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
+ this.mIndexType = indexType;
+ this.mRuleKey = ruleKey;
+ }
+
+ /** Returns the indexing type for the rule. */
+ @IndexType
+ public int getIndexType() {
+ return mIndexType;
+ }
+
+ /** Returns the identified rule key. */
+ public String getRuleKey() {
+ return mRuleKey;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
new file mode 100644
index 0000000..f9c7912
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -0,0 +1,176 @@
+/*
+ * 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.integrity.serializer;
+
+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;
+
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Formula;
+import android.content.integrity.Rule;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+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 and returns a sorted list of rules
+ * per each index.
+ *
+ * The sorting guarantees an order based on the key but the rules that have the same key
+ * can be in arbitrary order. For example, given the rules of [package_name_a_rule_1,
+ * package_name_a_rule_2, package_name_b_rule_3, package_name_b_rule_4], the method will
+ * guarantee that package_name_b rules (i.e., 3 and 4) will never come before package_name_a
+ * rules (i.e., 1 and 2). However, we do not care about the ordering between rule 1 and 2.
+ * We also do not care about the ordering between rule 3 and 4.
+ */
+ public static Map<Integer, List<Rule>> splitRulesIntoIndexBuckets(List<Rule> rules) {
+ if (rules == null) {
+ throw new IllegalArgumentException(
+ "Index buckets cannot be created for null rule list.");
+ }
+
+ Map<Integer, Map<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());
+
+ // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
+ // entries sorted by their index key.
+ for (Rule rule : rules) {
+ RuleIndexingDetails indexingDetails;
+ try {
+ indexingDetails = getIndexingDetails(rule.getFormula());
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ String.format("Malformed rule identified. [%s]", rule.toString()));
+ }
+
+ String ruleKey =
+ indexingDetails.getIndexType() != NOT_INDEXED
+ ? indexingDetails.getRuleKey()
+ : DEFAULT_RULE_KEY;
+
+ if (!typeOrganizedRuleMap.get(indexingDetails.getIndexType()).containsKey(ruleKey)) {
+ typeOrganizedRuleMap
+ .get(indexingDetails.getIndexType())
+ .put(ruleKey, new ArrayList());
+ }
+
+ typeOrganizedRuleMap
+ .get(indexingDetails.getIndexType())
+ .get(ruleKey)
+ .add(rule);
+ }
+
+ // Per indexing type, create the sorted rule set based on their key.
+ Map<Integer, List<Rule>> orderedListPerIndexingType = new HashMap<>();
+
+ for (Integer indexingKey : typeOrganizedRuleMap.keySet()) {
+ List<Rule> sortedRules = new ArrayList();
+ for (Map.Entry<String, List<Rule>> entry :
+ typeOrganizedRuleMap.get(indexingKey).entrySet()) {
+ sortedRules.addAll(entry.getValue());
+ }
+ orderedListPerIndexingType.put(indexingKey, sortedRules);
+ }
+
+ return orderedListPerIndexingType;
+ }
+
+ private static RuleIndexingDetails getIndexingDetails(Formula formula) {
+ switch (formula.getTag()) {
+ case Formula.COMPOUND_FORMULA_TAG:
+ return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
+ case Formula.STRING_ATOMIC_FORMULA_TAG:
+ return getIndexingDetailsForStringAtomicFormula(
+ (AtomicFormula.StringAtomicFormula) formula);
+ case Formula.INT_ATOMIC_FORMULA_TAG:
+ case Formula.BOOLEAN_ATOMIC_FORMULA_TAG:
+ // Package name and app certificate related formulas are string atomic formulas.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ default:
+ throw new IllegalArgumentException(
+ String.format("Invalid formula tag type: %s", formula.getTag()));
+ }
+ }
+
+ private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
+ CompoundFormula compoundFormula) {
+ int connector = compoundFormula.getConnector();
+ List<Formula> formulas = compoundFormula.getFormulas();
+
+ switch (connector) {
+ case CompoundFormula.AND:
+ case CompoundFormula.OR:
+ // If there is a package name related atomic rule, return package name indexed.
+ Optional<RuleIndexingDetails> packageNameRule =
+ formulas.stream()
+ .map(formula -> getIndexingDetails(formula))
+ .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
+ == PACKAGE_NAME_INDEXED)
+ .findAny();
+ if (packageNameRule.isPresent()) {
+ return packageNameRule.get();
+ }
+
+ // If there is an app certificate related atomic rule but no package name related
+ // atomic rule, return app certificate indexed.
+ Optional<RuleIndexingDetails> appCertificateRule =
+ formulas.stream()
+ .map(formula -> getIndexingDetails(formula))
+ .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
+ == APP_CERTIFICATE_INDEXED)
+ .findAny();
+ if (appCertificateRule.isPresent()) {
+ return appCertificateRule.get();
+ }
+
+ // Do not index when there is not package name or app certificate indexing.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ default:
+ // Having a NOT operator in the indexing messes up the indexing; e.g., deny
+ // installation if app certificate is NOT X (should not be indexed with app cert
+ // X). We will not keep these rules indexed.
+ // Also any other type of unknown operators will not be indexed.
+ return new RuleIndexingDetails(NOT_INDEXED);
+ }
+ }
+
+ private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula(
+ AtomicFormula.StringAtomicFormula atomicFormula) {
+ switch (atomicFormula.getKey()) {
+ case AtomicFormula.PACKAGE_NAME:
+ return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue());
+ case AtomicFormula.APP_CERTIFICATE:
+ return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue());
+ default:
+ return new RuleIndexingDetails(NOT_INDEXED);
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
new file mode 100644
index 0000000..5c51f31
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.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.integrity.serializer;
+
+import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
+import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
+
+import android.util.Xml;
+
+import com.android.server.integrity.model.RuleMetadata;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/** Helper class for writing rule metadata. */
+public class RuleMetadataSerializer {
+ /** Serialize the rule metadata to an output stream. */
+ public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
+ throws IOException {
+ XmlSerializer xmlSerializer = Xml.newSerializer();
+ xmlSerializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+
+ serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
+ serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
+
+ xmlSerializer.endDocument();
+ }
+
+ private static void serializeTaggedValue(XmlSerializer xmlSerializer, String tag, String value)
+ throws IOException {
+ xmlSerializer.startTag(/* namespace= */ null, tag);
+ xmlSerializer.text(value);
+ xmlSerializer.endTag(/* namespace= */ null, tag);
+ }
+}
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 cfe50c6..ebf6a2e 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
@@ -16,6 +16,10 @@
package com.android.server.integrity.serializer;
+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;
+
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
import android.content.integrity.Formula;
@@ -29,6 +33,7 @@
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
/** A helper class to serialize rules from the {@link Rule} model to Xml representation. */
@@ -75,13 +80,32 @@
}
}
- private void serializeRules(List<Rule> rules, XmlSerializer xmlSerializer) throws IOException {
- xmlSerializer.startTag(NAMESPACE, RULE_LIST_TAG);
+ private void serializeRules(List<Rule> rules, XmlSerializer xmlSerializer)
+ throws RuleSerializeException {
+ try {
+ // Determine the indexing groups and the order of the rules within each indexed group.
+ Map<Integer, List<Rule>> indexedRules =
+ RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
+
+ // Write the XML formatted rules in order.
+ xmlSerializer.startTag(NAMESPACE, RULE_LIST_TAG);
+
+ serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED), xmlSerializer);
+ serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED), xmlSerializer);
+ serializeRuleList(indexedRules.get(NOT_INDEXED), xmlSerializer);
+
+ xmlSerializer.endTag(NAMESPACE, RULE_LIST_TAG);
+ xmlSerializer.endDocument();
+ } catch (Exception e) {
+ throw new RuleSerializeException(e.getMessage(), e);
+ }
+ }
+
+ private void serializeRuleList(List<Rule> rules, XmlSerializer xmlSerializer)
+ throws IOException {
for (Rule rule : rules) {
serializeRule(rule, xmlSerializer);
}
- xmlSerializer.endTag(NAMESPACE, RULE_LIST_TAG);
- xmlSerializer.endDocument();
}
private void serializeRule(Rule rule, XmlSerializer xmlSerializer) throws IOException {
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 557ba54..f913ba3 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -531,8 +531,10 @@
CarrierConfigManager configManager = (CarrierConfigManager)
mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId();
- String mccMnc = SubscriptionManager.isValidSubscriptionId(ddSubId)
- ? phone.getSimOperator(ddSubId) : phone.getSimOperator();
+ if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
+ phone = phone.createForSubscriptionId(ddSubId);
+ }
+ String mccMnc = phone.getSimOperator();
boolean isKeepLppProfile = false;
if (!TextUtils.isEmpty(mccMnc)) {
if (DEBUG) Log.d(TAG, "SIM MCC/MNC is available: " + mccMnc);
@@ -1913,24 +1915,17 @@
String setId = null;
int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+ if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
+ phone = phone.createForSubscriptionId(ddSubId);
+ }
if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) {
- if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
- setId = phone.getSubscriberId(ddSubId);
- }
- if (setId == null) {
- setId = phone.getSubscriberId();
- }
+ setId = phone.getSubscriberId();
if (setId != null) {
// This means the framework has the SIM card.
type = AGPS_SETID_TYPE_IMSI;
}
} else if ((flags & AGPS_RIL_REQUEST_SETID_MSISDN) == AGPS_RIL_REQUEST_SETID_MSISDN) {
- if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
- setId = phone.getLine1Number(ddSubId);
- }
- if (setId == null) {
- setId = phone.getLine1Number();
- }
+ setId = phone.getLine1Number();
if (setId != null) {
// This means the framework has the SIM card.
type = AGPS_SETID_TYPE_MSISDN;
diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
index ea4f9c4..dd522b9 100644
--- a/services/core/java/com/android/server/location/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -637,7 +637,7 @@
return new Notification.Builder(context, SystemNotificationChannels.NETWORK_ALERTS)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on)
.setWhen(0)
- .setOngoing(true)
+ .setOngoing(false)
.setAutoCancel(true)
.setColor(context.getColor(
com.android.internal.R.color.system_notification_accent_color))
diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java
index eb2a37b..f625452 100644
--- a/services/core/java/com/android/server/location/LocationSettingsStore.java
+++ b/services/core/java/com/android/server/location/LocationSettingsStore.java
@@ -23,7 +23,6 @@
import static android.provider.Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS;
import static android.provider.Settings.Secure.LOCATION_MODE;
import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
-import static android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
import android.app.ActivityManager;
import android.content.Context;
@@ -35,6 +34,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.SystemConfig;
@@ -89,7 +89,6 @@
private final Context mContext;
private final IntegerSecureSetting mLocationMode;
- private final StringListCachedSecureSetting mLocationProvidersAllowed;
private final LongGlobalSetting mBackgroundThrottleIntervalMs;
private final StringListCachedSecureSetting mLocationPackageBlacklist;
private final StringListCachedSecureSetting mLocationPackageWhitelist;
@@ -101,8 +100,6 @@
mContext = context;
mLocationMode = new IntegerSecureSetting(context, LOCATION_MODE, handler);
- mLocationProvidersAllowed = new StringListCachedSecureSetting(context,
- LOCATION_PROVIDERS_ALLOWED, handler);
mBackgroundThrottleIntervalMs = new LongGlobalSetting(context,
LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, handler);
mLocationPackageBlacklist = new StringListCachedSecureSetting(context,
@@ -117,6 +114,16 @@
() -> SystemConfig.getInstance().getAllowIgnoreLocationSettings(), handler);
}
+ /** Called when system is ready. */
+ public synchronized void onSystemReady() {
+ mLocationMode.register();
+ mBackgroundThrottleIntervalMs.register();
+ mLocationPackageBlacklist.register();
+ mLocationPackageWhitelist.register();
+ mBackgroundThrottlePackageWhitelist.register();
+ mIgnoreSettingsPackageWhitelist.register();
+ }
+
/**
* Retrieve if location is enabled or not.
*/
@@ -139,28 +146,6 @@
}
/**
- * Retrieve the currently allowed location providers.
- */
- public List<String> getLocationProvidersAllowed(int userId) {
- return mLocationProvidersAllowed.getValueForUser(userId);
- }
-
- /**
- * Add a listener for changes to the currently allowed location providers.
- */
- public void addOnLocationProvidersAllowedChangedListener(UserSettingChangedListener listener) {
- mLocationProvidersAllowed.addListener(listener);
- }
-
- /**
- * Remove a listener for changes to the currently allowed location providers.
- */
- public void removeOnLocationProvidersAllowedChangedListener(
- UserSettingChangedListener listener) {
- mLocationProvidersAllowed.removeListener(listener);
- }
-
- /**
* Retrieve the background throttle interval.
*/
public long getBackgroundThrottleIntervalMs() {
@@ -280,9 +265,6 @@
ipw.print("Location Enabled: ");
ipw.println(isLocationEnabled(userId));
- ipw.print("Location Providers Allowed: ");
- ipw.println(getLocationProvidersAllowed(userId));
-
List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(userId);
if (!locationPackageBlacklist.isEmpty()) {
ipw.println("Location Blacklisted Packages:");
@@ -329,13 +311,25 @@
private abstract static class ObservingSetting extends ContentObserver {
private final CopyOnWriteArrayList<UserSettingChangedListener> mListeners;
+ private boolean mRegistered;
- private ObservingSetting(Context context, String settingName, Handler handler) {
+ private ObservingSetting(Handler handler) {
super(handler);
mListeners = new CopyOnWriteArrayList<>();
+ }
+
+ protected boolean isRegistered() {
+ return mRegistered;
+ }
+
+ protected void register(Context context, Uri uri) {
+ if (mRegistered) {
+ return;
+ }
context.getContentResolver().registerContentObserver(
- getUriFor(settingName), false, this, UserHandle.USER_ALL);
+ uri, false, this, UserHandle.USER_ALL);
+ mRegistered = true;
}
public void addListener(UserSettingChangedListener listener) {
@@ -346,8 +340,6 @@
mListeners.remove(listener);
}
- protected abstract Uri getUriFor(String settingName);
-
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
for (UserSettingChangedListener listener : mListeners) {
@@ -362,20 +354,19 @@
private final String mSettingName;
private IntegerSecureSetting(Context context, String settingName, Handler handler) {
- super(context, settingName, handler);
+ super(handler);
mContext = context;
mSettingName = settingName;
}
+ private void register() {
+ register(mContext, Settings.Secure.getUriFor(mSettingName));
+ }
+
public int getValueForUser(int defaultValue, int userId) {
return Settings.Secure.getIntForUser(mContext.getContentResolver(), mSettingName,
defaultValue, userId);
}
-
- @Override
- protected Uri getUriFor(String settingName) {
- return Settings.Secure.getUriFor(settingName);
- }
}
private static class StringListCachedSecureSetting extends ObservingSetting {
@@ -383,31 +374,44 @@
private final Context mContext;
private final String mSettingName;
- private int mCachedUserId = UserHandle.USER_NULL;
+ @GuardedBy("this")
+ private int mCachedUserId;
+ @GuardedBy("this")
private List<String> mCachedValue;
private StringListCachedSecureSetting(Context context, String settingName,
Handler handler) {
- super(context, settingName, handler);
+ super(handler);
mContext = context;
mSettingName = settingName;
+
+ mCachedUserId = UserHandle.USER_NULL;
+ }
+
+ public synchronized void register() {
+ register(mContext, Settings.Secure.getUriFor(mSettingName));
}
public synchronized List<String> getValueForUser(int userId) {
Preconditions.checkArgument(userId != UserHandle.USER_NULL);
+ List<String> value = mCachedValue;
if (userId != mCachedUserId) {
String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
mSettingName, userId);
if (TextUtils.isEmpty(setting)) {
- mCachedValue = Collections.emptyList();
+ value = Collections.emptyList();
} else {
- mCachedValue = Arrays.asList(setting.split(","));
+ value = Arrays.asList(setting.split(","));
}
- mCachedUserId = userId;
+
+ if (isRegistered()) {
+ mCachedUserId = userId;
+ mCachedValue = value;
+ }
}
- return mCachedValue;
+ return value;
}
public synchronized void invalidateForUser(int userId) {
@@ -422,11 +426,6 @@
invalidateForUser(userId);
super.onChange(selfChange, uri, userId);
}
-
- @Override
- protected Uri getUriFor(String settingName) {
- return Settings.Secure.getUriFor(settingName);
- }
}
private static class LongGlobalSetting extends ObservingSetting {
@@ -435,20 +434,19 @@
private final String mSettingName;
private LongGlobalSetting(Context context, String settingName, Handler handler) {
- super(context, settingName, handler);
+ super(handler);
mContext = context;
mSettingName = settingName;
}
+ public void register() {
+ register(mContext, Settings.Global.getUriFor(mSettingName));
+ }
+
public long getValue(long defaultValue) {
return Settings.Global.getLong(mContext.getContentResolver(), mSettingName,
defaultValue);
}
-
- @Override
- protected Uri getUriFor(String settingName) {
- return Settings.Global.getUriFor(settingName);
- }
}
private static class StringSetCachedGlobalSetting extends ObservingSetting {
@@ -457,29 +455,42 @@
private final String mSettingName;
private final Supplier<ArraySet<String>> mBaseValuesSupplier;
+ @GuardedBy("this")
private boolean mValid;
+ @GuardedBy("this")
private ArraySet<String> mCachedValue;
private StringSetCachedGlobalSetting(Context context, String settingName,
Supplier<ArraySet<String>> baseValuesSupplier, Handler handler) {
- super(context, settingName, handler);
+ super(handler);
mContext = context;
mSettingName = settingName;
mBaseValuesSupplier = baseValuesSupplier;
+
+ mValid = false;
+ }
+
+ public synchronized void register() {
+ register(mContext, Settings.Global.getUriFor(mSettingName));
}
public synchronized Set<String> getValue() {
+ ArraySet<String> value = mCachedValue;
if (!mValid) {
- mCachedValue = new ArraySet<>(mBaseValuesSupplier.get());
+ value = new ArraySet<>(mBaseValuesSupplier.get());
String setting = Settings.Global.getString(mContext.getContentResolver(),
mSettingName);
if (!TextUtils.isEmpty(setting)) {
- mCachedValue.addAll(Arrays.asList(setting.split(",")));
+ value.addAll(Arrays.asList(setting.split(",")));
}
- mValid = true;
+
+ if (isRegistered()) {
+ mValid = true;
+ mCachedValue = value;
+ }
}
- return mCachedValue;
+ return value;
}
public synchronized void invalidate() {
@@ -492,10 +503,5 @@
invalidate();
super.onChange(selfChange, uri, userId);
}
-
- @Override
- protected Uri getUriFor(String settingName) {
- return Settings.Global.getUriFor(settingName);
- }
}
}
diff --git a/services/core/java/com/android/server/LocationUsageLogger.java b/services/core/java/com/android/server/location/LocationUsageLogger.java
similarity index 72%
rename from services/core/java/com/android/server/LocationUsageLogger.java
rename to services/core/java/com/android/server/location/LocationUsageLogger.java
index a8a3cc4..755438b 100644
--- a/services/core/java/com/android/server/LocationUsageLogger.java
+++ b/services/core/java/com/android/server/location/LocationUsageLogger.java
@@ -14,37 +14,113 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.location;
+
+import static com.android.server.LocationManagerService.TAG;
import android.app.ActivityManager;
import android.location.Geofence;
import android.location.LocationManager;
import android.location.LocationRequest;
-import android.os.SystemClock;
import android.stats.location.LocationStatsEnums;
import android.util.Log;
import android.util.StatsLog;
+import com.android.internal.annotations.GuardedBy;
+
import java.time.Instant;
/**
* Logger for Location API usage logging.
*/
public class LocationUsageLogger {
- private static final String TAG = "LocationUsageLogger";
- private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
private static final int ONE_SEC_IN_MILLIS = 1000;
private static final int ONE_MINUTE_IN_MILLIS = 60000;
private static final int ONE_HOUR_IN_MILLIS = 3600000;
- private long mLastApiUsageLogHour = 0;
-
- private int mApiUsageLogHourlyCount = 0;
-
private static final int API_USAGE_LOG_HOURLY_CAP = 60;
- private static int providerNameToStatsdEnum(String provider) {
+ @GuardedBy("this")
+ private long mLastApiUsageLogHour = 0;
+ @GuardedBy("this")
+ private int mApiUsageLogHourlyCount = 0;
+
+ /**
+ * Log a location API usage event.
+ */
+ public void logLocationApiUsage(int usageType, int apiInUse,
+ String packageName, LocationRequest locationRequest,
+ boolean hasListener, boolean hasIntent,
+ Geofence geofence, int activityImportance) {
+ try {
+ if (hitApiUsageLogCap()) {
+ return;
+ }
+
+ boolean isLocationRequestNull = locationRequest == null;
+ boolean isGeofenceNull = geofence == null;
+
+ StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType,
+ apiInUse, packageName,
+ isLocationRequestNull
+ ? LocationStatsEnums.PROVIDER_UNKNOWN
+ : bucketizeProvider(locationRequest.getProvider()),
+ isLocationRequestNull
+ ? LocationStatsEnums.QUALITY_UNKNOWN
+ : locationRequest.getQuality(),
+ isLocationRequestNull
+ ? LocationStatsEnums.INTERVAL_UNKNOWN
+ : bucketizeInterval(locationRequest.getInterval()),
+ isLocationRequestNull
+ ? LocationStatsEnums.DISTANCE_UNKNOWN
+ : bucketizeDistance(
+ locationRequest.getSmallestDisplacement()),
+ isLocationRequestNull ? 0 : locationRequest.getNumUpdates(),
+ // only log expireIn for USAGE_STARTED
+ isLocationRequestNull || usageType == LocationStatsEnums.USAGE_ENDED
+ ? LocationStatsEnums.EXPIRATION_UNKNOWN
+ : bucketizeExpireIn(locationRequest.getExpireIn()),
+ getCallbackType(apiInUse, hasListener, hasIntent),
+ isGeofenceNull
+ ? LocationStatsEnums.RADIUS_UNKNOWN
+ : bucketizeRadius(geofence.getRadius()),
+ categorizeActivityImportance(activityImportance));
+ } catch (Exception e) {
+ // Swallow exceptions to avoid crashing LMS.
+ Log.w(TAG, "Failed to log API usage to statsd.", e);
+ }
+ }
+
+ /**
+ * Log a location API usage event.
+ */
+ public void logLocationApiUsage(int usageType, int apiInUse, String providerName) {
+ try {
+ if (hitApiUsageLogCap()) {
+ return;
+ }
+
+ StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, apiInUse,
+ /* package_name= */ null,
+ bucketizeProvider(providerName),
+ LocationStatsEnums.QUALITY_UNKNOWN,
+ LocationStatsEnums.INTERVAL_UNKNOWN,
+ LocationStatsEnums.DISTANCE_UNKNOWN,
+ /* numUpdates= */ 0,
+ LocationStatsEnums.EXPIRATION_UNKNOWN,
+ getCallbackType(
+ apiInUse,
+ /* isListenerNull= */ true,
+ /* isIntentNull= */ true),
+ /* bucketizedRadius= */ 0,
+ LocationStatsEnums.IMPORTANCE_UNKNOWN);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to log API usage to statsd.", e);
+ }
+ }
+
+ private static int bucketizeProvider(String provider) {
if (LocationManager.NETWORK_PROVIDER.equals(provider)) {
return LocationStatsEnums.PROVIDER_NETWORK;
} else if (LocationManager.GPS_PROVIDER.equals(provider)) {
@@ -58,8 +134,7 @@
}
}
- private static int bucketizeIntervalToStatsdEnum(long interval) {
- // LocationManager already converts negative values to 0.
+ private static int bucketizeInterval(long interval) {
if (interval < ONE_SEC_IN_MILLIS) {
return LocationStatsEnums.INTERVAL_BETWEEN_0_SEC_AND_1_SEC;
} else if (interval < ONE_SEC_IN_MILLIS * 5) {
@@ -75,9 +150,8 @@
}
}
- private static int bucketizeSmallestDisplacementToStatsdEnum(float smallestDisplacement) {
- // LocationManager already converts negative values to 0.
- if (smallestDisplacement == 0) {
+ private static int bucketizeDistance(float smallestDisplacement) {
+ if (smallestDisplacement <= 0) {
return LocationStatsEnums.DISTANCE_ZERO;
} else if (smallestDisplacement > 0 && smallestDisplacement <= 100) {
return LocationStatsEnums.DISTANCE_BETWEEN_0_AND_100;
@@ -86,7 +160,7 @@
}
}
- private static int bucketizeRadiusToStatsdEnum(float radius) {
+ private static int bucketizeRadius(float radius) {
if (radius < 0) {
return LocationStatsEnums.RADIUS_NEGATIVE;
} else if (radius < 100) {
@@ -104,14 +178,11 @@
}
}
- private static int getBucketizedExpireIn(long expireAt) {
- if (expireAt == Long.MAX_VALUE) {
+ private static int bucketizeExpireIn(long expireIn) {
+ if (expireIn == Long.MAX_VALUE) {
return LocationStatsEnums.EXPIRATION_NO_EXPIRY;
}
- long elapsedRealtime = SystemClock.elapsedRealtime();
- long expireIn = Math.max(0, expireAt - elapsedRealtime);
-
if (expireIn < 20 * ONE_SEC_IN_MILLIS) {
return LocationStatsEnums.EXPIRATION_BETWEEN_0_AND_20_SEC;
} else if (expireIn < ONE_MINUTE_IN_MILLIS) {
@@ -129,8 +200,8 @@
if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return LocationStatsEnums.IMPORTANCE_TOP;
} else if (importance == ActivityManager
- .RunningAppProcessInfo
- .IMPORTANCE_FOREGROUND_SERVICE) {
+ .RunningAppProcessInfo
+ .IMPORTANCE_FOREGROUND_SERVICE) {
return LocationStatsEnums.IMPORTANCE_FORGROUND_SERVICE;
} else {
return LocationStatsEnums.IMPORTANCE_BACKGROUND;
@@ -154,117 +225,16 @@
}
}
- // Update the hourly count of APIUsage log event.
- // Returns false if hit the hourly log cap.
- private boolean checkApiUsageLogCap() {
- if (D) {
- Log.d(TAG, "checking APIUsage log cap.");
- }
-
+ private synchronized boolean hitApiUsageLogCap() {
long currentHour = Instant.now().toEpochMilli() / ONE_HOUR_IN_MILLIS;
if (currentHour > mLastApiUsageLogHour) {
mLastApiUsageLogHour = currentHour;
mApiUsageLogHourlyCount = 0;
- return true;
+ return false;
} else {
mApiUsageLogHourlyCount = Math.min(
- mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP);
- return mApiUsageLogHourlyCount < API_USAGE_LOG_HOURLY_CAP;
- }
- }
-
- /**
- * Log a Location API usage event to Statsd.
- * Logging event is capped at 60 per hour. Usage events exceeding
- * the cap will be dropped by LocationUsageLogger.
- */
- public void logLocationApiUsage(int usageType, int apiInUse,
- String packageName, LocationRequest locationRequest,
- boolean hasListener, boolean hasIntent,
- Geofence geofence, int activityImportance) {
- try {
- if (!checkApiUsageLogCap()) {
- return;
- }
-
- boolean isLocationRequestNull = locationRequest == null;
- boolean isGeofenceNull = geofence == null;
- if (D) {
- Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: "
- + apiInUse + ", packageName: " + (packageName == null ? "" : packageName)
- + ", locationRequest: "
- + (isLocationRequestNull ? "" : locationRequest.toString())
- + ", hasListener: " + hasListener
- + ", hasIntent: " + hasIntent
- + ", geofence: "
- + (isGeofenceNull ? "" : geofence.toString())
- + ", importance: " + activityImportance);
- }
-
- StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType,
- apiInUse, packageName,
- isLocationRequestNull
- ? LocationStatsEnums.PROVIDER_UNKNOWN
- : providerNameToStatsdEnum(locationRequest.getProvider()),
- isLocationRequestNull
- ? LocationStatsEnums.QUALITY_UNKNOWN
- : locationRequest.getQuality(),
- isLocationRequestNull
- ? LocationStatsEnums.INTERVAL_UNKNOWN
- : bucketizeIntervalToStatsdEnum(locationRequest.getInterval()),
- isLocationRequestNull
- ? LocationStatsEnums.DISTANCE_UNKNOWN
- : bucketizeSmallestDisplacementToStatsdEnum(
- locationRequest.getSmallestDisplacement()),
- isLocationRequestNull ? 0 : locationRequest.getNumUpdates(),
- // only log expireIn for USAGE_STARTED
- isLocationRequestNull || usageType == LocationStatsEnums.USAGE_ENDED
- ? LocationStatsEnums.EXPIRATION_UNKNOWN
- : getBucketizedExpireIn(locationRequest.getExpireAt()),
- getCallbackType(apiInUse, hasListener, hasIntent),
- isGeofenceNull
- ? LocationStatsEnums.RADIUS_UNKNOWN
- : bucketizeRadiusToStatsdEnum(geofence.getRadius()),
- categorizeActivityImportance(activityImportance));
- } catch (Exception e) {
- // Swallow exceptions to avoid crashing LMS.
- Log.w(TAG, "Failed to log API usage to statsd.", e);
- }
- }
-
- /**
- * Log a Location API usage event to Statsd.
- * Logging event is capped at 60 per hour. Usage events exceeding
- * the cap will be dropped by LocationUsageLogger.
- */
- public void logLocationApiUsage(int usageType, int apiInUse, String providerName) {
- try {
- if (!checkApiUsageLogCap()) {
- return;
- }
-
- if (D) {
- Log.d(TAG, "log API Usage to statsd. usageType: " + usageType + ", apiInUse: "
- + apiInUse + ", providerName: " + providerName);
- }
-
- StatsLog.write(StatsLog.LOCATION_MANAGER_API_USAGE_REPORTED, usageType, apiInUse,
- /* package_name= */ null,
- providerNameToStatsdEnum(providerName),
- LocationStatsEnums.QUALITY_UNKNOWN,
- LocationStatsEnums.INTERVAL_UNKNOWN,
- LocationStatsEnums.DISTANCE_UNKNOWN,
- /* numUpdates= */ 0,
- LocationStatsEnums.EXPIRATION_UNKNOWN,
- getCallbackType(
- apiInUse,
- /* isListenerNull= */ true,
- /* isIntentNull= */ true),
- /* bucketizedRadius= */ 0,
- LocationStatsEnums.IMPORTANCE_UNKNOWN);
- } catch (Exception e) {
- // Swallow exceptions to avoid crashing LMS.
- Log.w(TAG, "Failed to log API usage to statsd.", e);
+ mApiUsageLogHourlyCount + 1, API_USAGE_LOG_HOURLY_CAP);
+ return mApiUsageLogHourlyCount >= API_USAGE_LOG_HOURLY_CAP;
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index ec4aedd..51fcbb0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1325,6 +1325,8 @@
private void unlockUser(int userId, byte[] token, byte[] secret,
@ChallengeType int challengeType, long challenge,
@Nullable ArrayList<PendingResetLockout> resetLockouts) {
+ Slog.i(TAG, "Unlocking user " + userId + " with secret only, length "
+ + (secret != null ? secret.length : 0));
// TODO: make this method fully async so we can update UI with progress strings
final boolean alreadyUnlocked = mUserManager.isUserUnlockingOrUnlocked(userId);
final CountDownLatch latch = new CountDownLatch(1);
@@ -2651,11 +2653,7 @@
}
}
}
-
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- setUserPasswordMetrics(userCredential, userId);
- unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
-
// Do resetLockout / revokeChallenge when all profiles are unlocked
if (hasEnrolledBiometrics) {
if (resetLockouts == null) {
@@ -2664,18 +2662,13 @@
resetLockouts.add(new PendingResetLockout(userId, response.getPayload()));
}
- final byte[] secret = authResult.authToken.deriveDiskEncryptionKey();
- Slog.i(TAG, "Unlocking user " + userId + " with secret only, length " + secret.length);
- unlockUser(userId, null, secret, challengeType, challenge, resetLockouts);
-
- activateEscrowTokens(authResult.authToken, userId);
-
- if (isManagedProfileWithSeparatedLock(userId)) {
- setDeviceUnlockedForUser(userId);
- }
- mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
-
- onAuthTokenKnownForUser(userId, authResult.authToken);
+ // TODO: Move setUserPasswordMetrics() inside onCredentialVerified(): this will require
+ // LSS to store an encrypted version of the latest password metric for every user,
+ // because user credential is not known when onCredentialVerified() is called during
+ // a token-based unlock.
+ setUserPasswordMetrics(userCredential, userId);
+ onCredentialVerified(authResult.authToken, challengeType, challenge, resetLockouts,
+ userId);
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
if (response.getTimeout() > 0) {
requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
@@ -2685,6 +2678,27 @@
return response;
}
+ private void onCredentialVerified(AuthenticationToken authToken,
+ @ChallengeType int challengeType, long challenge,
+ @Nullable ArrayList<PendingResetLockout> resetLockouts, int userId) {
+
+ unlockKeystore(authToken.deriveKeyStorePassword(), userId);
+
+ {
+ final byte[] secret = authToken.deriveDiskEncryptionKey();
+ unlockUser(userId, null, secret, challengeType, challenge, resetLockouts);
+ Arrays.fill(secret, (byte) 0);
+ }
+ activateEscrowTokens(authToken, userId);
+
+ if (isManagedProfileWithSeparatedLock(userId)) {
+ setDeviceUnlockedForUser(userId);
+ }
+ mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+
+ onAuthTokenKnownForUser(userId, authToken);
+ }
+
private void setDeviceUnlockedForUser(int userId) {
final TrustManager trustManager = mContext.getSystemService(TrustManager.class);
trustManager.setDeviceLockedForUser(userId, false);
@@ -3057,8 +3071,10 @@
return false;
}
}
- unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey());
- onAuthTokenKnownForUser(userId, authResult.authToken);
+ // TODO: Reset biometrics lockout here. Ideally that should be self-contained inside
+ // onCredentialVerified(), which will require some refactoring on the current lockout
+ // reset logic.
+ onCredentialVerified(authResult.authToken, CHALLENGE_NONE, 0, null, userId);
return true;
}
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index eb706d7..1d39177 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -240,8 +240,7 @@
if (!mPrevActiveAudioPlaybackConfigs.containsKey(
config.getPlayerInterfaceId())) {
if (DEBUG) {
- Log.d(TAG, "Found a new active media playback. "
- + AudioPlaybackConfiguration.toLogFriendlyString(config));
+ Log.d(TAG, "Found a new active media playback. " + config);
}
// New active audio playback.
int index = mSortedAudioPlaybackClientUids.indexOf(uid);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 9fcee50..e7b8860 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -183,8 +183,9 @@
}
public void setControlCategories(@NonNull IMediaRouter2Client client,
- @Nullable List<String> categories) {
+ @NonNull List<String> categories) {
Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(categories, "categories must not be null");
final long token = Binder.clearCallingIdentity();
try {
@@ -390,8 +391,11 @@
private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
if (clientRecord != null) {
- clientRecord.mControlCategories = categories;
+ if (clientRecord.mControlCategories.equals(categories)) {
+ return;
+ }
+ clientRecord.mControlCategories = categories;
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateClientUsage,
clientRecord.mUserRecord.mHandler, clientRecord));
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index c25e206..069aeef 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -47,7 +47,8 @@
import android.media.Session2CommandGroup;
import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
-import android.media.session.ICallback;
+import android.media.session.IOnMediaKeyEventDispatchedListener;
+import android.media.session.IOnMediaKeyEventSessionChangedListener;
import android.media.session.IOnMediaKeyListener;
import android.media.session.IOnVolumeKeyLongPressListener;
import android.media.session.ISession;
@@ -70,6 +71,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -749,7 +751,10 @@
private final int mFullUserId;
private final MediaSessionStack mPriorityStack;
- private final HashMap<IBinder, CallbackRecord> mCallbacks = new HashMap<>();
+ private final HashMap<IBinder, OnMediaKeyEventDispatchedListenerRecord>
+ mOnMediaKeyEventDispatchedListeners = new HashMap<>();
+ private final HashMap<IBinder, OnMediaKeyEventSessionChangedListenerRecord>
+ mOnMediaKeyEventSessionChangedListeners = new HashMap<>();
private PendingIntent mLastMediaButtonReceiver;
private ComponentName mRestoredMediaButtonReceiver;
@@ -795,21 +800,47 @@
}
}
- public void registerCallbackLocked(ICallback callback, int uid) {
- IBinder cbBinder = callback.asBinder();
- CallbackRecord cr = new CallbackRecord(callback, uid);
- mCallbacks.put(cbBinder, cr);
+ public void addOnMediaKeyEventDispatchedListenerLocked(
+ IOnMediaKeyEventDispatchedListener listener, int uid) {
+ IBinder cbBinder = listener.asBinder();
+ OnMediaKeyEventDispatchedListenerRecord cr =
+ new OnMediaKeyEventDispatchedListenerRecord(listener, uid);
+ mOnMediaKeyEventDispatchedListeners.put(cbBinder, cr);
try {
cbBinder.linkToDeath(cr, 0);
} catch (RemoteException e) {
- Log.w(TAG, "Failed to register callback", e);
- mCallbacks.remove(cbBinder);
+ Log.w(TAG, "Failed to add listener", e);
+ mOnMediaKeyEventDispatchedListeners.remove(cbBinder);
}
}
- public void unregisterCallbackLocked(ICallback callback) {
- IBinder cbBinder = callback.asBinder();
- CallbackRecord cr = mCallbacks.remove(cbBinder);
+ public void removeOnMediaKeyEventDispatchedListenerLocked(
+ IOnMediaKeyEventDispatchedListener listener) {
+ IBinder cbBinder = listener.asBinder();
+ OnMediaKeyEventDispatchedListenerRecord cr =
+ mOnMediaKeyEventDispatchedListeners.remove(cbBinder);
+ cbBinder.unlinkToDeath(cr, 0);
+ }
+
+ public void addOnMediaKeyEventSessionChangedListenerLocked(
+ IOnMediaKeyEventSessionChangedListener listener, int uid) {
+ IBinder cbBinder = listener.asBinder();
+ OnMediaKeyEventSessionChangedListenerRecord cr =
+ new OnMediaKeyEventSessionChangedListenerRecord(listener, uid);
+ mOnMediaKeyEventSessionChangedListeners.put(cbBinder, cr);
+ try {
+ cbBinder.linkToDeath(cr, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to add listener", e);
+ mOnMediaKeyEventSessionChangedListeners.remove(cbBinder);
+ }
+ }
+
+ public void removeOnMediaKeyEventSessionChangedListener(
+ IOnMediaKeyEventSessionChangedListener listener) {
+ IBinder cbBinder = listener.asBinder();
+ OnMediaKeyEventSessionChangedListenerRecord cr =
+ mOnMediaKeyEventSessionChangedListeners.remove(cbBinder);
cbBinder.unlinkToDeath(cr, 0);
}
@@ -831,8 +862,16 @@
pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
pw.println(indent + "Media key listener package: "
+ getCallingPackageName(mOnMediaKeyListenerUid));
- pw.println(indent + "Callbacks: registered " + mCallbacks.size() + " callback(s)");
- for (CallbackRecord cr : mCallbacks.values()) {
+ pw.println(indent + "OnMediaKeyEventDispatchedListener: added "
+ + mOnMediaKeyEventDispatchedListeners.size() + " listener(s)");
+ for (OnMediaKeyEventDispatchedListenerRecord cr
+ : mOnMediaKeyEventDispatchedListeners.values()) {
+ pw.println(indent + " from " + getCallingPackageName(cr.uid));
+ }
+ pw.println(indent + "OnMediaKeyEventSessionChangedListener: added "
+ + mOnMediaKeyEventSessionChangedListeners.size() + " listener(s)");
+ for (OnMediaKeyEventSessionChangedListenerRecord cr
+ : mOnMediaKeyEventSessionChangedListeners.values()) {
pw.println(indent + " from " + getCallingPackageName(cr.uid));
}
pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
@@ -894,19 +933,22 @@
mFullUserId);
}
- private void pushAddressedPlayerChangedLocked(ICallback callback) {
+ private void pushAddressedPlayerChangedLocked(
+ IOnMediaKeyEventSessionChangedListener callback) {
try {
MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
if (mediaButtonSession != null) {
- callback.onAddressedPlayerChangedToMediaSession(
+ callback.onMediaKeyEventSessionChanged(mediaButtonSession.getPackageName(),
mediaButtonSession.getSessionToken());
} else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
- callback.onAddressedPlayerChangedToMediaButtonReceiver(
+ callback.onMediaKeyEventSessionChanged(
mCurrentFullUserRecord.mLastMediaButtonReceiver
- .getIntent().getComponent());
+ .getIntent().getComponent().getPackageName(),
+ null);
} else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
- callback.onAddressedPlayerChangedToMediaButtonReceiver(
- mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
+ callback.onMediaKeyEventSessionChanged(
+ mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(),
+ null);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
@@ -914,7 +956,8 @@
}
private void pushAddressedPlayerChangedLocked() {
- for (CallbackRecord cr : mCallbacks.values()) {
+ for (OnMediaKeyEventSessionChangedListenerRecord cr
+ : mOnMediaKeyEventSessionChangedListeners.values()) {
pushAddressedPlayerChangedLocked(cr.callback);
}
}
@@ -953,11 +996,12 @@
return COMPONENT_TYPE_BROADCAST;
}
- final class CallbackRecord implements IBinder.DeathRecipient {
- public final ICallback callback;
+ final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient {
+ public final IOnMediaKeyEventDispatchedListener callback;
public final int uid;
- CallbackRecord(ICallback callback, int uid) {
+ OnMediaKeyEventDispatchedListenerRecord(IOnMediaKeyEventDispatchedListener callback,
+ int uid) {
this.callback = callback;
this.uid = uid;
}
@@ -965,7 +1009,25 @@
@Override
public void binderDied() {
synchronized (mLock) {
- mCallbacks.remove(callback.asBinder());
+ mOnMediaKeyEventDispatchedListeners.remove(callback.asBinder());
+ }
+ }
+ }
+
+ final class OnMediaKeyEventSessionChangedListenerRecord implements IBinder.DeathRecipient {
+ public final IOnMediaKeyEventSessionChangedListener callback;
+ public final int uid;
+
+ OnMediaKeyEventSessionChangedListenerRecord(
+ IOnMediaKeyEventSessionChangedListener callback, int uid) {
+ this.callback = callback;
+ this.uid = uid;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mOnMediaKeyEventSessionChangedListeners.remove(callback.asBinder());
}
}
}
@@ -1042,6 +1104,13 @@
private boolean mVoiceButtonHandled = false;
@Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ (new MediaShellCommand()).exec(this, in, out, err, args, callback,
+ resultReceiver);
+ }
+
+ @Override
public ISession createSession(String packageName, ISessionCallback cb, String tag,
Bundle sessionInfo, int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
@@ -1347,7 +1416,8 @@
}
@Override
- public void registerCallback(final ICallback callback) {
+ public void addOnMediaKeyEventDispatchedListener(
+ final IOnMediaKeyEventDispatchedListener listener) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
@@ -1355,18 +1425,18 @@
try {
if (!hasMediaControlPermission(pid, uid)) {
throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
- + " register Callback");
+ + " add MediaKeyEventDispatchedListener");
}
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null || user.mFullUserId != userId) {
- Log.w(TAG, "Only the full user can register the callback"
+ Log.w(TAG, "Only the full user can add the listener"
+ ", userId=" + userId);
return;
}
- user.registerCallbackLocked(callback, uid);
- Log.d(TAG, "The callback (" + callback.asBinder()
- + ") is registered by " + getCallingPackageName(uid));
+ user.addOnMediaKeyEventDispatchedListenerLocked(listener, uid);
+ Log.d(TAG, "The MediaKeyEventDispatchedListener (" + listener.asBinder()
+ + ") is added by " + getCallingPackageName(uid));
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1374,7 +1444,8 @@
}
@Override
- public void unregisterCallback(final ICallback callback) {
+ public void removeOnMediaKeyEventDispatchedListener(
+ final IOnMediaKeyEventDispatchedListener listener) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
@@ -1382,18 +1453,74 @@
try {
if (!hasMediaControlPermission(pid, uid)) {
throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
- + " unregister Callback");
+ + " remove MediaKeyEventDispatchedListener");
}
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null || user.mFullUserId != userId) {
- Log.w(TAG, "Only the full user can unregister the callback"
+ Log.w(TAG, "Only the full user can remove the listener"
+ ", userId=" + userId);
return;
}
- user.unregisterCallbackLocked(callback);
- Log.d(TAG, "The callback (" + callback.asBinder()
- + ") is unregistered by " + getCallingPackageName(uid));
+ user.removeOnMediaKeyEventDispatchedListenerLocked(listener);
+ Log.d(TAG, "The MediaKeyEventDispatchedListener (" + listener.asBinder()
+ + ") is removed by " + getCallingPackageName(uid));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void addOnMediaKeyEventSessionChangedListener(
+ final IOnMediaKeyEventSessionChangedListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!hasMediaControlPermission(pid, uid)) {
+ throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+ + " add MediaKeyEventSessionChangedListener");
+ }
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null || user.mFullUserId != userId) {
+ Log.w(TAG, "Only the full user can add the listener"
+ + ", userId=" + userId);
+ return;
+ }
+ user.addOnMediaKeyEventSessionChangedListenerLocked(listener, uid);
+ Log.d(TAG, "The MediaKeyEventSessionChangedListener (" + listener.asBinder()
+ + ") is added by " + getCallingPackageName(uid));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void removeOnMediaKeyEventSessionChangedListener(
+ final IOnMediaKeyEventSessionChangedListener listener) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(uid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!hasMediaControlPermission(pid, uid)) {
+ throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+ + " remove MediaKeyEventSessionChangedListener");
+ }
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null || user.mFullUserId != userId) {
+ Log.w(TAG, "Only the full user can remove the listener"
+ + ", userId=" + userId);
+ return;
+ }
+ user.removeOnMediaKeyEventSessionChangedListener(listener);
+ Log.d(TAG, "The MediaKeyEventSessionChangedListener (" + listener.asBinder()
+ + ") is removed by " + getCallingPackageName(uid));
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -2007,10 +2134,10 @@
needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
mKeyEventReceiver);
try {
- for (FullUserRecord.CallbackRecord cr
- : mCurrentFullUserRecord.mCallbacks.values()) {
- cr.callback.onMediaKeyEventDispatchedToMediaSession(
- keyEvent, session.getSessionToken());
+ for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+ : mCurrentFullUserRecord.mOnMediaKeyEventDispatchedListeners.values()) {
+ cr.callback.onMediaKeyEventDispatched(
+ keyEvent, session.getPackageName(), session.getSessionToken());
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to send callback", e);
@@ -2040,10 +2167,11 @@
ComponentName componentName = mCurrentFullUserRecord
.mLastMediaButtonReceiver.getIntent().getComponent();
if (componentName != null) {
- for (FullUserRecord.CallbackRecord cr
- : mCurrentFullUserRecord.mCallbacks.values()) {
- cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
- keyEvent, componentName);
+ for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+ : mCurrentFullUserRecord
+ .mOnMediaKeyEventDispatchedListeners.values()) {
+ cr.callback.onMediaKeyEventDispatched(keyEvent,
+ componentName.getPackageName(), null);
}
}
} else {
@@ -2075,10 +2203,11 @@
Log.w(TAG, "Error sending media button to the restored intent "
+ receiver + ", type=" + componentType, e);
}
- for (FullUserRecord.CallbackRecord cr
- : mCurrentFullUserRecord.mCallbacks.values()) {
- cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
- keyEvent, receiver);
+ for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+ : mCurrentFullUserRecord
+ .mOnMediaKeyEventDispatchedListeners.values()) {
+ cr.callback.onMediaKeyEventDispatched(keyEvent,
+ receiver.getPackageName(), null);
}
}
} catch (CanceledException e) {
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/services/core/java/com/android/server/media/MediaShellCommand.java
similarity index 64%
rename from cmds/media/src/com/android/commands/media/Media.java
rename to services/core/java/com/android/server/media/MediaShellCommand.java
index 1e915f8..20df271 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -1,29 +1,27 @@
/*
-**
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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 (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.commands.media;
+package com.android.server.media;
import android.app.ActivityThread;
import android.content.Context;
import android.media.MediaMetadata;
import android.media.session.ISessionManager;
import android.media.session.MediaController;
-import android.media.session.MediaController.PlaybackInfo;
-import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -31,59 +29,41 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ShellCommand;
import android.os.SystemClock;
-import android.util.AndroidException;
+import android.text.TextUtils;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import com.android.internal.os.BaseCommand;
-
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.io.PrintStream;
+import java.io.PrintWriter;
import java.util.List;
-public class Media extends BaseCommand {
+/**
+ * ShellCommand for MediaSessionService.
+ */
+public class MediaShellCommand extends ShellCommand {
// This doesn't belongs to any package. Setting the package name to empty string.
private static final String PACKAGE_NAME = "";
private static ActivityThread sThread;
private static MediaSessionManager sMediaSessionManager;
private ISessionManager mSessionService;
-
- /**
- * Command-line entry point.
- *
- * @param args The command-line arguments
- */
- public static void main(String[] args) {
- (new Media()).run(args);
- }
+ private PrintWriter mWriter;
+ private PrintWriter mErrorWriter;
@Override
- public void onShowUsage(PrintStream out) {
- out.println(
- "usage: media [subcommand] [options]\n" +
- " media dispatch KEY\n" +
- " media list-sessions\n" +
- " media monitor <tag>\n" +
- " media volume [options]\n" +
- "\n" +
- "media dispatch: dispatch a media key to the system.\n" +
- " KEY may be: play, pause, play-pause, mute, headsethook,\n" +
- " stop, next, previous, rewind, record, fast-forword.\n" +
- "media list-sessions: print a list of the current sessions.\n" +
- "media monitor: monitor updates to the specified session.\n" +
- " Use the tag from list-sessions.\n" +
- "media volume: " + VolumeCtrl.USAGE
- );
- }
+ public int onCommand(String cmd) {
+ mWriter = getOutPrintWriter();
+ mErrorWriter = getErrPrintWriter();
- @Override
- public void onRun() throws Exception {
+ if (TextUtils.isEmpty(cmd)) {
+ return handleDefaultCommands(cmd);
+ }
if (sThread == null) {
- Looper.prepareMainLooper();
+ Looper.prepare();
sThread = ActivityThread.systemMain();
Context context = sThread.getSystemContext();
sMediaSessionManager =
@@ -92,25 +72,47 @@
mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
Context.MEDIA_SESSION_SERVICE));
if (mSessionService == null) {
- System.err.println(NO_SYSTEM_ERROR_CODE);
- throw new AndroidException(
+ throw new IllegalStateException(
"Can't connect to media session service; is the system running?");
}
- String op = nextArgRequired();
-
- if (op.equals("dispatch")) {
- runDispatch();
- } else if (op.equals("list-sessions")) {
- runListSessions();
- } else if (op.equals("monitor")) {
- runMonitor();
- } else if (op.equals("volume")) {
- runVolume();
- } else {
- showError("Error: unknown command '" + op + "'");
- return;
+ try {
+ if (cmd.equals("dispatch")) {
+ runDispatch();
+ } else if (cmd.equals("list-sessions")) {
+ runListSessions();
+ } else if (cmd.equals("monitor")) {
+ runMonitor();
+ } else if (cmd.equals("volume")) {
+ runVolume();
+ } else {
+ showError("Error: unknown command '" + cmd + "'");
+ return -1;
+ }
+ } catch (Exception e) {
+ showError(e.toString());
+ return -1;
}
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ mWriter.println("usage: media_session [subcommand] [options]");
+ mWriter.println(" media_session dispatch KEY");
+ mWriter.println(" media_session dispatch KEY");
+ mWriter.println(" media_session list-sessions");
+ mWriter.println(" media_session monitor <tag>");
+ mWriter.println(" media_session volume [options]");
+ mWriter.println();
+ mWriter.println("media_session dispatch: dispatch a media key to the system.");
+ mWriter.println(" KEY may be: play, pause, play-pause, mute, headsethook,");
+ mWriter.println(" stop, next, previous, rewind, record, fast-forword.");
+ mWriter.println("media_session list-sessions: print a list of the current sessions.");
+ mWriter.println("media_session monitor: monitor updates to the specified session.");
+ mWriter.println(" Use the tag from list-sessions.");
+ mWriter.println("media_session volume: " + VolumeCtrl.USAGE);
+ mWriter.println();
}
private void sendMediaKey(KeyEvent event) {
@@ -121,7 +123,7 @@
}
private void runMonitor() throws Exception {
- String id = nextArgRequired();
+ String id = getNextArgRequired();
if (id == null) {
showError("Error: must include a session id");
return;
@@ -133,7 +135,8 @@
for (MediaController controller : controllers) {
try {
if (controller != null && id.equals(controller.getTag())) {
- ControllerMonitor monitor = new ControllerMonitor(controller);
+ MediaShellCommand.ControllerMonitor monitor =
+ new MediaShellCommand.ControllerMonitor(controller);
monitor.run();
success = true;
break;
@@ -143,15 +146,15 @@
}
}
} catch (Exception e) {
- System.out.println("***Error monitoring session*** " + e.getMessage());
+ mErrorWriter.println("***Error monitoring session*** " + e.getMessage());
}
if (!success) {
- System.out.println("No session found with id " + id);
+ mErrorWriter.println("No session found with id " + id);
}
}
private void runDispatch() throws Exception {
- String cmd = nextArgRequired();
+ String cmd = getNextArgRequired();
int keycode;
if ("play".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_PLAY;
@@ -186,68 +189,73 @@
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
}
+ void showError(String errMsg) {
+ onHelp();
+ mErrorWriter.println(errMsg);
+ }
+
class ControllerCallback extends MediaController.Callback {
@Override
public void onSessionDestroyed() {
- System.out.println("onSessionDestroyed. Enter q to quit.");
+ mWriter.println("onSessionDestroyed. Enter q to quit.");
}
@Override
public void onSessionEvent(String event, Bundle extras) {
- System.out.println("onSessionEvent event=" + event + ", extras=" + extras);
+ mWriter.println("onSessionEvent event=" + event + ", extras=" + extras);
}
@Override
public void onPlaybackStateChanged(PlaybackState state) {
- System.out.println("onPlaybackStateChanged " + state);
+ mWriter.println("onPlaybackStateChanged " + state);
}
@Override
public void onMetadataChanged(MediaMetadata metadata) {
String mmString = metadata == null ? null : "title=" + metadata
.getDescription();
- System.out.println("onMetadataChanged " + mmString);
+ mWriter.println("onMetadataChanged " + mmString);
}
@Override
- public void onQueueChanged(List<QueueItem> queue) {
- System.out.println("onQueueChanged, "
+ public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+ mWriter.println("onQueueChanged, "
+ (queue == null ? "null queue" : " size=" + queue.size()));
}
@Override
public void onQueueTitleChanged(CharSequence title) {
- System.out.println("onQueueTitleChange " + title);
+ mWriter.println("onQueueTitleChange " + title);
}
@Override
public void onExtrasChanged(Bundle extras) {
- System.out.println("onExtrasChanged " + extras);
+ mWriter.println("onExtrasChanged " + extras);
}
@Override
- public void onAudioInfoChanged(PlaybackInfo info) {
- System.out.println("onAudioInfoChanged " + info);
+ public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
+ mWriter.println("onAudioInfoChanged " + info);
}
}
private class ControllerMonitor {
private final MediaController mController;
- private final ControllerCallback mControllerCallback;
+ private final MediaShellCommand.ControllerCallback mControllerCallback;
ControllerMonitor(MediaController controller) {
mController = controller;
- mControllerCallback = new ControllerCallback();
+ mControllerCallback = new MediaShellCommand.ControllerCallback();
}
void printUsageMessage() {
try {
- System.out.println("V2Monitoring session " + mController.getTag()
+ mWriter.println("V2Monitoring session " + mController.getTag()
+ "... available commands: play, pause, next, previous");
} catch (RuntimeException e) {
- System.out.println("Error trying to monitor session!");
+ mWriter.println("Error trying to monitor session!");
}
- System.out.println("(q)uit: finish monitoring");
+ mWriter.println("(q)uit: finish monitoring");
}
void run() throws RemoteException {
@@ -258,7 +266,7 @@
try {
mController.registerCallback(mControllerCallback);
} catch (RuntimeException e) {
- System.out.println("Error registering monitor callback");
+ mErrorWriter.println("Error registering monitor callback");
}
}
};
@@ -284,7 +292,7 @@
} else if ("previous".equals(line)) {
dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
} else {
- System.out.println("Invalid command: " + line);
+ mErrorWriter.println("Invalid command: " + line);
}
synchronized (this) {
@@ -316,19 +324,19 @@
mController.dispatchMediaButtonEvent(down);
mController.dispatchMediaButtonEvent(up);
} catch (RuntimeException e) {
- System.out.println("Failed to dispatch " + keyCode);
+ mErrorWriter.println("Failed to dispatch " + keyCode);
}
}
}
private void runListSessions() {
- System.out.println("Sessions:");
+ mWriter.println("Sessions:");
try {
List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
for (MediaController controller : controllers) {
if (controller != null) {
try {
- System.out.println(" tag=" + controller.getTag()
+ mWriter.println(" tag=" + controller.getTag()
+ ", package=" + controller.getPackageName());
} catch (RuntimeException e) {
// ignore
@@ -336,7 +344,7 @@
}
}
} catch (Exception e) {
- System.out.println("***Error listing sessions***");
+ mErrorWriter.println("***Error listing sessions***");
}
}
diff --git a/cmds/media/src/com/android/commands/media/VolumeCtrl.java b/services/core/java/com/android/server/media/VolumeCtrl.java
old mode 100755
new mode 100644
similarity index 64%
rename from cmds/media/src/com/android/commands/media/VolumeCtrl.java
rename to services/core/java/com/android/server/media/VolumeCtrl.java
index 1629c6f..7a26665
--- a/cmds/media/src/com/android/commands/media/VolumeCtrl.java
+++ b/services/core/java/com/android/server/media/VolumeCtrl.java
@@ -1,21 +1,20 @@
/*
-**
-** Copyright 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.
-*/
+ * 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.commands.media;
+package com.android.server.media;
import android.content.Context;
import android.media.AudioManager;
@@ -26,45 +25,41 @@
import com.android.internal.os.BaseCommand;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.lang.ArrayIndexOutOfBoundsException;
-
/**
* Command line tool to exercise AudioService.setStreamVolume()
* and AudioService.adjustStreamVolume()
*/
public class VolumeCtrl {
- private final static String TAG = "VolumeCtrl";
+ private static final String TAG = "VolumeCtrl";
// --stream affects --set, --adj or --get options.
// --show affects --set and --adj options.
// --get can be used with --set, --adj or by itself.
- public final static String USAGE = new String(
- "the options are as follows: \n" +
- "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n" +
- "\t\t controls AudioManager.STREAM_MUSIC if no stream is specified\n"+
- "\t\t--set INDEX sets the volume index value\n" +
- "\t\t--adj DIRECTION adjusts the volume, use raise|same|lower for the direction\n" +
- "\t\t--get outputs the current volume\n" +
- "\t\t--show shows the UI during the volume change\n" +
- "\texamples:\n" +
- "\t\tadb shell media volume --show --stream 3 --set 11\n" +
- "\t\tadb shell media volume --stream 0 --adj lower\n" +
- "\t\tadb shell media volume --stream 3 --get\n"
- );
+ public static final String USAGE = new String("the options are as follows: \n"
+ + "\t\t--stream STREAM selects the stream to control, see AudioManager.STREAM_*\n"
+ + "\t\t controls AudioManager.STREAM_MUSIC if no stream is specified\n"
+ + "\t\t--set INDEX sets the volume index value\n"
+ + "\t\t--adj DIRECTION adjusts the volume, use raise|same|lower for the direction\n"
+ + "\t\t--get outputs the current volume\n"
+ + "\t\t--show shows the UI during the volume change\n"
+ + "\texamples:\n"
+ + "\t\tadb shell media volume --show --stream 3 --set 11\n"
+ + "\t\tadb shell media volume --stream 0 --adj lower\n"
+ + "\t\tadb shell media volume --stream 3 --get\n"
+ );
- private final static int VOLUME_CONTROL_MODE_SET = 1;
- private final static int VOLUME_CONTROL_MODE_ADJUST = 2;
+ private static final int VOLUME_CONTROL_MODE_SET = 1;
+ private static final int VOLUME_CONTROL_MODE_ADJUST = 2;
- private final static String ADJUST_LOWER = "lower";
- private final static String ADJUST_SAME = "same";
- private final static String ADJUST_RAISE = "raise";
+ private static final String ADJUST_LOWER = "lower";
+ private static final String ADJUST_SAME = "same";
+ private static final String ADJUST_RAISE = "raise";
- public static void run(BaseCommand cmd) throws Exception {
+ /**
+ * Runs a given MediaShellCommand
+ */
+ public static void run(MediaShellCommand cmd) throws Exception {
//----------------------------------------
// Default parameters
int stream = AudioManager.STREAM_MUSIC;
@@ -78,7 +73,7 @@
// read options
String option;
String adjustment = null;
- while ((option = cmd.nextOption()) != null) {
+ while ((option = cmd.getNextOption()) != null) {
switch (option) {
case "--show":
showUi = true;
@@ -88,17 +83,17 @@
log(LOG_V, "will get volume");
break;
case "--stream":
- stream = Integer.decode(cmd.nextArgRequired()).intValue();
+ stream = Integer.decode(cmd.getNextArgRequired()).intValue();
log(LOG_V, "will control stream=" + stream + " (" + streamName(stream) + ")");
break;
case "--set":
- volIndex = Integer.decode(cmd.nextArgRequired()).intValue();
+ volIndex = Integer.decode(cmd.getNextArgRequired()).intValue();
mode = VOLUME_CONTROL_MODE_SET;
log(LOG_V, "will set volume to index=" + volIndex);
break;
case "--adj":
mode = VOLUME_CONTROL_MODE_ADJUST;
- adjustment = cmd.nextArgRequired();
+ adjustment = cmd.getNextArgRequired();
log(LOG_V, "will adjust volume");
break;
default:
@@ -140,7 +135,7 @@
if ((volIndex > audioService.getStreamMaxVolume(stream))
|| (volIndex < audioService.getStreamMinVolume(stream))) {
cmd.showError(String.format("Error: invalid volume index %d for stream %d "
- + "(should be in [%d..%d])", volIndex, stream,
+ + "(should be in [%d..%d])", volIndex, stream,
audioService.getStreamMinVolume(stream),
audioService.getStreamMaxVolume(stream)));
return;
@@ -149,7 +144,7 @@
//----------------------------------------
// Non-interactive test
- final int flag = showUi? AudioManager.FLAG_SHOW_UI : 0;
+ final int flag = showUi ? AudioManager.FLAG_SHOW_UI : 0;
final String pack = cmd.getClass().getPackage().getName();
if (mode == VOLUME_CONTROL_MODE_SET) {
audioService.setStreamVolume(stream, volIndex, flag, pack/*callingPackage*/);
@@ -157,9 +152,9 @@
audioService.adjustStreamVolume(stream, adjDir, flag, pack);
}
if (doGet) {
- log(LOG_V, "volume is " + audioService.getStreamVolume(stream) +
- " in range [" + audioService.getStreamMinVolume(stream) +
- ".." + audioService.getStreamMaxVolume(stream) + "]");
+ log(LOG_V, "volume is " + audioService.getStreamVolume(stream)
+ + " in range [" + audioService.getStreamMinVolume(stream)
+ + ".." + audioService.getStreamMaxVolume(stream) + "]");
}
}
@@ -181,5 +176,4 @@
return "invalid stream";
}
}
-
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 82d74bc..2ebca88 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1032,6 +1032,7 @@
// READ_NETWORK_USAGE_HISTORY permission above.
synchronized (mNetworkPoliciesSecondLock) {
+ updateNetworkRulesNL();
updateNetworkEnabledNL();
updateNotificationsNL();
}
@@ -1827,7 +1828,7 @@
}
String[] mergedSubscriberId = ArrayUtils.defeatNullable(
- tm.createForSubscriptionId(subId).getMergedSubscriberIdsFromGroup());
+ tm.createForSubscriptionId(subId).getMergedImsisFromGroup());
mergedSubscriberIdsList.add(mergedSubscriberId);
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 673e830..0288f0c 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -873,6 +873,8 @@
+ mPersistThreshold);
}
+ final long oldGlobalAlertBytes = mGlobalAlertBytes;
+
// update and persist if beyond new thresholds
final long currentTime = mClock.millis();
synchronized (mStatsLock) {
@@ -886,8 +888,9 @@
mUidTagRecorder.maybePersistLocked(currentTime);
}
- // re-arm global alert
- registerGlobalAlert();
+ if (oldGlobalAlertBytes != mGlobalAlertBytes) {
+ registerGlobalAlert();
+ }
}
@Override
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index bc05154..a7e40cb 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.provider.Settings;
import android.telecom.TelecomManager;
import com.android.internal.util.NotificationMessagingUtil;
@@ -55,14 +54,15 @@
final boolean isLeftHighImportance = leftImportance >= IMPORTANCE_DEFAULT;
final boolean isRightHighImportance = rightImportance >= IMPORTANCE_DEFAULT;
- // With new interruption model, prefer importance bucket above all other criteria
- // (to ensure buckets are contiguous)
- if (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) == 1) {
- if (isLeftHighImportance != isRightHighImportance) {
- // by importance bucket, high importance higher than low importance
- return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
- }
+ if (isLeftHighImportance != isRightHighImportance) {
+ // by importance bucket, high importance higher than low importance
+ return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
+ }
+
+ // If a score has been assigned by notification assistant service, use this service
+ // rank results within each bucket instead of this comparator implementation.
+ if (left.getRankingScore() != right.getRankingScore()) {
+ return -1 * Float.compare(left.getRankingScore(), right.getRankingScore());
}
// first all colorized notifications
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b57bfa0..c8afcc9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -144,6 +144,7 @@
private int mSystemImportance = IMPORTANCE_UNSPECIFIED;
private int mAssistantImportance = IMPORTANCE_UNSPECIFIED;
private int mImportance = IMPORTANCE_UNSPECIFIED;
+ private float mRankingScore = 0f;
// Field used in global sort key to bypass normal notifications
private int mCriticality = CriticalNotificationExtractor.NORMAL;
// A MetricsEvent.NotificationImportanceExplanation, tracking source of mImportance.
@@ -655,6 +656,9 @@
importance = Math.min(IMPORTANCE_HIGH, importance);
setAssistantImportance(importance);
}
+ if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
+ mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
+ }
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
}
@@ -772,6 +776,10 @@
return mImportance;
}
+ public float getRankingScore() {
+ return mRankingScore;
+ }
+
public CharSequence getImportanceExplanation() {
switch (mImportanceExplanationCode) {
case MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN:
@@ -1289,6 +1297,18 @@
return getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
}
+ public boolean hasUndecoratedRemoteView() {
+ Notification notification = getNotification();
+ Class<? extends Notification.Style> style = notification.getNotificationStyle();
+ boolean hasDecoratedStyle = style != null
+ && (Notification.DecoratedCustomViewStyle.class.equals(style)
+ || Notification.DecoratedMediaCustomViewStyle.class.equals(style));
+ boolean hasCustomRemoteView = notification.contentView != null
+ || notification.bigContentView != null
+ || notification.headsUpContentView != null;
+ return hasCustomRemoteView && !hasDecoratedStyle;
+ }
+
@VisibleForTesting
static final class Light {
public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index ac8d1a9..d1fe0d9 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -149,7 +149,7 @@
stats.numPostedByApp++;
stats.updateInterarrivalEstimate(now);
stats.countApiUse(notification);
- stats.numUndecoratedRemoteViews += (isUndecoratedRemoteView(notification) ? 1 : 0);
+ stats.numUndecoratedRemoteViews += (notification.hasUndecoratedRemoteView() ? 1 : 0);
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
if (ENABLE_SQLITE_LOG) {
@@ -158,13 +158,6 @@
}
/**
- * Does this notification use RemoveViews without a platform decoration?
- */
- protected static boolean isUndecoratedRemoteView(NotificationRecord notification) {
- return (notification.getNotification().getNotificationStyle() == null);
- }
-
- /**
* Called when a notification has been updated.
*/
public synchronized void registerUpdatedByApp(NotificationRecord notification,
@@ -1303,7 +1296,7 @@
} else {
putPosttimeVisibility(r, cv);
}
- cv.put(COL_UNDECORATED, (isUndecoratedRemoteView(r) ? 1 : 0));
+ cv.put(COL_UNDECORATED, (r.hasUndecoratedRemoteView() ? 1 : 0));
SQLiteDatabase db = mHelper.getWritableDatabase();
if (db.insert(TAB_LOG, null, cv) < 0) {
Log.wtf(TAG, "Error while trying to insert values: " + cv);
diff --git a/tests/utils/testutils/java/test/package-info.java b/services/core/java/com/android/server/package-info.java
similarity index 77%
rename from tests/utils/testutils/java/test/package-info.java
rename to services/core/java/com/android/server/package-info.java
index c34d7b2..dd94edd 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/services/core/java/com/android/server/package-info.java
@@ -15,7 +15,8 @@
*/
/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+ * @hide
+ * TODO(b/146466118) remove this javadoc tag
+ */
+@android.annotation.Hide
+package com.android.server;
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 2b4b409..5ae8c58 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -17,11 +17,11 @@
package com.android.server.pm;
import android.annotation.IntDef;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
import android.apex.IApexService;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -176,13 +176,9 @@
* enough for it to be activated at the next boot, the caller needs to call
* {@link #markStagedSessionReady(int)}.
*
- * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
- * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
- * an array of identifiers of all the child sessions. Otherwise it should
- * be an empty array.
* @throws PackageManagerException if call to apexd fails
*/
- abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
+ abstract ApexInfoList submitStagedSession(ApexSessionParams params)
throws PackageManagerException;
/**
@@ -450,11 +446,10 @@
}
@Override
- ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
- throws PackageManagerException {
+ ApexInfoList submitStagedSession(ApexSessionParams params) throws PackageManagerException {
try {
final ApexInfoList apexInfoList = new ApexInfoList();
- mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
+ mApexService.submitStagedSession(params, apexInfoList);
return apexInfoList;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
@@ -686,7 +681,7 @@
}
@Override
- ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds)
+ ApexInfoList submitStagedSession(ApexSessionParams params)
throws PackageManagerException {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"Device doesn't support updating APEX");
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index f2a2e65..13bd7e5 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -627,11 +627,13 @@
final int providersSize = ArrayUtils.size(pkg.getProviders());
StringBuilder r = null;
for (int i = 0; i < providersSize; i++) {
- EffectiveProvider p = new EffectiveProvider(pkg.getProviders().get(i));
+ ParsedProvider p = pkg.getProviders().get(i);
mProviders.addProvider(p);
if (p.getAuthority() != null) {
String[] names = p.getAuthority().split(";");
- p.setEffectiveAuthority(null);
+
+ // TODO(b/135203078): Remove this mutation
+ p.setAuthority(null);
for (int j = 0; j < names.length; j++) {
if (j == 1 && p.isSyncable()) {
// We only want the first authority for a provider to possibly be
@@ -641,15 +643,15 @@
// to a provider that we don't want to change.
// Only do this for the second authority since the resulting provider
// object can be the same for all future authorities for this provider.
- p = new EffectiveProvider(p);
- p.setEffectiveSyncable(false);
+ p = new ParsedProvider(p);
+ p.setSyncable(false);
}
if (!mProvidersByAuthority.containsKey(names[j])) {
mProvidersByAuthority.put(names[j], p);
if (p.getAuthority() == null) {
- p.setEffectiveAuthority(names[j]);
+ p.setAuthority(names[j]);
} else {
- p.setEffectiveAuthority(p.getAuthority() + ";" + names[j]);
+ p.setAuthority(p.getAuthority() + ";" + names[j]);
}
if (DEBUG_PACKAGE_SCANNING && chatty) {
Log.d(TAG, "Registered content provider: " + names[j]
@@ -2113,35 +2115,4 @@
return info.authoritiesIterator();
}
}
-
- // TODO(b/135203078): Document or remove this if possible.
- class EffectiveProvider extends ParsedProvider {
-
- private String mEffectiveAuthority;
- private boolean mEffectiveSyncable;
-
- public EffectiveProvider(ParsedProvider parsedProvider) {
- this.setFrom(parsedProvider);
- this.mEffectiveAuthority = parsedProvider.getAuthority();
- this.mEffectiveSyncable = parsedProvider.isSyncable();
- }
-
- public void setEffectiveAuthority(String authority) {
- this.mEffectiveAuthority = authority;
- }
-
- public void setEffectiveSyncable(boolean syncable) {
- this.mEffectiveSyncable = syncable;
- }
-
- @Override
- public String getAuthority() {
- return mEffectiveAuthority;
- }
-
- @Override
- public boolean isSyncable() {
- return mEffectiveSyncable;
- }
- }
}
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 0719797..0dfea7f 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -76,13 +76,12 @@
return false;
}
}
- CharSequence packageNameSeq = params.getCharSequence("packageName");
- if (packageNameSeq == null) {
- Slog.e(TAG, "Must specify package name.");
+ ComponentName componentName = params.getParcelable("componentName");
+ if (componentName == null) {
+ Slog.e(TAG, "Must specify component name.");
return false;
}
- String packageName = packageNameSeq.toString();
- ComponentName dataLoaderComponent = getDataLoaderServiceName(packageName);
+ ComponentName dataLoaderComponent = resolveDataLoaderComponentName(componentName);
if (dataLoaderComponent == null) {
return false;
}
@@ -103,22 +102,23 @@
/**
* Find the ComponentName of the data loader service provider, given its package name.
*
- * @param packageName the package name of the provider.
+ * @param componentName the name of the provider.
* @return ComponentName of the data loader service provider. Null if provider not found.
*/
- private @Nullable ComponentName getDataLoaderServiceName(String packageName) {
+ private @Nullable ComponentName resolveDataLoaderComponentName(
+ ComponentName componentName) {
final PackageManager pm = mContext.getPackageManager();
if (pm == null) {
Slog.e(TAG, "PackageManager is not available.");
return null;
}
Intent intent = new Intent(Intent.ACTION_LOAD_DATA);
- intent.setPackage(packageName);
+ intent.setComponent(componentName);
List<ResolveInfo> services =
pm.queryIntentServicesAsUser(intent, 0, UserHandle.getCallingUserId());
if (services == null || services.isEmpty()) {
Slog.e(TAG,
- "Failed to find data loader service provider in package " + packageName);
+ "Failed to find data loader service provider in " + componentName);
return null;
}
@@ -128,23 +128,21 @@
int numServices = services.size();
for (int i = 0; i < numServices; i++) {
ResolveInfo ri = services.get(i);
- ComponentName componentName = new ComponentName(
+ ComponentName resolved = new ComponentName(
ri.serviceInfo.packageName, ri.serviceInfo.name);
// There should only be one matching provider inside the given package.
// If there's more than one, return the first one found.
try {
- ApplicationInfo ai = pm.getApplicationInfo(componentName.getPackageName(), 0);
+ ApplicationInfo ai = pm.getApplicationInfo(resolved.getPackageName(), 0);
if (checkLoader && !ai.isPrivilegedApp()) {
Slog.w(TAG,
- "Data loader: " + componentName.getPackageName()
- + " is not a privileged app, skipping.");
+ "Data loader: " + resolved + " is not a privileged app, skipping.");
continue;
}
- return componentName;
+ return resolved;
} catch (PackageManager.NameNotFoundException ex) {
Slog.w(TAG,
- "Privileged data loader: " + componentName.getPackageName()
- + " not found, skipping.");
+ "Privileged data loader: " + resolved + " not found, skipping.");
}
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 26cd42d..f962eed 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -28,6 +28,7 @@
import android.os.IInstalld;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.storage.CrateMetadata;
import android.text.format.DateUtils;
import android.util.Slog;
@@ -293,6 +294,43 @@
}
}
+ /**
+ * To get all of the CrateMetadata of the crates for the specified user app by the installd.
+ *
+ * @param uuid the UUID
+ * @param packageNames the application package names
+ * @param userId the user id
+ * @return the array of CrateMetadata
+ */
+ @Nullable
+ public CrateMetadata[] getAppCrates(@NonNull String uuid, @NonNull String[] packageNames,
+ @UserIdInt int userId) throws InstallerException {
+ if (!checkBeforeRemote()) return null;
+ try {
+ return mInstalld.getAppCrates(uuid, packageNames, userId);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ /**
+ * To retrieve all of the CrateMetadata of the crate for the specified user app by the installd.
+ *
+ * @param uuid the UUID
+ * @param userId the user id
+ * @return the array of CrateMetadata
+ */
+ @Nullable
+ public CrateMetadata[] getUserCrates(String uuid, @UserIdInt int userId)
+ throws InstallerException {
+ if (!checkBeforeRemote()) return null;
+ try {
+ return mInstalld.getUserCrates(uuid, userId);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void setAppQuota(String uuid, int userId, int appId, long cacheQuota)
throws InstallerException {
if (!checkBeforeRemote()) return;
@@ -580,6 +618,30 @@
}
}
+ /**
+ * Bind mount private volume CE and DE mirror storage.
+ */
+ public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.onPrivateVolumeMounted(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ /**
+ * Unmount private volume CE and DE mirror storage.
+ */
+ public void onPrivateVolumeRemoved(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.onPrivateVolumeRemoved(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
String profileName, String codePath, String dexMetadataPath) throws InstallerException {
if (!checkBeforeRemote()) return false;
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 30b2c9d..8333ae5 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -39,6 +39,7 @@
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.InstantAppIntentFilter;
import android.content.pm.InstantAppRequest;
+import android.content.pm.InstantAppRequestInfo;
import android.content.pm.InstantAppResolveInfo;
import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
import android.metrics.LogMaker;
@@ -47,6 +48,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -63,7 +66,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
-import java.util.UUID;
/** @hide */
public abstract class InstantAppResolver {
@@ -117,26 +119,40 @@
return sanitizedIntent;
}
+ /**
+ * Generate an {@link InstantAppDigest} from an {@link Intent} which contains hashes of the
+ * host. The object contains both secure and insecure hash array variants, and the secure
+ * version must be passed along to ensure the random data is consistent.
+ */
+ @NonNull
+ public static InstantAppDigest parseDigest(@NonNull Intent origIntent) {
+ if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) {
+ return new InstantAppResolveInfo.InstantAppDigest(origIntent.getData().getHost(),
+ 5 /*maxDigests*/);
+ } else {
+ return InstantAppResolveInfo.InstantAppDigest.UNDEFINED;
+ }
+ }
+
public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(
InstantAppResolverConnection connection, InstantAppRequest requestObj) {
final long startTime = System.currentTimeMillis();
- final String token = UUID.randomUUID().toString();
+ final String token = requestObj.token;
if (DEBUG_INSTANT) {
Log.d(TAG, "[" + token + "] Phase1; resolving");
}
- final Intent origIntent = requestObj.origIntent;
- final Intent sanitizedIntent = sanitizeIntent(origIntent);
AuxiliaryResolveInfo resolveInfo = null;
@ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
+ Intent origIntent = requestObj.origIntent;
try {
final List<InstantAppResolveInfo> instantAppResolveInfoList =
- connection.getInstantAppResolveInfoList(sanitizedIntent,
- requestObj.digest.getDigestPrefixSecure(), requestObj.userId, token);
+ connection.getInstantAppResolveInfoList(buildRequestInfo(requestObj));
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
resolveInfo = InstantAppResolver.filterInstantAppIntent(
instantAppResolveInfoList, origIntent, requestObj.resolvedType,
- requestObj.userId, origIntent.getPackage(), requestObj.digest, token);
+ requestObj.userId, origIntent.getPackage(), token,
+ requestObj.hostDigestPrefixSecure);
}
} catch (ConnectionException e) {
if (e.failure == ConnectionException.FAILURE_BIND) {
@@ -166,7 +182,7 @@
// if the match external flag is set, return an empty resolve info instead of a null result.
if (resolveInfo == null && (origIntent.getFlags() & FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
return new AuxiliaryResolveInfo(token, false, createFailureIntent(origIntent, token),
- null /* filters */);
+ null /* filters */, requestObj.hostDigestPrefixSecure);
}
return resolveInfo;
}
@@ -175,7 +191,7 @@
InstantAppResolverConnection connection, InstantAppRequest requestObj,
ActivityInfo instantAppInstaller, Handler callbackHandler) {
final long startTime = System.currentTimeMillis();
- final String token = requestObj.responseObj.token;
+ final String token = requestObj.token;
if (DEBUG_INSTANT) {
Log.d(TAG, "[" + token + "] Phase2; resolving");
}
@@ -191,8 +207,8 @@
final AuxiliaryResolveInfo instantAppIntentInfo =
InstantAppResolver.filterInstantAppIntent(
instantAppResolveInfoList, origIntent, null /*resolvedType*/,
- 0 /*userId*/, origIntent.getPackage(), requestObj.digest,
- token);
+ 0 /*userId*/, origIntent.getPackage(),
+ token, requestObj.hostDigestPrefixSecure);
if (instantAppIntentInfo != null) {
failureIntent = instantAppIntentInfo.failureIntent;
} else {
@@ -223,8 +239,7 @@
}
};
try {
- connection.getInstantAppIntentFilterList(sanitizedIntent,
- requestObj.digest.getDigestPrefixSecure(), requestObj.userId, token, callback,
+ connection.getInstantAppIntentFilterList(buildRequestInfo(requestObj), callback,
callbackHandler, startTime);
} catch (ConnectionException e) {
@ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
@@ -356,10 +371,22 @@
return intent;
}
+ private static InstantAppRequestInfo buildRequestInfo(InstantAppRequest request) {
+ return new InstantAppRequestInfo(
+ sanitizeIntent(request.origIntent),
+ // This must only expose the secured version of the host
+ request.hostDigestPrefixSecure,
+ UserHandle.getUserHandleForUid(request.userId),
+ request.isRequesterInstantApp,
+ request.token
+ );
+ }
+
private static AuxiliaryResolveInfo filterInstantAppIntent(
- List<InstantAppResolveInfo> instantAppResolveInfoList,
- Intent origIntent, String resolvedType, int userId, String packageName,
- InstantAppDigest digest, String token) {
+ List<InstantAppResolveInfo> instantAppResolveInfoList, Intent origIntent,
+ String resolvedType, int userId, String packageName, String token,
+ int[] hostDigestPrefixSecure) {
+ InstantAppDigest digest = InstantAppResolver.parseDigest(origIntent);
final int[] shaPrefix = digest.getDigestPrefix();
final byte[][] digestBytes = digest.getDigestBytes();
boolean requiresSecondPhase = false;
@@ -404,7 +431,7 @@
}
if (filters != null && !filters.isEmpty()) {
return new AuxiliaryResolveInfo(token, requiresSecondPhase,
- createFailureIntent(origIntent, token), filters);
+ createFailureIntent(origIntent, token), filters, hostDigestPrefixSecure);
}
// Hash or filter mis-match; no instant apps for this domain.
return null;
diff --git a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
index c0e9f58..0fe2eb1 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.InstantAppRequestInfo;
import android.content.pm.InstantAppResolveInfo;
import android.os.Binder;
import android.os.Build;
@@ -85,13 +86,13 @@
mBgHandler = BackgroundThread.getHandler();
}
- public List<InstantAppResolveInfo> getInstantAppResolveInfoList(Intent sanitizedIntent,
- int[] hashPrefix, int userId, String token) throws ConnectionException {
+ public List<InstantAppResolveInfo> getInstantAppResolveInfoList(InstantAppRequestInfo request)
+ throws ConnectionException {
throwIfCalledOnMainThread();
IInstantAppResolver target = null;
try {
try {
- target = getRemoteInstanceLazy(token);
+ target = getRemoteInstanceLazy(request.token);
} catch (TimeoutException e) {
throw new ConnectionException(ConnectionException.FAILURE_BIND);
} catch (InterruptedException e) {
@@ -99,8 +100,7 @@
}
try {
return mGetInstantAppResolveInfoCaller
- .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, userId,
- token);
+ .getInstantAppResolveInfoList(target, request);
} catch (TimeoutException e) {
throw new ConnectionException(ConnectionException.FAILURE_CALL);
} catch (RemoteException ignore) {
@@ -113,8 +113,8 @@
return null;
}
- public void getInstantAppIntentFilterList(Intent sanitizedIntent, int[] hashPrefix, int userId,
- String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
+ public void getInstantAppIntentFilterList(InstantAppRequestInfo request,
+ PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
throws ConnectionException {
final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
@Override
@@ -126,9 +126,8 @@
}
};
try {
- getRemoteInstanceLazy(token)
- .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, userId, token,
- remoteCallback);
+ getRemoteInstanceLazy(request.token)
+ .getInstantAppIntentFilterList(request, remoteCallback);
} catch (TimeoutException e) {
throw new ConnectionException(ConnectionException.FAILURE_BIND);
} catch (InterruptedException e) {
@@ -352,12 +351,10 @@
};
}
- public List<InstantAppResolveInfo> getInstantAppResolveInfoList(
- IInstantAppResolver target, Intent sanitizedIntent, int[] hashPrefix, int userId,
- String token) throws RemoteException, TimeoutException {
+ public List<InstantAppResolveInfo> getInstantAppResolveInfoList(IInstantAppResolver target,
+ InstantAppRequestInfo request) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
- target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, userId, token,
- sequence, mCallback);
+ target.getInstantAppResolveInfoList(request, sequence, mCallback);
return getResultTimed(sequence);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index dceca0a..e2dfa12 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -217,6 +217,7 @@
public void systemReady() {
mAppOps = mContext.getSystemService(AppOpsManager.class);
+ mStagingManager.systemReady();
synchronized (mSessions) {
readSessionsLocked();
@@ -257,8 +258,9 @@
}
// Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
// atomic install which needs to query sessions, which requires lock on mSessions.
+ boolean isDeviceUpgrading = mPm.isDeviceUpgrading();
for (PackageInstallerSession session : stagedSessionsToRestore) {
- if (mPm.isDeviceUpgrading() && !session.isStagedAndInTerminalState()) {
+ if (isDeviceUpgrading && !session.isStagedAndInTerminalState()) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"Build fingerprint has changed");
}
@@ -637,7 +639,7 @@
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
installSource, params, createdMillis,
- stageDir, stageCid, false, false, false, null, SessionInfo.INVALID_ID,
+ stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
@@ -1014,12 +1016,28 @@
}
}
+ static void sendPendingStreaming(Context context, IntentSender target, int sessionId,
+ Throwable cause) {
+ final Intent intent = new Intent();
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_STREAMING);
+ if (cause != null && !TextUtils.isEmpty(cause.getMessage())) {
+ intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
+ "Staging Image Not Ready [" + cause.getMessage() + "]");
+ } else {
+ intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
+ }
+ try {
+ target.sendIntent(context, 0, intent, null, null);
+ } catch (SendIntentException ignored) {
+ }
+ }
+
static void sendOnUserActionRequired(Context context, IntentSender target, int sessionId,
Intent intent) {
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
- fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_PENDING_USER_ACTION);
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
try {
target.sendIntent(context, 0, fillIn, null, null);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 518ec50..ac183dc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -16,11 +16,14 @@
package com.android.server.pm;
+import static android.content.pm.DataLoaderType.INCREMENTAL;
+import static android.content.pm.DataLoaderType.STREAMING;
import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
@@ -30,11 +33,13 @@
import static com.android.internal.util.XmlUtils.readBitmapAttribute;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.readUriAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -46,14 +51,21 @@
import android.annotation.Nullable;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
+import android.content.pm.DataLoaderManager;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.FileSystemControlParcel;
+import android.content.pm.IDataLoader;
+import android.content.pm.IDataLoaderStatusListener;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstallerSession;
+import android.content.pm.IPackageInstallerSessionFileSystemConnector;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -80,6 +92,7 @@
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.Process;
+import android.os.RemoteException;
import android.os.RevocableFileDescriptor;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -128,6 +141,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String TAG = "PackageInstallerSession";
@@ -142,6 +156,7 @@
/** XML constants used for persisting a session */
static final String TAG_SESSION = "session";
static final String TAG_CHILD_SESSION = "childSession";
+ static final String TAG_SESSION_FILE = "sessionFile";
private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION =
"whitelisted-restricted-permission";
@@ -183,6 +198,13 @@
private static final String ATTR_VOLUME_UUID = "volumeUuid";
private static final String ATTR_NAME = "name";
private static final String ATTR_INSTALL_REASON = "installRason";
+ private static final String ATTR_IS_DATALOADER = "isDataLoader";
+ private static final String ATTR_DATALOADER_TYPE = "dataLoaderType";
+ private static final String ATTR_DATALOADER_PACKAGE_NAME = "dataLoaderPackageName";
+ private static final String ATTR_DATALOADER_CLASS_NAME = "dataLoaderClassName";
+ private static final String ATTR_DATALOADER_ARGUMENTS = "dataLoaderArguments";
+ private static final String ATTR_LENGTH_BYTES = "lengthBytes";
+ private static final String ATTR_METADATA = "metadata";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
@@ -278,6 +300,29 @@
@GuardedBy("mLock")
private int mParentSessionId;
+ static class FileInfo {
+ public final String name;
+ public final Long lengthBytes;
+ public final byte[] metadata;
+
+ public static FileInfo added(String name, Long lengthBytes, byte[] metadata) {
+ return new FileInfo(name, lengthBytes, metadata);
+ }
+
+ public static FileInfo removed(String name) {
+ return new FileInfo(name, -1L, null);
+ }
+
+ FileInfo(String name, Long lengthBytes, byte[] metadata) {
+ this.name = name;
+ this.lengthBytes = lengthBytes;
+ this.metadata = metadata;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private ArrayList<FileInfo> mFiles = new ArrayList<>();
+
@GuardedBy("mLock")
private boolean mStagedSessionApplied;
@GuardedBy("mLock")
@@ -313,6 +358,7 @@
@GuardedBy("mLock")
private boolean mVerityFound;
+ // TODO(b/146080380): merge file list with Callback installation.
private IncrementalFileStorages mIncrementalFileStorages;
private static final FileFilter sAddedFilter = new FileFilter() {
@@ -344,7 +390,9 @@
IntentSender statusReceiver;
switch (msg.what) {
case MSG_SEAL:
- handleSeal((IntentSender) msg.obj);
+ statusReceiver = (IntentSender) msg.obj;
+
+ handleSeal(statusReceiver);
break;
case MSG_COMMIT:
handleCommit();
@@ -378,6 +426,18 @@
}
};
+ private boolean isDataLoaderInstallation() {
+ return params.dataLoaderParams != null;
+ }
+
+ private boolean isStreamingInstallation() {
+ return isDataLoaderInstallation() && params.dataLoaderParams.getType() == STREAMING;
+ }
+
+ private boolean isIncrementalInstallation() {
+ return isDataLoaderInstallation() && params.dataLoaderParams.getType() == INCREMENTAL;
+ }
+
/**
* @return {@code true} iff the installing is app an device owner or affiliated profile owner.
*/
@@ -435,7 +495,8 @@
PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
SessionParams params, long createdMillis,
- File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed,
+ File stageDir, String stageCid, FileInfo[] files, boolean prepared,
+ boolean committed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
String stagedSessionErrorMessage) {
@@ -464,6 +525,12 @@
}
this.mParentSessionId = parentSessionId;
+ if (files != null) {
+ for (FileInfo file : files) {
+ mFiles.add(file);
+ }
+ }
+
if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) {
throw new IllegalArgumentException(
"Exactly one of stageDir or stageCid stage must be set");
@@ -479,14 +546,13 @@
stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
// TODO(b/136132412): sanity check if session should not be incremental
- if (!params.isStaged && params.incrementalParams != null
- && !params.incrementalParams.getPackageName().isEmpty()) {
+ if (!params.isStaged && isIncrementalInstallation()) {
IncrementalManager incrementalManager = (IncrementalManager) mContext.getSystemService(
Context.INCREMENTAL_SERVICE);
if (incrementalManager != null) {
mIncrementalFileStorages =
new IncrementalFileStorages(mPackageName, stageDir, incrementalManager,
- params.incrementalParams);
+ params.dataLoaderParams);
}
}
}
@@ -526,6 +592,7 @@
info.installFlags = params.installFlags;
info.isMultiPackage = params.isMultiPackage;
info.isStaged = params.isStaged;
+ info.rollbackDataPolicy = params.rollbackDataPolicy;
info.parentSessionId = mParentSessionId;
info.childSessionIds = mChildSessionIds.copyKeys();
if (info.childSessionIds == null) {
@@ -592,15 +659,19 @@
}
}
+ @GuardedBy("mLock")
+ private void setClientProgressLocked(float progress) {
+ // Always publish first staging movement
+ final boolean forcePublish = (mClientProgress == 0);
+ mClientProgress = progress;
+ computeProgressLocked(forcePublish);
+ }
+
@Override
public void setClientProgress(float progress) {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
-
- // Always publish first staging movement
- final boolean forcePublish = (mClientProgress == 0);
- mClientProgress = progress;
- computeProgressLocked(forcePublish);
+ setClientProgressLocked(progress);
}
}
@@ -608,8 +679,7 @@
public void addClientProgress(float progress) {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
-
- setClientProgress(mClientProgress + progress);
+ setClientProgressLocked(mClientProgress + progress);
}
}
@@ -637,7 +707,10 @@
@GuardedBy("mLock")
private String[] getNamesLocked() {
- return stageDir.list();
+ if (!isDataLoaderInstallation()) {
+ return stageDir.list();
+ }
+ return mFiles.stream().map(fileInfo -> fileInfo.name).toArray(String[]::new);
}
private static File[] filterFiles(File parent, String[] names, FileFilter filter) {
@@ -659,6 +732,10 @@
@Override
public void removeSplit(String splitName) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot remove splits in a data loader installation session.");
+ }
if (TextUtils.isEmpty(params.appPackageName)) {
throw new IllegalStateException("Must specify package name to remove a split");
}
@@ -693,8 +770,31 @@
}
}
+ private void assertCanWrite(boolean reverseMode) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot write regular files in a data loader installation session.");
+ }
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ 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");
+ }
+ }
+ }
+
@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+ assertCanWrite(false);
try {
return doWriteInternal(name, offsetBytes, lengthBytes, null);
} catch (IOException e) {
@@ -705,6 +805,7 @@
@Override
public void write(String name, long offsetBytes, long lengthBytes,
ParcelFileDescriptor fd) {
+ assertCanWrite(fd != null);
try {
doWriteInternal(name, offsetBytes, lengthBytes, fd);
} catch (IOException e) {
@@ -720,9 +821,6 @@
final RevocableFileDescriptor fd;
final FileBridge bridge;
synchronized (mLock) {
- assertCallerIsOwnerOrRootLocked();
- assertPreparedAndNotSealedLocked("openWrite");
-
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
fd = new RevocableFileDescriptor();
bridge = null;
@@ -765,16 +863,6 @@
}
if (incomingFd != null) {
- 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");
- }
-
// In "reverse" mode, we're streaming data ourselves from the
// incoming FD, which means we never have to hand out our
// sensitive internal FD. We still rely on a "bridge" being
@@ -786,7 +874,10 @@
if (params.sizeBytes > 0) {
final long delta = progress - last.value;
last.value = progress;
- addClientProgress((float) delta / (float) params.sizeBytes);
+ synchronized (mLock) {
+ setClientProgressLocked(mClientProgress
+ + (float) delta / (float) params.sizeBytes);
+ }
}
});
} finally {
@@ -821,6 +912,10 @@
@Override
public ParcelFileDescriptor openRead(String name) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot read regular files in a data loader installation session.");
+ }
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
@@ -926,9 +1021,25 @@
return;
}
}
+
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
+ private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub {
+ @Override
+ public void writeData(String name, long offsetBytes, long lengthBytes,
+ ParcelFileDescriptor incomingFd) {
+ if (incomingFd == null) {
+ throw new IllegalArgumentException("incomingFd can't be null");
+ }
+ try {
+ doWriteInternal(name, offsetBytes, lengthBytes, incomingFd);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
+ }
+ }
+
private class ChildStatusIntentReceiver {
private final SparseIntArray mChildSessionsRemaining;
private final IntentSender mStatusReceiver;
@@ -988,6 +1099,14 @@
}
}
+ /** {@hide} */
+ private class StreamingException extends Exception {
+ StreamingException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+
/**
* Sanity checks to make sure it's ok to commit the session.
*/
@@ -1039,12 +1158,22 @@
}
wasSealed = mSealed;
- if (!mSealed) {
+ try {
+ if (!mSealed) {
+ sealLocked(childSessions);
+ }
+
try {
- sealAndValidateLocked(childSessions);
- } catch (PackageManagerException e) {
+ streamAndValidateLocked();
+ } catch (StreamingException e) {
+ // In case of streaming failure we don't want to fail or commit the session.
+ // Just return from this method and allow caller to commit again.
+ PackageInstallerService.sendPendingStreaming(mContext, mRemoteStatusReceiver,
+ sessionId, e);
return false;
}
+ } catch (PackageManagerException e) {
+ return false;
}
// Client staging is fully done at this point
@@ -1131,17 +1260,26 @@
}
/**
- * Seal the session to prevent further modification and validate the contents of it.
+ * Convenience wrapper, see {@link #sealLocked(List<PackageInstallerSession>) seal} and
+ * {@link #streamAndValidateLocked()}.
+ */
+ @GuardedBy("mLock")
+ private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
+ throws PackageManagerException, StreamingException {
+ sealLocked(childSessions);
+ streamAndValidateLocked();
+ }
+
+ /**
+ * Seal the session to prevent further modification.
*
* <p>The session will be sealed after calling this method even if it failed.
*
- * @param childSessions the child sessions of a multipackage that will be checked for
- * consistency. Can be null if session is not multipackage.
* @throws PackageManagerException if the session was sealed but something went wrong. If the
* session was sealed this is the only possible exception.
*/
@GuardedBy("mLock")
- private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
+ private void sealLocked(List<PackageInstallerSession> childSessions)
throws PackageManagerException {
try {
assertNoWriteFileTransfersOpenLocked();
@@ -1152,7 +1290,26 @@
if (childSessions != null) {
assertMultiPackageConsistencyLocked(childSessions);
}
+ } catch (PackageManagerException e) {
+ throw onSessionVerificationFailure(e);
+ } catch (Throwable e) {
+ // Convert all exceptions into package manager exceptions as only those are handled
+ // in the code above.
+ throw onSessionVerificationFailure(new PackageManagerException(e));
+ }
+ }
+ /**
+ * Prepare DataLoader and stream content for DataLoader sessions.
+ * Validate the contents of all session.
+ *
+ * @throws StreamingException if streaming failed.
+ * @throws PackageManagerException if validation failed.
+ */
+ @GuardedBy("mLock")
+ private void streamAndValidateLocked()
+ throws PackageManagerException, StreamingException {
+ try {
// Read transfers from the original owner stay open, but as the session's data cannot
// be modified anymore, there is no leak of information. For staged sessions, further
// validation is performed by the staging manager.
@@ -1161,6 +1318,8 @@
params.appPackageName, PackageManager.GET_SIGNATURES
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+ prepareDataLoader();
+
if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
validateApexInstallLocked();
} else {
@@ -1173,6 +1332,8 @@
}
} catch (PackageManagerException e) {
throw onSessionVerificationFailure(e);
+ } catch (StreamingException e) {
+ throw e;
} catch (Throwable e) {
// Convert all exceptions into package manager exceptions as only those are handled
// in the code above.
@@ -1208,6 +1369,8 @@
synchronized (mLock) {
try {
sealAndValidateLocked(childSessions);
+ } catch (StreamingException e) {
+ Slog.e(TAG, "Streaming failed", e);
} catch (PackageManagerException e) {
Slog.e(TAG, "Package not valid", e);
}
@@ -1277,6 +1440,8 @@
try {
sealAndValidateLocked(childSessions);
+ } catch (StreamingException e) {
+ throw new IllegalArgumentException("Streaming failed", e);
} catch (PackageManagerException e) {
throw new IllegalArgumentException("Package is not valid", e);
}
@@ -1517,9 +1682,10 @@
mInternalProgress = 0.5f;
computeProgressLocked(true);
- // Unpack native libraries
- // TODO(b/136132412): skip for incremental installation
- extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
+ // Unpack native libraries for non-incremental installation
+ if (isIncrementalInstallation()) {
+ extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
+ }
}
// We've reached point of no return; call into PMS to install the stage.
@@ -2223,6 +2389,171 @@
}
@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.");
+ }
+ // Use installer provided name for now; we always rename later
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new IllegalArgumentException("Invalid name: " + name);
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("addFile");
+
+ mFiles.add(FileInfo.added(name, lengthBytes, metadata));
+ }
+ }
+
+ @Override
+ public void removeFile(String name) {
+ if (!isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot add files to non-data loader installation session.");
+ }
+ if (TextUtils.isEmpty(params.appPackageName)) {
+ throw new IllegalStateException("Must specify package name to remove a split");
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("removeFile");
+
+ mFiles.add(FileInfo.removed(getRemoveMarkerName(name)));
+ }
+ }
+
+ static class Notificator {
+ private int mValue = 0;
+
+ void setValue(int value) {
+ synchronized (this) {
+ mValue = value;
+ this.notify();
+ }
+ }
+ int waitForValue() {
+ synchronized (this) {
+ while (mValue == 0) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ // Happens if someone interrupts your thread.
+ }
+ }
+ return mValue;
+ }
+ }
+ }
+
+ /**
+ * Makes sure files are present in staging location.
+ */
+ private void prepareDataLoader()
+ throws PackageManagerException, StreamingException {
+ if (!isStreamingInstallation()) {
+ return;
+ }
+
+ FileSystemConnector connector = new FileSystemConnector();
+
+ List<InstallationFile> addedFiles = mFiles.stream().filter(
+ file -> sAddedFilter.accept(new File(file.name))).map(
+ file -> new InstallationFile(
+ file.name, file.lengthBytes, file.metadata)).collect(
+ Collectors.toList());
+ List<String> removedFiles = mFiles.stream().filter(
+ file -> sRemovedFilter.accept(new File(file.name))).map(
+ file -> file.name.substring(
+ 0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
+ Collectors.toList());
+
+ DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
+ if (dataLoaderManager == null) {
+ throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+ "Failed to find data loader manager service");
+ }
+
+ // TODO(b/146080380): make this code async.
+ final Notificator created = new Notificator();
+ final Notificator started = new Notificator();
+ final Notificator imageReady = new Notificator();
+
+ IDataLoaderStatusListener listener = new IDataLoaderStatusListener.Stub() {
+ @Override
+ public void onStatusChanged(int dataLoaderId, int status) {
+ switch (status) {
+ case IDataLoaderStatusListener.DATA_LOADER_CREATED: {
+ created.setValue(1);
+ break;
+ }
+ case IDataLoaderStatusListener.DATA_LOADER_STARTED: {
+ started.setValue(1);
+ break;
+ }
+ case IDataLoaderStatusListener.DATA_LOADER_IMAGE_READY: {
+ imageReady.setValue(1);
+ break;
+ }
+ case IDataLoaderStatusListener.DATA_LOADER_IMAGE_NOT_READY: {
+ imageReady.setValue(2);
+ break;
+ }
+ }
+ }
+ };
+
+ final DataLoaderParams params = this.params.dataLoaderParams;
+
+ final FileSystemControlParcel control = new FileSystemControlParcel();
+ control.callback = connector;
+
+ Bundle dataLoaderParams = new Bundle();
+ dataLoaderParams.putParcelable("componentName", params.getComponentName());
+ dataLoaderParams.putParcelable("control", control);
+ dataLoaderParams.putParcelable("params", params.getData());
+
+ if (!dataLoaderManager.initializeDataLoader(sessionId, dataLoaderParams, listener)) {
+ throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+ "Failed to initialize data loader");
+ }
+ created.waitForValue();
+
+ IDataLoader dataLoader = dataLoaderManager.getDataLoader(sessionId);
+ if (dataLoader == null) {
+ throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+ "Failure to obtain data loader");
+ }
+
+ try {
+ dataLoader.start();
+ started.waitForValue();
+
+ dataLoader.prepareImage(addedFiles, removedFiles);
+ if (imageReady.waitForValue() == 2) {
+ throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+ "Failed to prepare image.");
+ }
+
+ dataLoader.destroy();
+ } catch (RemoteException e) {
+ throw new StreamingException(e);
+ }
+ }
+
+ @Override
public int[] getChildSessionIds() {
final int[] childSessionIds = mChildSessionIds.copyKeys();
if (childSessionIds != null) {
@@ -2294,20 +2625,6 @@
return mParentSessionId;
}
- @Override
- public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
- if (mIncrementalFileStorages == null) {
- throw new IllegalStateException(
- "Cannot add Incremental File to a non-Incremental session.");
- }
- try {
- mIncrementalFileStorages.addFile(new InstallationFile(name, size, metadata));
- } catch (IOException ex) {
- throw new IllegalStateException(
- "Failed to add and configure Incremental File: " + name, ex);
- }
- }
-
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
final IntentSender statusReceiver;
final String packageName;
@@ -2320,7 +2637,7 @@
}
if (statusReceiver != null) {
- // Execute observer.onPackageInstalled on different tread as we don't want callers
+ // Execute observer.onPackageInstalled on different thread as we don't want callers
// inside the system server have to worry about catching the callbacks while they are
// calling into the session
final SomeArgs args = SomeArgs.obtain();
@@ -2594,6 +2911,18 @@
writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
+ final boolean isDataLoader = params.dataLoaderParams != null;
+ writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader);
+ if (isDataLoader) {
+ writeIntAttribute(out, ATTR_DATALOADER_TYPE, params.dataLoaderParams.getType());
+ writeStringAttribute(out, ATTR_DATALOADER_PACKAGE_NAME,
+ params.dataLoaderParams.getComponentName().getPackageName());
+ writeStringAttribute(out, ATTR_DATALOADER_CLASS_NAME,
+ params.dataLoaderParams.getComponentName().getClassName());
+ writeStringAttribute(out, ATTR_DATALOADER_ARGUMENTS,
+ params.dataLoaderParams.getArguments());
+ }
+
writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
writeWhitelistedRestrictedPermissionsLocked(out,
params.whitelistedRestrictedPermissions);
@@ -2623,6 +2952,13 @@
writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
out.endTag(null, TAG_CHILD_SESSION);
}
+ for (FileInfo fileInfo : mFiles) {
+ out.startTag(null, TAG_SESSION_FILE);
+ writeStringAttribute(out, ATTR_NAME, fileInfo.name);
+ writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes);
+ writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata);
+ out.endTag(null, TAG_SESSION_FILE);
+ }
}
out.endTag(null, TAG_SESSION);
@@ -2696,6 +3032,16 @@
params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
+ if (readBooleanAttribute(in, ATTR_IS_DATALOADER)) {
+ params.dataLoaderParams = new DataLoaderParams(
+ readIntAttribute(in, ATTR_DATALOADER_TYPE),
+ new ComponentName(
+ readStringAttribute(in, ATTR_DATALOADER_PACKAGE_NAME),
+ readStringAttribute(in, ATTR_DATALOADER_CLASS_NAME)),
+ readStringAttribute(in, ATTR_DATALOADER_ARGUMENTS),
+ null);
+ }
+
final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
if (appIconFile.exists()) {
params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
@@ -2722,6 +3068,7 @@
List<String> grantedRuntimePermissions = new ArrayList<>();
List<String> whitelistedRestrictedPermissions = new ArrayList<>();
List<Integer> childSessionIds = new ArrayList<>();
+ List<FileInfo> files = new ArrayList<>();
int outerDepth = in.getDepth();
int type;
while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -2739,6 +3086,11 @@
if (TAG_CHILD_SESSION.equals(in.getName())) {
childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
}
+ if (TAG_SESSION_FILE.equals(in.getName())) {
+ files.add(new FileInfo(readStringAttribute(in, ATTR_NAME),
+ readLongAttribute(in, ATTR_LENGTH_BYTES, -1),
+ readByteArrayAttribute(in, ATTR_METADATA)));
+ }
}
if (grantedRuntimePermissions.size() > 0) {
@@ -2757,11 +3109,16 @@
childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
}
+ FileInfo[] fileInfosArray = null;
+ if (!files.isEmpty()) {
+ fileInfosArray = files.stream().toArray(FileInfo[]::new);
+ }
+
InstallSource installSource = InstallSource.create(installInitiatingPackageName,
installOriginatingPackageName, installerPackageName, false);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerUid,
- installSource, params, createdMillis, stageDir, stageCid,
+ installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
prepared, committed, sealed, childSessionIdsArray, parentSessionId,
isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 73ab82d..785ca7d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -27,6 +27,8 @@
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_HOME;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_VERSION_CODE;
import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -34,6 +36,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
@@ -96,6 +99,8 @@
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
+import static com.android.internal.util.ArrayUtils.emptyIfNull;
+import static com.android.internal.util.ArrayUtils.filter;
import static com.android.server.pm.ComponentResolver.RESOLVE_PRIORITY_SORTER;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
@@ -155,6 +160,7 @@
import android.content.pm.InstallSourceInfo;
import android.content.pm.InstantAppInfo;
import android.content.pm.InstantAppRequest;
+import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
@@ -248,7 +254,6 @@
import android.os.storage.VolumeRecord;
import android.permission.IPermissionManager;
import android.provider.DeviceConfig;
-import android.provider.MediaStore;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.security.KeyStore;
@@ -376,6 +381,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -383,6 +389,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -554,6 +561,11 @@
private static final boolean DEFAULT_VERIFY_ENABLE = true;
/**
+ * Whether integrity verification is enabled by default.
+ */
+ private static final boolean DEFAULT_INTEGRITY_VERIFY_ENABLE = true;
+
+ /**
* The default maximum time to wait for the verification agent to return in
* milliseconds.
*/
@@ -1439,6 +1451,8 @@
static final int ENABLE_ROLLBACK_TIMEOUT = 22;
static final int DEFERRED_NO_KILL_POST_DELETE = 23;
static final int DEFERRED_NO_KILL_INSTALL_OBSERVER = 24;
+ static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
+ static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
@@ -1603,6 +1617,10 @@
final boolean didRestore = (msg.arg2 != 0);
mRunningInstalls.delete(msg.arg1);
+ if (data != null && data.res.freezer != null) {
+ data.res.freezer.close();
+ }
+
if (data != null && data.mPostInstallRunnable != null) {
data.mPostInstallRunnable.run();
} else if (data != null) {
@@ -1698,13 +1716,13 @@
final int verificationId = msg.arg1;
final PackageVerificationState state = mPendingVerification.get(verificationId);
- if ((state != null) && !state.timeoutExtended()) {
+ if ((state != null) && !state.isVerificationComplete()
+ && !state.timeoutExtended()) {
final InstallParams params = state.getInstallParams();
final InstallArgs args = params.mArgs;
final Uri originUri = Uri.fromFile(args.origin.resolvedFile);
Slog.i(TAG, "Verification timed out for " + originUri);
- mPendingVerification.remove(verificationId);
final UserHandle user = args.getUser();
if (getDefaultVerificationResponse(user)
@@ -1719,11 +1737,54 @@
PackageManager.VERIFICATION_REJECT, user);
params.setReturnCode(
PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE);
+ state.setVerifierResponse(Binder.getCallingUid(),
+ PackageManager.VERIFICATION_REJECT);
+ }
+
+ if (state.areAllVerificationsComplete()) {
+ mPendingVerification.remove(verificationId);
}
Trace.asyncTraceEnd(
TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
+
params.handleVerificationFinished();
+
+ }
+ break;
+ }
+ case CHECK_PENDING_INTEGRITY_VERIFICATION: {
+ final int verificationId = msg.arg1;
+ final PackageVerificationState state = mPendingVerification.get(verificationId);
+
+ if (state != null && !state.isIntegrityVerificationComplete()) {
+ final InstallParams params = state.getInstallParams();
+ final InstallArgs args = params.mArgs;
+ final Uri originUri = Uri.fromFile(args.origin.resolvedFile);
+
+ Slog.i(TAG, "Integrity verification timed out for " + originUri);
+
+ state.setIntegrityVerificationResult(
+ getDefaultIntegrityVerificationResponse());
+
+ if (getDefaultIntegrityVerificationResponse()
+ == PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW) {
+ Slog.i(TAG, "Integrity check times out, continuing with " + originUri);
+ } else {
+ params.setReturnCode(
+ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE);
+ }
+
+ if (state.areAllVerificationsComplete()) {
+ mPendingVerification.remove(verificationId);
+ }
+
+ Trace.asyncTraceEnd(
+ TRACE_TAG_PACKAGE_MANAGER,
+ "integrity_verification",
+ verificationId);
+
+ params.handleIntegrityVerificationFinished();
}
break;
}
@@ -1732,7 +1793,9 @@
final PackageVerificationState state = mPendingVerification.get(verificationId);
if (state == null) {
- Slog.w(TAG, "Invalid verification token " + verificationId + " received");
+ Slog.w(TAG, "Verification with id " + verificationId
+ + " not found."
+ + " It may be invalid or overridden by integrity verification");
break;
}
@@ -1741,8 +1804,6 @@
state.setVerifierResponse(response.callerUid, response.code);
if (state.isVerificationComplete()) {
- mPendingVerification.remove(verificationId);
-
final InstallParams params = state.getInstallParams();
final InstallArgs args = params.mArgs;
final Uri originUri = Uri.fromFile(args.origin.resolvedFile);
@@ -1755,6 +1816,10 @@
PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE);
}
+ if (state.areAllVerificationsComplete()) {
+ mPendingVerification.remove(verificationId);
+ }
+
Trace.asyncTraceEnd(
TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
@@ -1763,6 +1828,43 @@
break;
}
+ case INTEGRITY_VERIFICATION_COMPLETE: {
+ final int verificationId = msg.arg1;
+
+ final PackageVerificationState state = mPendingVerification.get(verificationId);
+ if (state == null) {
+ Slog.w(TAG, "Integrity verification with id " + verificationId
+ + " not found. It may be invalid or overridden by verifier");
+ break;
+ }
+
+ final int response = (Integer) msg.obj;
+
+ final InstallParams params = state.getInstallParams();
+ final InstallArgs args = params.mArgs;
+ final Uri originUri = Uri.fromFile(args.origin.resolvedFile);
+
+ state.setIntegrityVerificationResult(response);
+
+ if (response == PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW) {
+ Slog.i(TAG, "Integrity check passed for " + originUri);
+ } else {
+ params.setReturnCode(
+ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE);
+ }
+
+ if (state.areAllVerificationsComplete()) {
+ mPendingVerification.remove(verificationId);
+ }
+
+ Trace.asyncTraceEnd(
+ TRACE_TAG_PACKAGE_MANAGER,
+ "integrity_verification",
+ verificationId);
+
+ params.handleIntegrityVerificationFinished();
+ break;
+ }
case START_INTENT_FILTER_VERIFICATIONS: {
IFVerificationParams params = (IFVerificationParams) msg.obj;
verifyIntentFiltersIfNeeded(params.userId, params.verifierUid, params.replacing,
@@ -3019,8 +3121,7 @@
mWellbeingPackage = getWellbeingPackageName();
mDocumenterPackage = getDocumenterPackageName();
- mConfiguratorPackage =
- mContext.getString(R.string.config_deviceConfiguratorPackageName);
+ mConfiguratorPackage = getDeviceConfiguratorPackageName();
mAppPredictionServicePackage = getAppPredictionServicePackageName();
mIncidentReportApproverPackage = getIncidentReportApproverPackageName();
mTelephonyPackages = getTelephonyPackageNames();
@@ -6135,10 +6236,12 @@
private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
Intent origIntent, String resolvedType, String callingPackage,
- Bundle verificationBundle, int userId) {
+ boolean isRequesterInstantApp, Bundle verificationBundle, int userId) {
final Message msg = mHandler.obtainMessage(INSTANT_APP_RESOLUTION_PHASE_TWO,
new InstantAppRequest(responseObj, origIntent, resolvedType,
- callingPackage, userId, verificationBundle, false /*resolveForStart*/));
+ callingPackage, isRequesterInstantApp, userId, verificationBundle,
+ false /*resolveForStart*/, responseObj.hostDigestPrefixSecure,
+ responseObj.token));
mHandler.sendMessage(msg);
}
@@ -6757,8 +6860,10 @@
}
}
if (addInstant) {
- result = maybeAddInstantAppInstaller(
- result, intent, resolvedType, flags, userId, resolveForStart);
+ String callingPkgName = getInstantAppPackageName(filterCallingUid);
+ boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
+ result = maybeAddInstantAppInstaller(result, intent, resolvedType, flags, userId,
+ resolveForStart, isRequesterInstantApp);
}
if (sortResult) {
Collections.sort(result, RESOLVE_PRIORITY_SORTER);
@@ -6769,7 +6874,8 @@
}
private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent,
- String resolvedType, int flags, int userId, boolean resolveForStart) {
+ String resolvedType, int flags, int userId, boolean resolveForStart,
+ boolean isRequesterInstantApp) {
// first, check to see if we've got an instant app already installed
final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
ResolveInfo localInstantApp = null;
@@ -6817,10 +6923,13 @@
if (localInstantApp == null) {
// we don't have an instant app locally, resolve externally
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral");
+ String token = UUID.randomUUID().toString();
+ InstantAppDigest digest = InstantAppResolver.parseDigest(intent);
final InstantAppRequest requestObject = new InstantAppRequest(
null /*responseObj*/, intent /*origIntent*/, resolvedType,
- null /*callingPackage*/, userId, null /*verificationBundle*/,
- resolveForStart);
+ null /*callingPackage*/, isRequesterInstantApp,
+ userId, null /*verificationBundle*/, resolveForStart,
+ digest.getDigestPrefixSecure(), token);
auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne(
mInstantAppResolverConnection, requestObject);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -13079,6 +13188,15 @@
}
/**
+ * Get the default integrity verification response code.
+ */
+ private int getDefaultIntegrityVerificationResponse() {
+ // We are not exposing this as a user-configurable setting because we don't want to provide
+ // an easy way to get around the integrity check.
+ return PackageManager.VERIFICATION_REJECT;
+ }
+
+ /**
* Check whether or not package verification has been enabled.
*
* @return true if verification should be performed
@@ -13121,6 +13239,15 @@
}
}
+ /**
+ * Check whether or not integrity verification has been enabled.
+ */
+ private boolean isIntegrityVerificationEnabled() {
+ // We are not exposing this as a user-configurable setting because we don't want to provide
+ // an easy way to get around the integrity check.
+ return DEFAULT_INTEGRITY_VERIFY_ENABLE;
+ }
+
@Override
public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
throws RemoteException {
@@ -13434,35 +13561,10 @@
// restore if appropriate, then pass responsibility back to the
// Package Manager to run the post-install observer callbacks
// and broadcasts.
- IBackupManager bm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- if (bm != null) {
- // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
- // in the BackupManager. USER_ALL is used in compatibility tests.
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- }
- if (DEBUG_INSTALL) {
- Log.v(TAG, "token " + token + " to BM for possible restore for user " + userId);
- }
- Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
- try {
- if (bm.isBackupServiceActive(userId)) {
- bm.restoreAtInstallForUser(
- userId, res.pkg.getAppInfoPackageName(), token);
- } else {
- doRestore = false;
- }
- } catch (RemoteException e) {
- // can't happen; the backup manager is local
- } catch (Exception e) {
- Slog.e(TAG, "Exception trying to enqueue restore", e);
- doRestore = false;
- }
- } else {
- Slog.e(TAG, "Backup Manager not found!");
- doRestore = false;
+ if (res.freezer != null) {
+ res.freezer.close();
}
+ doRestore = performBackupManagerRestore(userId, token, res);
}
// If this is an update to a package that might be potentially downgraded, then we
@@ -13471,42 +13573,7 @@
//
// TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
- IRollbackManager rm = IRollbackManager.Stub.asInterface(
- ServiceManager.getService(Context.ROLLBACK_SERVICE));
-
- final String packageName = res.pkg.getAppInfoPackageName();
- final String seInfo = res.pkg.getSeInfo();
- final int[] allUsers = mUserManager.getUserIds();
- final int[] installedUsers;
-
- final PackageSetting ps;
- int appId = -1;
- long ceDataInode = -1;
- synchronized (mSettings) {
- ps = mSettings.getPackageLPr(packageName);
- if (ps != null) {
- appId = ps.appId;
- ceDataInode = ps.getCeDataInode(userId);
- }
-
- // 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.
- installedUsers = ps.queryInstalledUsers(allUsers, true);
- }
-
- boolean doSnapshotOrRestore = data != null && data.args != null
- && ((data.args.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
- || (data.args.installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
-
- if (ps != null && doSnapshotOrRestore) {
- try {
- rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
- seInfo, token);
- } catch (RemoteException re) {
- Log.e(TAG, "Error snapshotting/restoring user data: " + re);
- }
- doRestore = true;
- }
+ doRestore = performRollbackManagerRestore(userId, token, res, data);
}
if (!doRestore) {
@@ -13522,6 +13589,91 @@
}
/**
+ * Perform Backup Manager restore for a given {@link PackageInstalledInfo}.
+ * Returns whether the restore successfully completed.
+ */
+ private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
+ IBackupManager bm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ if (bm != null) {
+ // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
+ // in the BackupManager. USER_ALL is used in compatibility tests.
+ if (userId == UserHandle.USER_ALL) {
+ userId = UserHandle.USER_SYSTEM;
+ }
+ if (DEBUG_INSTALL) {
+ Log.v(TAG, "token " + token + " to BM for possible restore for user " + userId);
+ }
+ Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
+ try {
+ if (bm.isUserReadyForBackup(userId)) {
+ bm.restoreAtInstallForUser(
+ userId, res.pkg.getAppInfoPackageName(), token);
+ } else {
+ Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
+ + "didn't take place.");
+ return false;
+ }
+ } catch (RemoteException e) {
+ // can't happen; the backup manager is local
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception trying to enqueue restore", e);
+ return false;
+ }
+ } else {
+ Slog.e(TAG, "Backup Manager not found!");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Perform Rollback Manager restore for a given {@link PackageInstalledInfo}.
+ * Returns whether the restore successfully completed.
+ */
+ private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res,
+ PostInstallData data) {
+ IRollbackManager rm = IRollbackManager.Stub.asInterface(
+ ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+ final String packageName = res.pkg.getAppInfoPackageName();
+ final String seInfo = res.pkg.getSeInfo();
+ final int[] allUsers = mUserManager.getUserIds();
+ final int[] installedUsers;
+
+ final PackageSetting ps;
+ int appId = -1;
+ long ceDataInode = -1;
+ synchronized (mSettings) {
+ ps = mSettings.getPackageLPr(packageName);
+ if (ps != null) {
+ appId = ps.appId;
+ ceDataInode = ps.getCeDataInode(userId);
+ }
+
+ // 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.
+ installedUsers = ps.queryInstalledUsers(allUsers, true);
+ }
+
+ boolean doSnapshotOrRestore = data != null && data.args != null
+ && ((data.args.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+ || (data.args.installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
+
+ if (ps != null && doSnapshotOrRestore) {
+ try {
+ rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
+ seInfo, token);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error snapshotting/restoring user data: " + re);
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Callback from PackageSettings whenever an app is first transitioned out of the
* 'stopped' state. Normally we just issue the broadcast, but we can't do that if
* the app was "launched" for a restoreAtInstall operation. Therefore we check
@@ -13806,6 +13958,7 @@
@NonNull final InstallSource installSource;
final String volumeUuid;
private boolean mVerificationCompleted;
+ private boolean mIntegrityVerificationCompleted;
private boolean mEnableRollbackCompleted;
private InstallArgs mArgs;
int mRet;
@@ -14067,155 +14220,30 @@
final InstallArgs args = createInstallArgs(this);
mVerificationCompleted = true;
+ mIntegrityVerificationCompleted = true;
mEnableRollbackCompleted = true;
mArgs = args;
if (ret == PackageManager.INSTALL_SUCCEEDED) {
- // TODO: http://b/22976637
- // Apps installed for "all" users use the device owner to verify the app
- UserHandle verifierUser = getUser();
- if (verifierUser == UserHandle.ALL) {
- verifierUser = UserHandle.SYSTEM;
- }
+ final int verificationId = mPendingVerificationToken++;
- /*
- * Determine if we have any installed package verifiers. If we
- * do, then we'll defer to them to verify the packages.
- */
- final int requiredUid = mRequiredVerifierPackage == null ? -1
- : getPackageUid(mRequiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
- verifierUser.getIdentifier());
- final int installerUid =
- verificationInfo == null ? -1 : verificationInfo.installerUid;
- if (!origin.existing && requiredUid != -1
- && isVerificationEnabled(
- verifierUser.getIdentifier(), installFlags, installerUid)) {
- final Intent verification = new Intent(
- Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
- verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- verification.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
- PACKAGE_MIME_TYPE);
- verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // Query all live verifiers based on current user state
- final List<ResolveInfo> receivers = queryIntentReceiversInternal(verification,
- PACKAGE_MIME_TYPE, 0, verifierUser.getIdentifier(),
- false /*allowDynamicSplits*/);
-
- if (DEBUG_VERIFY) {
- Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
- + verification.toString() + " with " + pkgLite.verifiers.length
- + " optional verifiers");
- }
-
- final int verificationId = mPendingVerificationToken++;
-
- verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
-
- verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
- installSource.installerPackageName);
-
- verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS,
- installFlags);
-
- verification.putExtra(PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME,
- pkgLite.packageName);
-
- verification.putExtra(PackageManager.EXTRA_VERIFICATION_VERSION_CODE,
- pkgLite.versionCode);
-
- verification.putExtra(PackageManager.EXTRA_VERIFICATION_LONG_VERSION_CODE,
- pkgLite.getLongVersionCode());
-
- if (verificationInfo != null) {
- if (verificationInfo.originatingUri != null) {
- verification.putExtra(Intent.EXTRA_ORIGINATING_URI,
- verificationInfo.originatingUri);
- }
- if (verificationInfo.referrer != null) {
- verification.putExtra(Intent.EXTRA_REFERRER,
- verificationInfo.referrer);
- }
- if (verificationInfo.originatingUid >= 0) {
- verification.putExtra(Intent.EXTRA_ORIGINATING_UID,
- verificationInfo.originatingUid);
- }
- if (verificationInfo.installerUid >= 0) {
- verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID,
- verificationInfo.installerUid);
- }
- }
-
- final PackageVerificationState verificationState = new PackageVerificationState(
- requiredUid, this);
-
+ // Perform package verification (unless we are simply moving the package).
+ if (!origin.existing) {
+ PackageVerificationState verificationState =
+ new PackageVerificationState(this);
mPendingVerification.append(verificationId, verificationState);
- final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
- receivers, verificationState);
+ sendIntegrityVerificationRequest(verificationId, pkgLite, verificationState);
+ ret = sendPackageVerificationRequest(
+ verificationId, pkgLite, verificationState);
- DeviceIdleInternal idleController =
- mInjector.getLocalDeviceIdleController();
- final long idleDuration = getVerificationTimeout();
-
- /*
- * If any sufficient verifiers were listed in the package
- * manifest, attempt to ask them.
- */
- if (sufficientVerifiers != null) {
- final int N = sufficientVerifiers.size();
- if (N == 0) {
- Slog.i(TAG, "Additional verifiers required, but none installed.");
- ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
- } else {
- for (int i = 0; i < N; i++) {
- final ComponentName verifierComponent = sufficientVerifiers.get(i);
- idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
- verifierComponent.getPackageName(), idleDuration,
- verifierUser.getIdentifier(), false, "package verifier");
-
- final Intent sufficientIntent = new Intent(verification);
- sufficientIntent.setComponent(verifierComponent);
- mContext.sendBroadcastAsUser(sufficientIntent, verifierUser);
- }
- }
- }
-
- final ComponentName requiredVerifierComponent = matchComponentForVerifier(
- mRequiredVerifierPackage, receivers);
- if (ret == PackageManager.INSTALL_SUCCEEDED
- && mRequiredVerifierPackage != null) {
- Trace.asyncTraceBegin(
- TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
- /*
- * Send the intent to the required verification agent,
- * but only start the verification timeout after the
- * target BroadcastReceivers have run.
- */
- verification.setComponent(requiredVerifierComponent);
- idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
- mRequiredVerifierPackage, idleDuration,
- verifierUser.getIdentifier(), false, "package verifier");
- mContext.sendOrderedBroadcastAsUser(verification, verifierUser,
- android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final Message msg = mHandler
- .obtainMessage(CHECK_PENDING_VERIFICATION);
- msg.arg1 = verificationId;
- mHandler.sendMessageDelayed(msg, getVerificationTimeout());
- }
- }, null, 0, null, null);
-
- /*
- * We don't want the copy to proceed until verification
- * succeeds.
- */
- mVerificationCompleted = false;
+ // If both verifications are skipped, we should remove the state.
+ if (verificationState.areAllVerificationsComplete()) {
+ mPendingVerification.remove(verificationId);
}
}
+
if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
// TODO(ruhler) b/112431924: Don't do this in case of 'move'?
final int enableRollbackToken = mPendingEnableRollbackToken++;
@@ -14271,6 +14299,228 @@
mRet = ret;
}
+ /**
+ * Send a request to check the integrity of the package.
+ */
+ void sendIntegrityVerificationRequest(
+ int verificationId,
+ PackageInfoLite pkgLite,
+ PackageVerificationState verificationState) {
+ if (!isIntegrityVerificationEnabled()) {
+ // Consider the integrity check as passed.
+ verificationState.setIntegrityVerificationResult(
+ PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+ return;
+ }
+
+ final Intent integrityVerification =
+ new Intent(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
+
+ integrityVerification.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
+ PACKAGE_MIME_TYPE);
+
+ final int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND;
+ integrityVerification.addFlags(flags);
+
+ integrityVerification.putExtra(EXTRA_VERIFICATION_ID, verificationId);
+ integrityVerification.putExtra(EXTRA_PACKAGE_NAME, pkgLite.packageName);
+ integrityVerification.putExtra(EXTRA_VERSION_CODE, pkgLite.versionCode);
+ populateInstallerExtras(integrityVerification);
+
+ // send to integrity component only.
+ integrityVerification.setPackage("android");
+
+ DeviceIdleInternal idleController =
+ mInjector.getLocalDeviceIdleController();
+ final long idleDuration = getVerificationTimeout();
+
+ idleController.addPowerSaveTempWhitelistAppDirect(Process.myUid(),
+ idleDuration,
+ false, "integrity component");
+ mContext.sendOrderedBroadcastAsUser(integrityVerification, UserHandle.SYSTEM,
+ /* receiverPermission= */ null,
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Message msg =
+ mHandler.obtainMessage(CHECK_PENDING_INTEGRITY_VERIFICATION);
+ msg.arg1 = verificationId;
+ // TODO: do we want to use the same timeout?
+ mHandler.sendMessageDelayed(msg, getVerificationTimeout());
+ }
+ }, /* scheduler= */ null,
+ /* initialCode= */ 0,
+ /* initialData= */ null,
+ /* initialExtras= */ null);
+
+ Trace.asyncTraceBegin(
+ TRACE_TAG_PACKAGE_MANAGER, "integrity_verification", verificationId);
+
+ // stop the copy until verification succeeds.
+ mIntegrityVerificationCompleted = false;
+ }
+
+ /**
+ * Send a request to verifier(s) to verify the package if necessary, and return
+ * {@link PackageManager#INSTALL_SUCCEEDED} if succeeded.
+ */
+ int sendPackageVerificationRequest(
+ int verificationId,
+ PackageInfoLite pkgLite,
+ PackageVerificationState verificationState) {
+ int ret = INSTALL_SUCCEEDED;
+
+ // TODO: http://b/22976637
+ // Apps installed for "all" users use the device owner to verify the app
+ UserHandle verifierUser = getUser();
+ if (verifierUser == UserHandle.ALL) {
+ verifierUser = UserHandle.SYSTEM;
+ }
+
+ /*
+ * Determine if we have any installed package verifiers. If we
+ * do, then we'll defer to them to verify the packages.
+ */
+ final int requiredUid = mRequiredVerifierPackage == null ? -1
+ : getPackageUid(mRequiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
+ verifierUser.getIdentifier());
+ verificationState.setRequiredVerifierUid(requiredUid);
+ final int installerUid =
+ verificationInfo == null ? -1 : verificationInfo.installerUid;
+ if (!origin.existing && requiredUid != -1
+ && isVerificationEnabled(
+ verifierUser.getIdentifier(), installFlags, installerUid)) {
+ final Intent verification = new Intent(
+ Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+ verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ verification.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)),
+ PACKAGE_MIME_TYPE);
+ verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ // Query all live verifiers based on current user state
+ final List<ResolveInfo> receivers = queryIntentReceiversInternal(verification,
+ PACKAGE_MIME_TYPE, 0, verifierUser.getIdentifier(),
+ false /*allowDynamicSplits*/);
+
+ if (DEBUG_VERIFY) {
+ Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
+ + verification.toString() + " with " + pkgLite.verifiers.length
+ + " optional verifiers");
+ }
+
+ verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
+
+ verification.putExtra(
+ PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS, installFlags);
+
+ verification.putExtra(
+ PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME, pkgLite.packageName);
+
+ verification.putExtra(
+ PackageManager.EXTRA_VERIFICATION_VERSION_CODE, pkgLite.versionCode);
+
+ verification.putExtra(
+ PackageManager.EXTRA_VERIFICATION_LONG_VERSION_CODE,
+ pkgLite.getLongVersionCode());
+
+ populateInstallerExtras(verification);
+
+ final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
+ receivers, verificationState);
+
+ DeviceIdleInternal idleController =
+ mInjector.getLocalDeviceIdleController();
+ final long idleDuration = getVerificationTimeout();
+
+ /*
+ * If any sufficient verifiers were listed in the package
+ * manifest, attempt to ask them.
+ */
+ if (sufficientVerifiers != null) {
+ final int n = sufficientVerifiers.size();
+ if (n == 0) {
+ Slog.i(TAG, "Additional verifiers required, but none installed.");
+ ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+ } else {
+ for (int i = 0; i < n; i++) {
+ final ComponentName verifierComponent = sufficientVerifiers.get(i);
+ idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
+ verifierComponent.getPackageName(), idleDuration,
+ verifierUser.getIdentifier(), false, "package verifier");
+
+ final Intent sufficientIntent = new Intent(verification);
+ sufficientIntent.setComponent(verifierComponent);
+ mContext.sendBroadcastAsUser(sufficientIntent, verifierUser);
+ }
+ }
+ }
+
+ final ComponentName requiredVerifierComponent = matchComponentForVerifier(
+ mRequiredVerifierPackage, receivers);
+ if (mRequiredVerifierPackage != null) {
+ /*
+ * Send the intent to the required verification agent,
+ * but only start the verification timeout after the
+ * target BroadcastReceivers have run.
+ */
+ verification.setComponent(requiredVerifierComponent);
+ idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
+ mRequiredVerifierPackage, idleDuration,
+ verifierUser.getIdentifier(), false, "package verifier");
+ mContext.sendOrderedBroadcastAsUser(verification, verifierUser,
+ android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Message msg = mHandler
+ .obtainMessage(CHECK_PENDING_VERIFICATION);
+ msg.arg1 = verificationId;
+ mHandler.sendMessageDelayed(msg, getVerificationTimeout());
+ }
+ }, null, 0, null, null);
+
+ Trace.asyncTraceBegin(
+ TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
+
+ /*
+ * We don't want the copy to proceed until verification
+ * succeeds.
+ */
+ mVerificationCompleted = false;
+ }
+ } else {
+ verificationState.setVerifierResponse(
+ requiredUid, PackageManager.VERIFICATION_ALLOW);
+ }
+ return ret;
+ }
+
+ void populateInstallerExtras(Intent intent) {
+ intent.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
+ installSource.initiatingPackageName);
+
+ if (verificationInfo != null) {
+ if (verificationInfo.originatingUri != null) {
+ intent.putExtra(Intent.EXTRA_ORIGINATING_URI,
+ verificationInfo.originatingUri);
+ }
+ if (verificationInfo.referrer != null) {
+ intent.putExtra(Intent.EXTRA_REFERRER,
+ verificationInfo.referrer);
+ }
+ if (verificationInfo.originatingUid >= 0) {
+ intent.putExtra(Intent.EXTRA_ORIGINATING_UID,
+ verificationInfo.originatingUid);
+ }
+ if (verificationInfo.installerUid >= 0) {
+ intent.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID,
+ verificationInfo.installerUid);
+ }
+ }
+ }
+
void setReturnCode(int ret) {
if (mRet == PackageManager.INSTALL_SUCCEEDED) {
// Only update mRet if it was previously INSTALL_SUCCEEDED to
@@ -14280,10 +14530,28 @@
}
void handleVerificationFinished() {
- mVerificationCompleted = true;
- handleReturnCode();
+ if (!mVerificationCompleted) {
+ mVerificationCompleted = true;
+ if (mIntegrityVerificationCompleted || mRet != INSTALL_SUCCEEDED) {
+ mIntegrityVerificationCompleted = true;
+ handleReturnCode();
+ }
+ // integrity verification still pending.
+ }
}
+ void handleIntegrityVerificationFinished() {
+ if (!mIntegrityVerificationCompleted) {
+ mIntegrityVerificationCompleted = true;
+ if (mVerificationCompleted || mRet != INSTALL_SUCCEEDED) {
+ mVerificationCompleted = true;
+ handleReturnCode();
+ }
+ // verifier still pending
+ }
+ }
+
+
void handleRollbackEnabled() {
// TODO(ruhler) b/112431924: Consider halting the install if we
// couldn't enable rollback.
@@ -14293,7 +14561,8 @@
@Override
void handleReturnCode() {
- if (mVerificationCompleted && mEnableRollbackCompleted) {
+ if (mVerificationCompleted
+ && mIntegrityVerificationCompleted && mEnableRollbackCompleted) {
if ((installFlags & PackageManager.INSTALL_DRY_RUN) != 0) {
String packageName = "";
try {
@@ -14575,7 +14844,8 @@
return false;
}
- if (!SELinux.restoreconRecursive(afterCodeFile)) {
+ //TODO(b/136132412): enable selinux restorecon for incremental directories
+ if (!onIncremental && !SELinux.restoreconRecursive(afterCodeFile)) {
Slog.w(TAG, "Failed to restorecon");
return false;
}
@@ -14802,6 +15072,7 @@
ArrayMap<String, PackageInstalledInfo> addedChildPackages;
// The set of packages consuming this shared library or null if no consumers exist.
ArrayList<AndroidPackage> libraryConsumers;
+ PackageFreezer freezer;
public void setError(int code, String msg) {
setReturnCode(code);
@@ -15675,16 +15946,14 @@
// TODO(patb): create a more descriptive reason than unknown in future release
// mark all non-failure installs as UNKNOWN so we do not treat them as success
for (InstallRequest request : requests) {
+ if (request.installResult.freezer != null) {
+ request.installResult.freezer.close();
+ }
if (request.installResult.returnCode == PackageManager.INSTALL_SUCCEEDED) {
request.installResult.returnCode = PackageManager.INSTALL_UNKNOWN;
}
}
}
- for (PrepareResult result : prepareResults.values()) {
- if (result.freezer != null) {
- result.freezer.close();
- }
- }
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
@@ -15792,15 +16061,13 @@
public final ParsedPackage packageToScan;
public final boolean clearCodeCache;
public final boolean system;
- public final PackageFreezer freezer;
public final PackageSetting originalPs;
public final PackageSetting disabledPs;
private PrepareResult(boolean replace, int scanFlags,
int parseFlags, AndroidPackage existingPackage,
ParsedPackage packageToScan, boolean clearCodeCache, boolean system,
- PackageFreezer freezer, PackageSetting originalPs,
- PackageSetting disabledPs) {
+ PackageSetting originalPs, PackageSetting disabledPs) {
this.replace = replace;
this.scanFlags = scanFlags;
this.parseFlags = parseFlags;
@@ -15808,7 +16075,6 @@
this.packageToScan = packageToScan;
this.clearCodeCache = clearCodeCache;
this.system = system;
- this.freezer = freezer;
this.originalPs = originalPs;
this.disabledPs = disabledPs;
}
@@ -16437,9 +16703,10 @@
shouldCloseFreezerBeforeReturn = false;
return new PrepareResult(replace, targetScanFlags, targetParseFlags,
- existingPackage, parsedPackage, replace /* clearCodeCache */, sysPkg, freezer,
+ existingPackage, parsedPackage, replace /* clearCodeCache */, sysPkg,
ps, disabledPs);
} finally {
+ res.freezer = freezer;
if (shouldCloseFreezerBeforeReturn) {
freezer.close();
}
@@ -16588,37 +16855,48 @@
+ " Activities needs verification ...");
int count = 0;
-
+ boolean handlesWebUris = false;
+ final boolean alreadyVerified;
synchronized (mLock) {
// If this is a new install and we see that we've already run verification for this
// package, we have nothing to do: it means the state was restored from backup.
- if (!replacing) {
- IntentFilterVerificationInfo ivi =
- mSettings.getIntentFilterVerificationLPr(packageName);
- if (ivi != null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Package " + packageName+ " already verified: status="
- + ivi.getStatusString());
- }
- return;
+ final IntentFilterVerificationInfo ivi =
+ mSettings.getIntentFilterVerificationLPr(packageName);
+ alreadyVerified = (ivi != null);
+ if (!replacing && alreadyVerified) {
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.i(TAG, "Package " + packageName + " already verified: status="
+ + ivi.getStatusString());
}
+ return;
}
- // If any filters need to be verified, then all need to be.
+ // If any filters need to be verified, then all need to be. In addition, we need to
+ // know whether an updating app has any web navigation intent filters, to re-
+ // examine handling policy even if not re-verifying.
boolean needToVerify = false;
for (ParsedActivity a : activities) {
for (ParsedActivityIntentInfo filter : a.intents) {
+ if (filter.handlesWebUris(true)) {
+ handlesWebUris = true;
+ }
if (filter.needsVerification() && needsNetworkVerificationLPr(filter)) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(TAG,
"Intent filter needs verification, so processing all filters");
}
needToVerify = true;
+ // It's safe to break out here because filter.needsVerification()
+ // can only be true if filter.handlesWebUris(true) returns true, so
+ // we've already noted that.
break;
}
}
}
+ // Note whether this app publishes any web navigation handling support at all,
+ // and whether there are any web-nav filters that fit the profile for running
+ // a verification pass now.
if (needToVerify) {
final int verificationId = mIntentFilterVerificationToken++;
for (ParsedActivity a : activities) {
@@ -16636,13 +16914,23 @@
}
if (count > 0) {
+ // count > 0 means that we're running a full verification pass
if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Starting " + count
+ " IntentFilter verification" + (count > 1 ? "s" : "")
+ " for userId:" + userId);
mIntentFilterVerifier.startVerifications(userId);
+ } else if (alreadyVerified && handlesWebUris) {
+ // App used autoVerify in the past, no longer does, but still handles web
+ // navigation starts.
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.d(TAG, "App changed web filters but no longer verifying - resetting policy");
+ }
+ synchronized (mLock) {
+ clearIntentFilterVerificationsLPw(packageName, userId);
+ }
} else {
if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "No filters or not all autoVerify for " + packageName);
+ Slog.d(TAG, "No web filters or no prior verify policy for " + packageName);
}
}
}
@@ -17921,14 +18209,6 @@
}
}
mPermissionManager.resetRuntimePermissions(pkg, nextUserId);
- // Also delete contributed media, when requested
- if ((flags & PackageManager.DELETE_CONTRIBUTED_MEDIA) != 0) {
- try {
- MediaStore.deleteContributedMedia(mContext, ps.name, UserHandle.of(nextUserId));
- } catch (IOException e) {
- Slog.w(TAG, "Failed to delete contributed media for " + ps.name, e);
- }
- }
}
if (outInfo != null) {
@@ -19160,13 +19440,14 @@
@Override
public String getSystemTextClassifierPackageName() {
- return mContext.getString(R.string.config_defaultTextClassifierPackage);
+ return ensureSystemPackageName(mContext.getString(
+ R.string.config_defaultTextClassifierPackage));
}
@Override
public String[] getSystemTextClassifierPackages() {
- return mContext.getResources().getStringArray(
- R.array.config_defaultTextClassifierPackages);
+ return ensureSystemPackageNames(mContext.getResources().getStringArray(
+ R.array.config_defaultTextClassifierPackages));
}
@Override
@@ -19176,7 +19457,7 @@
if (flattenedComponentName != null) {
ComponentName componentName = ComponentName.unflattenFromString(flattenedComponentName);
if (componentName != null && componentName.getPackageName() != null) {
- return componentName.getPackageName();
+ return ensureSystemPackageName(componentName.getPackageName());
}
}
return null;
@@ -19201,9 +19482,15 @@
}
}
+ @Nullable
+ private String getDeviceConfiguratorPackageName() {
+ return ensureSystemPackageName(mContext.getString(
+ R.string.config_deviceConfiguratorPackageName));
+ }
+
@Override
public String getWellbeingPackageName() {
- return mContext.getString(R.string.config_defaultWellbeingPackage);
+ return ensureSystemPackageName(mContext.getString(R.string.config_defaultWellbeingPackage));
}
@Override
@@ -19218,9 +19505,21 @@
if (appPredictionServiceComponentName == null) {
return null;
}
- return appPredictionServiceComponentName.getPackageName();
+ return ensureSystemPackageName(appPredictionServiceComponentName.getPackageName());
}
+ private @NonNull String[] dropNonSystemPackages(@NonNull String[] pkgNames) {
+ return emptyIfNull(filter(pkgNames, String[]::new, mIsSystemPackage), String.class);
+ }
+
+ private Predicate<String> mIsSystemPackage = (pkgName) -> {
+ if ("android".equals(pkgName)) {
+ return true;
+ }
+ AndroidPackage pkg = mPackages.get(pkgName);
+ return pkg != null && pkg.isSystem();
+ };
+
@Override
public String getSystemCaptionsServicePackageName() {
String flattenedSystemCaptionsServiceComponentName =
@@ -19235,7 +19534,7 @@
if (systemCaptionsServiceComponentName == null) {
return null;
}
- return systemCaptionsServiceComponentName.getPackageName();
+ return ensureSystemPackageName(systemCaptionsServiceComponentName.getPackageName());
}
@Override
@@ -19247,7 +19546,8 @@
}
public String getIncidentReportApproverPackageName() {
- return mContext.getString(R.string.config_incidentReportApproverPackage);
+ return ensureSystemPackageName(mContext.getString(
+ R.string.config_incidentReportApproverPackage));
}
@Override
@@ -19257,7 +19557,7 @@
if (!TextUtils.isEmpty(names)) {
telephonyPackageNames = names.trim().split(",");
}
- return telephonyPackageNames;
+ return ensureSystemPackageNames(telephonyPackageNames);
}
@Override
@@ -19274,7 +19574,32 @@
if (contentCaptureServiceComponentName == null) {
return null;
}
- return contentCaptureServiceComponentName.getPackageName();
+ return ensureSystemPackageName(contentCaptureServiceComponentName.getPackageName());
+ }
+
+ @Nullable
+ private String ensureSystemPackageName(@Nullable String packageName) {
+ if (packageName == null) {
+ return null;
+ }
+ if (getPackageInfo(packageName, MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE
+ | MATCH_DIRECT_BOOT_UNAWARE | MATCH_DISABLED_COMPONENTS,
+ UserHandle.getCallingUserId()) == null) {
+ return null;
+ }
+ return packageName;
+ }
+
+ @Nullable
+ private String[] ensureSystemPackageNames(@Nullable String[] packageNames) {
+ if (packageNames == null) {
+ return null;
+ }
+ final int packageNamesLength = packageNames.length;
+ for (int i = 0; i < packageNamesLength; i++) {
+ packageNames[i] = ensureSystemPackageName(packageNames[i]);
+ }
+ return ArrayUtils.filterNotNull(packageNames, String[]::new);
}
@Override
@@ -22473,7 +22798,11 @@
@Override
public @NonNull String[] getKnownPackageNames(int knownPackage, int userId) {
- switch (knownPackage) {
+ return dropNonSystemPackages(getKnownPackageNamesInternal(knownPackage, userId));
+ }
+
+ private String[] getKnownPackageNamesInternal(int knownPackage, int userId) {
+ switch(knownPackage) {
case PackageManagerInternal.PACKAGE_BROWSER:
return new String[]{mPermissionManager.getDefaultBrowser(userId)};
case PackageManagerInternal.PACKAGE_INSTALLER:
@@ -22500,6 +22829,8 @@
return filterOnlySystemPackages(mAppPredictionServicePackage);
case PackageManagerInternal.PACKAGE_TELEPHONY:
return filterOnlySystemPackages(mTelephonyPackages);
+ case PackageManagerInternal.PACKAGE_COMPANION:
+ return filterOnlySystemPackages("com.android.companiondevicemanager");
default:
return ArrayUtils.emptyArray(String.class);
}
@@ -22560,6 +22891,17 @@
}
@Override
+ public long getCeDataInode(String packageName, int userId) {
+ synchronized (mLock) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ return ps.getCeDataInode(userId);
+ }
+ return 0;
+ }
+ }
+
+ @Override
public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) {
synchronized (mLock) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
@@ -22684,6 +23026,12 @@
}
@Override
+ public ComponentName getSystemUiServiceComponent() {
+ return ComponentName.unflattenFromString(mContext.getResources().getString(
+ com.android.internal.R.string.config_systemUIServiceComponent));
+ }
+
+ @Override
public void setDeviceAndProfileOwnerPackages(
int deviceOwnerUserId, String deviceOwnerPackage,
SparseArray<String> profileOwnerPackages) {
@@ -22763,10 +23111,10 @@
@Override
public void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
Intent origIntent, String resolvedType, String callingPackage,
- Bundle verificationBundle, int userId) {
+ boolean isRequesterInstantApp, Bundle verificationBundle, int userId) {
PackageManagerService.this.requestInstantAppResolutionPhaseTwo(
- responseObj, origIntent, resolvedType, callingPackage, verificationBundle,
- userId);
+ responseObj, origIntent, resolvedType, callingPackage, isRequesterInstantApp,
+ verificationBundle, userId);
}
@Override
@@ -22979,16 +23327,10 @@
}
@Override
- public String getSharedUserIdForPackage(String packageName) {
+ @NonNull
+ public String[] getSharedUserPackagesForPackage(String packageName, int userId) {
synchronized (mLock) {
- return getSharedUserIdForPackageLocked(packageName);
- }
- }
-
- @Override
- public String[] getPackagesForSharedUserId(String sharedUserId, int userId) {
- synchronized (mLock) {
- return getPackagesForSharedUserIdLocked(sharedUserId, userId);
+ return getSharedUserPackagesForPackageLocked(packageName, userId);
}
}
@@ -23199,6 +23541,14 @@
mSettings.writeLPr();
}
}
+
+ @Override
+ public void setIntegrityVerificationResult(int verificationId, int verificationResult) {
+ final Message msg = mHandler.obtainMessage(INTEGRITY_VERIFICATION_COMPLETE);
+ msg.arg1 = verificationId;
+ msg.obj = verificationResult;
+ mHandler.sendMessage(msg);
+ }
}
@GuardedBy("mLock")
@@ -23213,35 +23563,25 @@
}
@GuardedBy("mLock")
- private String getSharedUserIdForPackageLocked(String packageName) {
- final PackageSetting ps = mSettings.mPackages.get(packageName);
- return (ps != null && ps.isSharedUser()) ? ps.sharedUser.name : null;
- }
-
- @GuardedBy("mLock")
- private String[] getPackagesForSharedUserIdLocked(String sharedUserId, int userId) {
- try {
- final SharedUserSetting sus = mSettings.getSharedUserLPw(
- sharedUserId, 0, 0, false);
- if (sus == null) {
- return EmptyArray.STRING;
- }
- String[] res = new String[sus.packages.size()];
- final Iterator<PackageSetting> it = sus.packages.iterator();
- int i = 0;
- while (it.hasNext()) {
- PackageSetting ps = it.next();
- if (ps.getInstalled(userId)) {
- res[i++] = ps.name;
- } else {
- res = ArrayUtils.removeElement(String.class, res, res[i]);
- }
- }
- return res;
- } catch (PackageManagerException e) {
- // Should not happen
+ @NonNull
+ private String[] getSharedUserPackagesForPackageLocked(String packageName, int userId) {
+ final PackageSetting packageSetting = mSettings.mPackages.get(packageName);
+ if (packageSetting == null || !packageSetting.isSharedUser()) {
+ return EmptyArray.STRING;
}
- return EmptyArray.STRING;
+
+ ArraySet<PackageSetting> packages = packageSetting.sharedUser.packages;
+ String[] res = new String[packages.size()];
+ final Iterator<PackageSetting> it = packages.iterator();
+ int i = 0;
+ while (it.hasNext()) {
+ PackageSetting ps = it.next();
+ if (ps.getInstalled(userId)) {
+ res[i++] = ps.name;
+ }
+ }
+ res = ArrayUtils.trimToSize(res, i);
+ return res != null ? res : EmptyArray.STRING;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fff404f..10e2780 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -34,6 +34,7 @@
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;
@@ -114,6 +115,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -135,6 +137,10 @@
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 =
@@ -175,6 +181,8 @@
return runQueryIntentReceivers();
case "install":
return runInstall();
+ case "install-streaming":
+ return runStreamingInstall();
case "install-abandon":
case "install-destroy":
return runInstallAbandon();
@@ -1152,9 +1160,23 @@
return 0;
}
- private int runInstall() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
+ 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);
+ }
+ return doRunInstall(params);
+ }
+
+ private int runInstall() throws RemoteException {
+ return doRunInstall(makeInstallParams());
+ }
+
+ private int doRunInstall(final InstallParams params) throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ final boolean streaming = params.sessionParams.dataLoaderParams != null;
ArrayList<String> inPaths = getRemainingArgs();
if (inPaths.isEmpty()) {
@@ -1181,17 +1203,30 @@
return 1;
}
- setParamsSize(params, inPaths);
+ if (!streaming) {
+ setParamsSize(params, inPaths);
+ }
+
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
boolean abandonSession = true;
try {
for (String inPath : inPaths) {
- String splitName = hasSplits ? (new File(inPath)).getName()
- : "base." + (isApex ? "apex" : "apk");
- if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
- false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
- return 1;
+ if (streaming) {
+ String name = new File(inPath).getName();
+ byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8);
+ if (doAddFile(sessionId, name, params.sessionParams.sizeBytes, metadata,
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ } else {
+ String splitName = hasSplits ? new File(inPath).getName()
+ : "base." + (isApex ? "apex" : "apk");
+
+ if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
}
}
if (doCommitSession(sessionId, false /*logSuccess*/)
@@ -2927,11 +2962,32 @@
return sessionId;
}
+ private int doAddFile(int sessionId, String name, long sizeBytes, byte[] metadata,
+ boolean logSuccess) throws RemoteException {
+ PackageInstaller.Session session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+ try {
+ session.addFile(name, sizeBytes, metadata);
+
+ if (logSuccess) {
+ getOutPrintWriter().println("Success");
+ }
+
+ return 0;
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
+ }
+
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
PackageInstaller.Session session = null;
try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+
final PrintWriter pw = getOutPrintWriter();
+
final ParcelFileDescriptor fd;
if (STDIN_PATH.equals(inPath)) {
fd = ParcelFileDescriptor.dup(getInFileDescriptor());
@@ -2953,8 +3009,6 @@
return 1;
}
- session = new PackageInstaller.Session(
- mInterface.getPackageInstaller().openSession(sessionId));
session.write(splitName, 0, sizeBytes, fd);
if (logSuccess) {
@@ -3000,7 +3054,6 @@
try {
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
-
for (String splitName : splitNames) {
session.removeSplit(splitName);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
new file mode 100644
index 0000000..1ee9ab8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -0,0 +1,92 @@
+/*
+ * 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.pm;
+
+import android.annotation.NonNull;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.InstallationFile;
+import android.os.ParcelFileDescriptor;
+import android.service.dataloader.DataLoaderService;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+
+/**
+ * Callback data loader for PackageManagerShellCommand installations.
+ */
+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 STDIN_PATH = "-";
+
+ @Override
+ public boolean onCreate(@NonNull DataLoaderParams dataLoaderParams,
+ @NonNull FileSystemConnector connector) {
+ mConnector = connector;
+ return true;
+ }
+ @Override
+ public boolean onPrepareImage(Collection<InstallationFile> addedFiles,
+ Collection<String> removedFiles) {
+ 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());
+ 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);
+ } finally {
+ IoUtils.closeQuietly(incomingFd);
+ }
+ }
+ }
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public DataLoaderService.DataLoader onCreateDataLoader() {
+ return new DataLoader();
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java
index c50bf59..ea7af90 100644
--- a/services/core/java/com/android/server/pm/PackageVerificationState.java
+++ b/services/core/java/com/android/server/pm/PackageVerificationState.java
@@ -22,18 +22,17 @@
import com.android.server.pm.PackageManagerService.InstallParams;
/**
- * Tracks the package verification state for a particular package. Each package
- * verification has a required verifier and zero or more sufficient verifiers.
- * Only one of the sufficient verifier list must return affirmative to allow the
- * package to be considered verified. If there are zero sufficient verifiers,
- * then package verification is considered complete.
+ * Tracks the package verification state for a particular package. Each package verification has a
+ * required verifier and zero or more sufficient verifiers. Only one of the sufficient verifier list
+ * must return affirmative to allow the package to be considered verified. If there are zero
+ * sufficient verifiers, then package verification is considered complete.
*/
class PackageVerificationState {
private final InstallParams mParams;
private final SparseBooleanArray mSufficientVerifierUids;
- private final int mRequiredVerifierUid;
+ private int mRequiredVerifierUid;
private boolean mSufficientVerificationComplete;
@@ -45,16 +44,13 @@
private boolean mExtendedTimeout;
+ private boolean mIntegrityVerificationComplete;
+
/**
- * Create a new package verification state where {@code requiredVerifierUid}
- * is the user ID for the package that must reply affirmative before things
- * can continue.
- *
- * @param requiredVerifierUid user ID of required package verifier
- * @param args
+ * Create a new package verification state where {@code requiredVerifierUid} is the user ID for
+ * the package that must reply affirmative before things can continue.
*/
- PackageVerificationState(int requiredVerifierUid, InstallParams params) {
- mRequiredVerifierUid = requiredVerifierUid;
+ PackageVerificationState(InstallParams params) {
mParams = params;
mSufficientVerifierUids = new SparseBooleanArray();
mExtendedTimeout = false;
@@ -64,6 +60,11 @@
return mParams;
}
+ /** Sets the user ID of the required package verifier. */
+ void setRequiredVerifierUid(int uid) {
+ mRequiredVerifierUid = uid;
+ }
+
/**
* Add a verifier which is added to our sufficient list.
*
@@ -74,8 +75,8 @@
}
/**
- * Should be called when a verification is received from an agent so the
- * state of the package verification can be tracked.
+ * Should be called when a verification is received from an agent so the state of the package
+ * verification can be tracked.
*
* @param uid user ID of the verifying agent
* @return {@code true} if the verifying agent actually exists in our list
@@ -114,9 +115,8 @@
}
/**
- * Returns whether verification is considered complete. This means that the
- * required verifier and at least one of the sufficient verifiers has
- * returned a positive verification.
+ * Returns whether verification is considered complete. This means that the required verifier
+ * and at least one of the sufficient verifiers has returned a positive verification.
*
* @return {@code true} when verification is considered complete
*/
@@ -133,8 +133,8 @@
}
/**
- * Returns whether installation should be allowed. This should only be
- * called after {@link #isVerificationComplete()} returns {@code true}.
+ * Returns whether installation should be allowed. This should only be called after {@link
+ * #isVerificationComplete()} returns {@code true}.
*
* @return {@code true} if installation should be allowed
*/
@@ -150,9 +150,7 @@
return true;
}
- /**
- * Extend the timeout for this Package to be verified.
- */
+ /** Extend the timeout for this Package to be verified. */
void extendTimeout() {
if (!mExtendedTimeout) {
mExtendedTimeout = true;
@@ -167,4 +165,16 @@
boolean timeoutExtended() {
return mExtendedTimeout;
}
+
+ void setIntegrityVerificationResult(int code) {
+ mIntegrityVerificationComplete = true;
+ }
+
+ boolean isIntegrityVerificationComplete() {
+ return mIntegrityVerificationComplete;
+ }
+
+ boolean areAllVerificationsComplete() {
+ return mIntegrityVerificationComplete && isVerificationComplete();
+ }
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6653011..9642a1a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1249,6 +1249,7 @@
return false;
}
ps.clearDomainVerificationStatusForUser(userId);
+ ps.setIntentFilterVerificationInfo(null);
return true;
}
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 7ea4e98..6c3eb31 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -21,10 +21,13 @@
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -36,6 +39,8 @@
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
import android.content.rollback.IRollbackManager;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -49,6 +54,7 @@
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
@@ -82,6 +88,9 @@
@GuardedBy("mStagedSessions")
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
+ @GuardedBy("mStagedSessions")
+ private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
+
StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
mPi = pi;
mApexManager = am;
@@ -166,18 +175,32 @@
private List<PackageInfo> submitSessionToApexService(
@NonNull PackageInstallerSession session) throws PackageManagerException {
- final IntArray childSessionsIds = new IntArray();
+ final IntArray childSessionIds = new IntArray();
if (session.isMultiPackage()) {
for (int id : session.getChildSessionIds()) {
if (isApexSession(mStagedSessions.get(id))) {
- childSessionsIds.add(id);
+ childSessionIds.add(id);
+ }
+ }
+ }
+ ApexSessionParams apexSessionParams = new ApexSessionParams();
+ apexSessionParams.sessionId = session.sessionId;
+ apexSessionParams.childSessionIds = childSessionIds.toArray();
+ if (session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
+ apexSessionParams.isRollback = true;
+ apexSessionParams.rollbackId = retrieveRollbackIdForCommitSession(session.sessionId);
+ } else {
+ synchronized (mStagedSessions) {
+ int rollbackId = mSessionRollbackIds.get(session.sessionId, -1);
+ if (rollbackId != -1) {
+ apexSessionParams.hasRollbackEnabled = true;
+ apexSessionParams.rollbackId = rollbackId;
}
}
}
// submitStagedSession will throw a PackageManagerException if apexd verification fails,
// which will be propagated to populate stagedSessionErrorMessage of this session.
- final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
- childSessionsIds.toArray());
+ final ApexInfoList apexInfoList = mApexManager.submitStagedSession(apexSessionParams);
final List<PackageInfo> result = new ArrayList<>();
for (ApexInfo apexInfo : apexInfoList.apexInfos) {
final PackageInfo packageInfo;
@@ -208,6 +231,19 @@
return result;
}
+ private int retrieveRollbackIdForCommitSession(int sessionId) throws PackageManagerException {
+ RollbackManager rm = mContext.getSystemService(RollbackManager.class);
+
+ List<RollbackInfo> rollbacks = rm.getRecentlyCommittedRollbacks();
+ for (RollbackInfo rollback : rollbacks) {
+ if (rollback.getCommittedSessionId() == sessionId) {
+ return rollback.getRollbackId();
+ }
+ }
+ throw new PackageManagerException(
+ "Could not find rollback id for commit session: " + sessionId);
+ }
+
private void checkRequiredVersionCode(final PackageInstallerSession session,
final PackageInfo activePackage) throws PackageManagerException {
if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
@@ -633,6 +669,7 @@
void abortSession(@NonNull PackageInstallerSession session) {
synchronized (mStagedSessions) {
mStagedSessions.remove(session.sessionId);
+ mSessionRollbackIds.delete(session.sessionId);
}
}
@@ -739,6 +776,17 @@
}
}
+ void systemReady() {
+ // Register the receiver of boot completed intent for staging manager.
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context ctx, Intent intent) {
+ mPreRebootVerificationHandler.readyToStart();
+ ctx.unregisterReceiver(this);
+ }
+ }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+ }
+
private static class LocalIntentReceiverAsync {
final Consumer<Intent> mConsumer;
@@ -789,6 +837,9 @@
}
private final class PreRebootVerificationHandler extends Handler {
+ // Hold session ids before handler gets ready to do the verification.
+ private IntArray mPendingSessionIds;
+ private boolean mIsReady;
PreRebootVerificationHandler(Looper looper) {
super(looper);
@@ -841,8 +892,26 @@
}
}
+ // Notify the handler that system is ready, and reschedule the pre-reboot verifications.
+ private synchronized void readyToStart() {
+ mIsReady = true;
+ if (mPendingSessionIds != null) {
+ for (int i = 0; i < mPendingSessionIds.size(); i++) {
+ startPreRebootVerification(mPendingSessionIds.get(i));
+ }
+ mPendingSessionIds = null;
+ }
+ }
+
// Method for starting the pre-reboot verification
- private void startPreRebootVerification(int sessionId) {
+ private synchronized void startPreRebootVerification(int sessionId) {
+ if (!mIsReady) {
+ if (mPendingSessionIds == null) {
+ mPendingSessionIds = new IntArray();
+ }
+ mPendingSessionIds.add(sessionId);
+ return;
+ }
obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
}
@@ -865,6 +934,28 @@
*/
private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
+
+ if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+ // If rollback is enabled for this session, we call through to the RollbackManager
+ // with the list of sessions it must enable rollback for. Note that
+ // notifyStagedSession is a synchronous operation.
+ final IRollbackManager rm = IRollbackManager.Stub.asInterface(
+ ServiceManager.getService(Context.ROLLBACK_SERVICE));
+ try {
+ // NOTE: To stay consistent with the non-staged install flow, we don't fail the
+ // entire install if rollbacks can't be enabled.
+ int rollbackId = rm.notifyStagedSession(session.sessionId);
+ if (rollbackId != -1) {
+ synchronized (mStagedSessions) {
+ mSessionRollbackIds.put(session.sessionId, rollbackId);
+ }
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Failed to notifyStagedSession for session: "
+ + session.sessionId, re);
+ }
+ }
+
notifyPreRebootVerification_Start_Complete(session.sessionId);
}
@@ -929,25 +1020,6 @@
* </ul></p>
*/
private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
- if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
- // If rollback is enabled for this session, we call through to the RollbackManager
- // with the list of sessions it must enable rollback for. Note that
- // notifyStagedSession is a synchronous operation.
- final IRollbackManager rm = IRollbackManager.Stub.asInterface(
- ServiceManager.getService(Context.ROLLBACK_SERVICE));
- try {
- // NOTE: To stay consistent with the non-staged install flow, we don't fail the
- // entire install if rollbacks can't be enabled.
- if (!rm.notifyStagedSession(session.sessionId)) {
- Slog.e(TAG, "Unable to enable rollback for session: "
- + session.sessionId);
- }
- } catch (RemoteException re) {
- Slog.e(TAG, "Failed to notifyStagedSession for session: "
- + session.sessionId, re);
- }
- }
-
// Proactively mark session as ready before calling apexd. Although this call order
// looks counter-intuitive, this is the easiest way to ensure that session won't end up
// in the inconsistent state:
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index e8798ff..59a5804 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -24,7 +24,20 @@
]
},
{
- "name": "PackageManagerShellCommandTest"
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
+ }
+ ]
+ },
+ {
+ "name": "GtsSecurityHostTestCases",
+ "options": [
+ {
+ "include-filter": "com.google.android.security.gts.PackageVerifierTest"
+ }
+ ]
}
],
"postsubmit": [
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ad4411c..5854e32 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -84,7 +84,6 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IntArray;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -351,6 +350,7 @@
* User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
* that should be applied to all users, including guests. Only non-empty restriction bundles are
* stored.
+ * The key is the user id of the user whom the restriction originated from.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyGlobalUserRestrictions = new SparseArray<>();
@@ -364,6 +364,7 @@
/**
* User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
* for each user. Only non-empty restriction bundles are stored.
+ * The key is the user id of the user whom the restriction originated from.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
@@ -579,15 +580,6 @@
applyUserRestrictionsLR(UserHandle.USER_SYSTEM);
}
- UserInfo currentGuestUser = findCurrentGuestUser();
- if (currentGuestUser != null && !hasUserRestriction(
- UserManager.DISALLOW_CONFIG_WIFI, currentGuestUser.id)) {
- // If a guest user currently exists, apply the DISALLOW_CONFIG_WIFI option
- // to it, in case this guest was created in a previous version where this
- // user restriction was not a default guest restriction.
- setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, currentGuestUser.id);
- }
-
mContext.registerReceiver(mDisableQuietModeCallback,
new IntentFilter(ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK),
null, mHandler);
@@ -1118,11 +1110,6 @@
info.flags ^= UserInfo.FLAG_ADMIN;
writeUserLP(getUserDataLU(info.id));
}
-
- // Remove non-admin restrictions.
- // Keep synchronized with createUserEvenWhenDisallowed.
- setUserRestriction(UserManager.DISALLOW_SMS, false, userId);
- setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, userId);
}
/**
@@ -1522,7 +1509,7 @@
public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
checkManageUsersPermission("update users");
if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) {
- Log.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
+ Slog.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
return;
}
mLocalService.setUserIcon(userId, bitmap);
@@ -1570,7 +1557,7 @@
return ParcelFileDescriptor.open(
new File(iconPath), ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
- Log.e(LOG_TAG, "Couldn't find icon file", e);
+ Slog.e(LOG_TAG, "Couldn't find icon file", e);
}
return null;
}
@@ -1602,10 +1589,12 @@
private void initDefaultGuestRestrictions() {
synchronized (mGuestRestrictions) {
if (mGuestRestrictions.isEmpty()) {
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, true);
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
- mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+ UserTypeDetails guestType = mUserTypes.get(UserManager.USER_TYPE_FULL_GUEST);
+ if (guestType == null) {
+ Slog.wtf(LOG_TAG, "Can't set default guest restrictions: type doesn't exist.");
+ return;
+ }
+ guestType.addDefaultRestrictionsTo(mGuestRestrictions);
}
}
}
@@ -1633,7 +1622,7 @@
/**
* See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
*/
- private void setDevicePolicyUserRestrictionsInner(@UserIdInt int userId,
+ private void setDevicePolicyUserRestrictionsInner(@UserIdInt int originatingUserId,
@Nullable Bundle restrictions,
@UserManagerInternal.OwnerType int restrictionOwnerType) {
final Bundle global = new Bundle();
@@ -1647,16 +1636,16 @@
synchronized (mRestrictionsLock) {
// Update global and local restrictions if they were changed.
globalChanged = updateRestrictionsIfNeededLR(
- userId, global, mDevicePolicyGlobalUserRestrictions);
+ originatingUserId, global, mDevicePolicyGlobalUserRestrictions);
localChanged = updateRestrictionsIfNeededLR(
- userId, local, mDevicePolicyLocalUserRestrictions);
+ originatingUserId, local, mDevicePolicyLocalUserRestrictions);
if (restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER) {
// Remember the global restriction owner userId to be able to make a distinction
// in getUserRestrictionSource on who set local policies.
- mDeviceOwnerUserId = userId;
+ mDeviceOwnerUserId = originatingUserId;
} else {
- if (mDeviceOwnerUserId == userId) {
+ if (mDeviceOwnerUserId == originatingUserId) {
// When profile owner sets restrictions it passes null global bundle and we
// reset global restriction owner userId.
// This means this user used to have DO, but now the DO is gone and the user
@@ -1666,15 +1655,16 @@
}
}
if (DBG) {
- Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: userId=" + userId
- + " global=" + global + (globalChanged ? " (changed)" : "")
- + " local=" + local + (localChanged ? " (changed)" : "")
+ Slog.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
+ + " originatingUserId=" + originatingUserId
+ + " global=" + global + (globalChanged ? " (changed)" : "")
+ + " local=" + local + (localChanged ? " (changed)" : "")
);
}
// Don't call them within the mRestrictionsLock.
synchronized (mPackagesLock) {
if (localChanged || globalChanged) {
- writeUserLP(getUserDataNoChecks(userId));
+ writeUserLP(getUserDataNoChecks(originatingUserId));
}
}
@@ -1682,7 +1672,7 @@
if (globalChanged) {
applyUserRestrictionsForAllUsersLR();
} else if (localChanged) {
- applyUserRestrictionsLR(userId);
+ applyUserRestrictionsLR(originatingUserId);
}
}
}
@@ -1727,7 +1717,7 @@
@GuardedBy("mRestrictionsLock")
private void invalidateEffectiveUserRestrictionsLR(@UserIdInt int userId) {
if (DBG) {
- Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
+ Slog.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
}
mCachedEffectiveUserRestrictions.remove(userId);
}
@@ -1949,7 +1939,7 @@
try {
mAppOpsService.setUserRestrictions(effective, mUserRestriconToken, userId);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+ Slog.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
}
}
});
@@ -2021,7 +2011,7 @@
try {
runningUsers = ActivityManager.getService().getRunningUserIds();
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to access ActivityManagerService");
+ Slog.w(LOG_TAG, "Unable to access ActivityManagerService");
return;
}
// Then re-calculate the effective restrictions and apply, only for running users.
@@ -2494,6 +2484,12 @@
mDevicePolicyLocalUserRestrictions, mDevicePolicyGlobalUserRestrictions
);
}
+ // DISALLOW_CONFIG_WIFI was made a default guest restriction some time during version 6.
+ final UserInfo currentGuestUser = findCurrentGuestUser();
+ if (currentGuestUser != null && !hasUserRestriction(
+ UserManager.DISALLOW_CONFIG_WIFI, currentGuestUser.id)) {
+ setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, currentGuestUser.id);
+ }
userVersion = 7;
}
@@ -2599,7 +2595,7 @@
}
}
} catch (Resources.NotFoundException e) {
- Log.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
+ Slog.e(LOG_TAG, "Couldn't find resource: config_defaultFirstUserRestrictions", e);
}
if (!restrictions.isEmpty()) {
@@ -3059,7 +3055,7 @@
? UserManager.DISALLOW_ADD_MANAGED_PROFILE
: UserManager.DISALLOW_ADD_USER;
if (hasUserRestriction(restriction, UserHandle.getCallingUserId())) {
- Log.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
+ Slog.w(LOG_TAG, "Cannot add user. " + restriction + " is enabled.");
return null;
}
return createUserInternalUnchecked(name, userType, flags, parentId,
@@ -3118,7 +3114,7 @@
DeviceStorageMonitorInternal dsm = LocalServices
.getService(DeviceStorageMonitorInternal.class);
if (dsm.isMemoryLow()) {
- Log.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
+ Slog.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
return null;
}
@@ -3141,36 +3137,36 @@
if (parent == null) return null;
}
if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) {
- Log.e(LOG_TAG, "Cannot add more users of type " + userType
+ Slog.e(LOG_TAG, "Cannot add more users of type " + userType
+ ". Maximum number of that type already exists.");
return null;
}
// TODO(b/142482943): Perhaps let the following code apply to restricted users too.
if (isProfile && !canAddMoreProfilesToUser(userType, parentId, false)) {
- Log.e(LOG_TAG, "Cannot add more profiles of type " + userType
+ Slog.e(LOG_TAG, "Cannot add more profiles of type " + userType
+ " for user " + parentId);
return null;
}
if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
// If we're not adding a guest/demo user or a profile and the 'user limit' has
// been reached, cannot add a user.
- Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
+ Slog.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
return null;
}
// In legacy mode, restricted profile's parent can only be the owner user
if (isRestricted && !UserManager.isSplitSystemUser()
&& (parentId != UserHandle.USER_SYSTEM)) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+ Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
return null;
}
if (isRestricted && UserManager.isSplitSystemUser()) {
if (parent == null) {
- Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ Slog.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ "specified");
return null;
}
if (!parent.info.canHaveProfile()) {
- Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ Slog.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ "created for the specified parent user id " + parentId);
return null;
}
@@ -3245,12 +3241,15 @@
writeUserLP(userData);
}
updateUserIds();
+
Bundle restrictions = new Bundle();
- // TODO(b/142482943): Generalize this using UserTypeDetails default restrictions.
if (isGuest) {
+ // Guest default restrictions can be modified via setDefaultGuestRestrictions.
synchronized (mGuestRestrictions) {
restrictions.putAll(mGuestRestrictions);
}
+ } else {
+ userTypeDetails.addDefaultRestrictionsTo(restrictions);
}
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.append(userId, restrictions);
@@ -3318,7 +3317,7 @@
+ Integer.toHexString(preCreatedUser.flags) + ").");
return null;
}
- Log.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type "
+ Slog.i(LOG_TAG, "Reusing pre-created user " + preCreatedUser.id + " of type "
+ userType + " and bestowing on it flags " + UserInfo.flagsToString(flags));
preCreatedUser.name = name;
preCreatedUser.flags = newFlags;
@@ -3483,7 +3482,7 @@
checkManageUsersPermission("Only the system can remove users");
if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
UserManager.DISALLOW_REMOVE_USER, false)) {
- Log.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
+ Slog.w(LOG_TAG, "Cannot remove user. DISALLOW_REMOVE_USER is enabled.");
return false;
}
@@ -3535,7 +3534,7 @@
String restriction = isManagedProfile
? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE : UserManager.DISALLOW_REMOVE_USER;
if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
- Log.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
+ Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
return false;
}
return removeUserUnchecked(userId);
@@ -3553,25 +3552,25 @@
final UserData userData;
int currentUser = ActivityManager.getCurrentUser();
if (currentUser == userId) {
- Log.w(LOG_TAG, "Current user cannot be removed.");
+ Slog.w(LOG_TAG, "Current user cannot be removed.");
return false;
}
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
userData = mUsers.get(userId);
if (userId == UserHandle.USER_SYSTEM) {
- Log.e(LOG_TAG, "System user cannot be removed.");
+ Slog.e(LOG_TAG, "System user cannot be removed.");
return false;
}
if (userData == null) {
- Log.e(LOG_TAG, String.format(
+ Slog.e(LOG_TAG, String.format(
"Cannot remove user %d, invalid user id provided.", userId));
return false;
}
if (mRemovingUserIds.get(userId)) {
- Log.e(LOG_TAG, String.format(
+ Slog.e(LOG_TAG, String.format(
"User %d is already scheduled for removal.", userId));
return false;
}
@@ -3591,7 +3590,7 @@
try {
mAppOpsService.removeUser(userId);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
+ Slog.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
}
// TODO(b/142482943): Send some sort of broadcast for profiles even if non-managed?
@@ -3616,7 +3615,7 @@
}
});
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Failed to stop user during removal.", e);
+ Slog.w(LOG_TAG, "Failed to stop user during removal.", e);
return false;
}
return res == ActivityManager.USER_OP_SUCCESS;
@@ -3828,7 +3827,7 @@
readEntry(restrictions, values, parser);
}
} catch (IOException|XmlPullParserException e) {
- Log.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+ Slog.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
} finally {
IoUtils.closeQuietly(fis);
}
@@ -3978,6 +3977,10 @@
@Override
public boolean isUserNameSet(@UserIdInt int userId) {
+ if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) {
+ throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED "
+ + "permissions to: get whether user name is set");
+ }
synchronized (mUsersLock) {
final UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.name != null;
@@ -4510,9 +4513,10 @@
private class LocalService extends UserManagerInternal {
@Override
- public void setDevicePolicyUserRestrictions(@UserIdInt int userId,
- @Nullable Bundle restrictions, @OwnerType int restrictionOwnerType) {
- UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId,
+ public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId,
+ @Nullable Bundle restrictions,
+ @OwnerType int restrictionOwnerType) {
+ UserManagerService.this.setDevicePolicyUserRestrictionsInner(originatingUserId,
restrictions, restrictionOwnerType);
}
@@ -4666,14 +4670,8 @@
@Override
public UserInfo createUserEvenWhenDisallowed(String name, @NonNull String userType,
@UserInfoFlag int flags, String[] disallowedPackages) {
- UserInfo user = createUserInternalUnchecked(name, userType, flags,
+ return createUserInternalUnchecked(name, userType, flags,
UserHandle.USER_NULL, /* preCreated= */ false, disallowedPackages);
- // Keep this in sync with UserManager.createUser
- if (user != null && !user.isAdmin() && !user.isDemo()) {
- setUserRestriction(UserManager.DISALLOW_SMS, true, user.id);
- setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, true, user.id);
- }
- return user;
}
@Override
@@ -4867,8 +4865,8 @@
}
private static void debug(String message) {
- Log.d(LOG_TAG, message +
- (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
+ Slog.d(LOG_TAG, message
+ + (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
}
/** @see #getMaxUsersOfTypePerParent(UserTypeDetails) */
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 0beff7a..90bd947 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -208,13 +208,6 @@
);
/**
- * User restrictions that default to {@code true} for device owners.
- */
- private static final Set<String> DEFAULT_ENABLED_FOR_DEVICE_OWNERS = Sets.newArraySet(
- UserManager.DISALLOW_ADD_MANAGED_PROFILE
- );
-
- /**
* User restrictions that default to {@code true} for managed profile owners.
*
* NB: {@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES} is also set by default but it is
@@ -411,11 +404,10 @@
}
/**
- * Returns the user restrictions that default to {@code true} for device owners.
- * These user restrictions are local, though. ie only for the device owner's user id.
+ * @return true if a restriction is settable by profile owner of an organization owned device.
*/
- public static @NonNull Set<String> getDefaultEnabledForDeviceOwner() {
- return DEFAULT_ENABLED_FOR_DEVICE_OWNERS;
+ public static boolean canProfileOwnerOfOrganizationOwnedDeviceChange(String restriction) {
+ return PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS.contains(restriction);
}
/**
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index c36b993..77bb48e 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -67,10 +67,11 @@
* then:
* <ul>
* <li>If {@link #isImplicitWhitelistMode()}, the package is implicitly treated as whitelisted
- * for all users</li>
- * <li>Otherwise, the package is implicitly treated as blacklisted for all non-SYSTEM users</li>
- * <li>Either way, for {@link UserHandle#USER_SYSTEM}, the package will be implicitly
- * whitelisted so that it can be used for local development purposes.</li>
+ * for <b>all</b> users</li>
+ * <li>Otherwise, if {@link #isImplicitWhitelistSystemMode()}, the package is implicitly treated
+ * as whitelisted for the <b>{@link UserHandle#USER_SYSTEM}</b> user (not other users),
+ * which is useful for local development purposes</li>
+ * <li>Otherwise, the package is implicitly treated as blacklisted for all users</li>
* </ul>
*
* <p><b>NOTE:</b> the {@code SystemConfig} state is only updated on first boot or after a system
@@ -86,22 +87,24 @@
* System Property whether to only install system packages on a user if they're whitelisted for
* that user type. These are flags and can be freely combined.
* <ul>
- * <li> 0 (0b0000) - disable whitelist (install all system packages; no logging)</li>
- * <li> 1 (0b0001) - enforce (only install system packages if they are whitelisted)</li>
- * <li> 2 (0b0010) - log (log when a non-whitelisted package is run)</li>
- * <li> 4 (0b0100) - implicitly whitelist any package not mentioned in the whitelist</li>
- * <li> 8 (0b1000) - ignore OTAs (don't install system packages during OTAs)</li>
- * <li>-1 - use device default (as defined in res/res/values/config.xml)</li>
+ * <li> 0 - disable whitelist (install all system packages; no logging)</li>
+ * <li> 1 - enforce (only install system packages if they are whitelisted)</li>
+ * <li> 2 - log (log when a non-whitelisted package is run)</li>
+ * <li> 4 - for all users: implicitly whitelist any package not mentioned in the whitelist</li>
+ * <li> 8 - for SYSTEM: implicitly whitelist any package not mentioned in the whitelist</li>
+ * <li> 16 - ignore OTAs (don't install system packages during OTAs)</li>
+ * <li>-1 - use device default (as defined in res/res/values/config.xml)</li>
* </ul>
* Note: This list must be kept current with config_userTypePackageWhitelistMode in
* frameworks/base/core/res/res/values/config.xml
*/
static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode";
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b0001;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0b0010;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b0100;
- static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0b1000;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0x00;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0x01;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0x02;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0x04;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM = 0x08;
+ static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA = 0x10;
static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
@IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = {
@@ -281,6 +284,14 @@
return isImplicitWhitelistMode(getWhitelistMode());
}
+ /**
+ * Whether to treat all packages that are not mentioned at all in the whitelist to be implicitly
+ * whitelisted for the SYSTEM user.
+ */
+ boolean isImplicitWhitelistSystemMode() {
+ return isImplicitWhitelistSystemMode(getWhitelistMode());
+ }
+
/** See {@link #isEnforceMode()}. */
private static boolean isEnforceMode(int whitelistMode) {
return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0;
@@ -301,6 +312,11 @@
return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST) != 0;
}
+ /** See {@link #isImplicitWhitelistSystemMode()}. */
+ private static boolean isImplicitWhitelistSystemMode(int whitelistMode) {
+ return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM) != 0;
+ }
+
/** Gets the PackageWhitelistMode for use of {@link #mWhitelistedPackagesForUserTypes}. */
private @PackageWhitelistMode int getWhitelistMode() {
final int runtimeMode = SystemProperties.getInt(
@@ -332,8 +348,8 @@
if (!isEnforceMode(mode)) {
return null;
}
- final boolean isSystemUser = mUm.isUserTypeSubtypeOfSystem(userType);
- final boolean isImplicitWhitelistMode = isImplicitWhitelistMode(mode);
+ final boolean implicitlyWhitelist = isImplicitWhitelistMode(mode)
+ || (isImplicitWhitelistSystemMode(mode) && mUm.isUserTypeSubtypeOfSystem(userType));
final Set<String> whitelistedPackages = getWhitelistedPackagesForUserType(userType);
final Set<String> installPackages = new ArraySet<>();
@@ -343,7 +359,7 @@
return;
}
if (shouldInstallPackage(pkg, mWhitelistedPackagesForUserTypes,
- whitelistedPackages, isImplicitWhitelistMode, isSystemUser)) {
+ whitelistedPackages, implicitlyWhitelist)) {
// Although the whitelist uses manifest names, this function returns packageNames.
installPackages.add(pkg.getPackageName());
}
@@ -360,31 +376,18 @@
* installed. This is only used for overriding the userWhitelist in
* certain situations (based on its keyset).
* @param userWhitelist set of package manifest names that should be installed on this
- * particular user. This must be consistent with userTypeWhitelist, but is
- * passed in separately to avoid repeatedly calculating it from
+ * <b>particular</b> user. This must be consistent with userTypeWhitelist,
+ * but is passed in separately to avoid repeatedly calculating it from
* userTypeWhitelist.
- * @param isImplicitWhitelistMode whether non-mentioned packages are implicitly whitelisted.
- * @param isSystemUser whether the user is USER_SYSTEM (which gets special treatment).
+ * @param implicitlyWhitelist whether non-mentioned packages are implicitly whitelisted.
*/
@VisibleForTesting
static boolean shouldInstallPackage(AndroidPackage sysPkg,
@NonNull ArrayMap<String, Long> userTypeWhitelist,
- @NonNull Set<String> userWhitelist, boolean isImplicitWhitelistMode,
- boolean isSystemUser) {
-
+ @NonNull Set<String> userWhitelist, boolean implicitlyWhitelist) {
final String pkgName = sysPkg.getManifestPackageName();
- boolean install = (isImplicitWhitelistMode && !userTypeWhitelist.containsKey(pkgName))
+ return (implicitlyWhitelist && !userTypeWhitelist.containsKey(pkgName))
|| userWhitelist.contains(pkgName);
-
- // For the purposes of local development, any package that isn't even mentioned in the
- // whitelist at all is implicitly treated as whitelisted for the SYSTEM user.
- if (!install && isSystemUser && !userTypeWhitelist.containsKey(pkgName)) {
- install = true;
- Slog.e(TAG, "System package " + pkgName + " is not mentioned "
- + "in SystemConfig's 'install-in-user-type' but we are "
- + "implicitly treating it as whitelisted for the SYSTEM user.");
- }
- return install;
}
/**
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 1631df3..826cd3f7 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -19,18 +19,17 @@
import android.annotation.ColorRes;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.StringRes;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
import android.content.res.Resources;
+import android.os.Bundle;
import android.os.UserManager;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
/**
* Contains the details about a multiuser "user type", such as a
@@ -75,8 +74,13 @@
/** The {@link UserInfo.UserInfoFlag}s that all users of this type will automatically have. */
private final @UserInfoFlag int mDefaultUserInfoPropertyFlags;
- // TODO(b/142482943): Hook these up to something and set them for each type.
- private final List<String> mDefaultRestrictions;
+ /**
+ * List of User Restrictions to apply by default to newly created users of this type.
+ * <p>Does not apply to SYSTEM users (since they are not formally created); for them use
+ * {@link com.android.internal.R.array#config_defaultFirstUserRestrictions} instead.
+ * The Bundle is of the form used by {@link UserRestrictionsUtils}.
+ */
+ private final @Nullable Bundle mDefaultRestrictions;
// Fields for profiles only, controlling the nature of their badges.
@@ -99,7 +103,7 @@
*
* <p>Must be set if mIconBadge is set.
*/
- private final int[] mBadgeLabels;
+ private final @Nullable int[] mBadgeLabels;
/**
* Resource ID ({@link ColorRes}) of the colors badge put on icons.
@@ -110,22 +114,21 @@
*
* <p>Must be set if mIconBadge is set.
*/
- private final int[] mBadgeColors;
+ private final @Nullable int[] mBadgeColors;
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
int iconBadge, int badgePlain, int badgeNoBackground,
- int[] badgeLabels, int[] badgeColors,
- ArrayList<String> defaultRestrictions) {
+ @Nullable int[] badgeLabels, @Nullable int[] badgeColors,
+ @Nullable Bundle defaultRestrictions) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
this.mMaxAllowedPerParent = maxAllowedPerParent;
this.mBaseType = baseType;
this.mDefaultUserInfoPropertyFlags = defaultUserInfoPropertyFlags;
- this.mDefaultRestrictions =
- Collections.unmodifiableList(new ArrayList<>(defaultRestrictions));
+ this.mDefaultRestrictions = defaultRestrictions;
this.mIconBadge = iconBadge;
this.mBadgePlain = badgePlain;
@@ -180,7 +183,7 @@
return mIconBadge != Resources.ID_NULL;
}
- /** Resource ID of the badge put on icons. */
+ /** Resource ID of the badge to put on icons. */
public @DrawableRes int getIconBadge() {
return mIconBadge;
}
@@ -231,9 +234,14 @@
return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
}
- // TODO(b/142482943): Hook this up and don't return the original.
- public List<String> getDefaultRestrictions() {
- return mDefaultRestrictions;
+ /** Returns a Bundle representing the default user restrictions. */
+ @NonNull Bundle getDefaultRestrictions() {
+ return UserRestrictionsUtils.clone(mDefaultRestrictions);
+ }
+
+ /** Adds the default user restrictions to the given bundle of restrictions. */
+ public void addDefaultRestrictionsTo(@NonNull Bundle currentRestrictions) {
+ UserRestrictionsUtils.merge(currentRestrictions, mDefaultRestrictions);
}
/** Dumps details of the UserTypeDetails. Do not parse this. */
@@ -247,7 +255,8 @@
pw.print(prefix); pw.print("mDefaultUserInfoFlags: ");
pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags));
pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel);
- pw.print(prefix); pw.print("mDefaultRestrictions: "); pw.println(mDefaultRestrictions);
+ pw.print(prefix); pw.println("mDefaultRestrictions: ");
+ UserRestrictionsUtils.dumpRestrictions(pw, prefix + " ", mDefaultRestrictions);
pw.print(prefix); pw.print("mIconBadge: "); pw.println(mIconBadge);
pw.print(prefix); pw.print("mBadgePlain: "); pw.println(mBadgePlain);
pw.print(prefix); pw.print("mBadgeNoBackground: "); pw.println(mBadgeNoBackground);
@@ -265,14 +274,14 @@
private int mMaxAllowed = UNLIMITED_NUMBER_OF_USERS;
private int mMaxAllowedPerParent = UNLIMITED_NUMBER_OF_USERS;
private int mDefaultUserInfoPropertyFlags = 0;
- private ArrayList<String> mDefaultRestrictions = new ArrayList<>();
+ private @Nullable Bundle mDefaultRestrictions = null;
private boolean mEnabled = true;
private int mLabel = Resources.ID_NULL;
- private int[] mBadgeLabels = null;
- private int[] mBadgeColors = null;
- private int mIconBadge = Resources.ID_NULL;
- private int mBadgePlain = Resources.ID_NULL;
- private int mBadgeNoBackground = Resources.ID_NULL;
+ private @Nullable int[] mBadgeLabels = null;
+ private @Nullable int[] mBadgeColors = null;
+ private @DrawableRes int mIconBadge = Resources.ID_NULL;
+ private @DrawableRes int mBadgePlain = Resources.ID_NULL;
+ private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
public Builder setName(String name) {
mName = name;
@@ -304,27 +313,27 @@
return this;
}
- public Builder setBadgeLabels(int ... badgeLabels) {
+ public Builder setBadgeLabels(@StringRes int ... badgeLabels) {
mBadgeLabels = badgeLabels;
return this;
}
- public Builder setBadgeColors(int ... badgeColors) {
+ public Builder setBadgeColors(@ColorRes int ... badgeColors) {
mBadgeColors = badgeColors;
return this;
}
- public Builder setIconBadge(int badgeIcon) {
+ public Builder setIconBadge(@DrawableRes int badgeIcon) {
mIconBadge = badgeIcon;
return this;
}
- public Builder setBadgePlain(int badgePlain) {
+ public Builder setBadgePlain(@DrawableRes int badgePlain) {
mBadgePlain = badgePlain;
return this;
}
- public Builder setBadgeNoBackground(int badgeNoBackground) {
+ public Builder setBadgeNoBackground(@DrawableRes int badgeNoBackground) {
mBadgeNoBackground = badgeNoBackground;
return this;
}
@@ -334,11 +343,15 @@
return this;
}
- public Builder setDefaultRestrictions(ArrayList<String> restrictions) {
+ public Builder setDefaultRestrictions(@Nullable Bundle restrictions) {
mDefaultRestrictions = restrictions;
return this;
}
+ @UserInfoFlag int getBaseType() {
+ return mBaseType;
+ }
+
public UserTypeDetails createUserTypeDetails() {
Preconditions.checkArgument(mName != null,
"Cannot create a UserTypeDetails with no name.");
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 43bbab1..7d45516 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -34,18 +34,36 @@
import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
import android.os.UserManager;
import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* Class for creating all {@link UserTypeDetails} on the device.
*
+ * This class is responsible both for defining the AOSP use types, as well as reading in customized
+ * user types from {@link com.android.internal.R.xml#config_user_types}.
+ *
* Tests are located in UserManagerServiceUserTypeTest.java.
* @hide
*/
public final class UserTypeFactory {
+ private static final String LOG_TAG = "UserTypeFactory";
+
/** This is a utility class, so no instantiable constructor. */
private UserTypeFactory() {}
@@ -55,21 +73,25 @@
* @return mapping from the name of each user type to its {@link UserTypeDetails} object
*/
public static ArrayMap<String, UserTypeDetails> getUserTypes() {
- final ArrayMap<String, UserTypeDetails> map = new ArrayMap<>();
- // TODO(b/142482943): Read an xml file for OEM customized types.
- // Remember to disallow "android." namespace
- // TODO(b/142482943): Read an xml file to get any overrides for the built-in types.
- final int maxManagedProfiles = 1;
- map.put(USER_TYPE_PROFILE_MANAGED,
- getDefaultTypeProfileManaged().setMaxAllowedPerParent(maxManagedProfiles)
- .createUserTypeDetails());
- map.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeSystemFull().createUserTypeDetails());
- map.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary().createUserTypeDetails());
- map.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest().createUserTypeDetails());
- map.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo().createUserTypeDetails());
- map.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted().createUserTypeDetails());
- map.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless().createUserTypeDetails());
- return map;
+ final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+ builders.put(USER_TYPE_PROFILE_MANAGED, getDefaultTypeProfileManaged());
+ builders.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeFullSystem());
+ builders.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary());
+ builders.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest());
+ builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo());
+ builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
+ builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
+
+ try (XmlResourceParser parser =
+ Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) {
+ customizeBuilders(builders, parser);
+ }
+
+ final ArrayMap<String, UserTypeDetails> types = new ArrayMap<>(builders.size());
+ for (int i = 0; i < builders.size(); i++) {
+ types.put(builders.keyAt(i), builders.valueAt(i).createUserTypeDetails());
+ }
+ return types;
}
/**
@@ -93,7 +115,8 @@
.setBadgeColors(
com.android.internal.R.color.profile_badge_1,
com.android.internal.R.color.profile_badge_2,
- com.android.internal.R.color.profile_badge_3);
+ com.android.internal.R.color.profile_badge_3)
+ .setDefaultRestrictions(null);
}
/**
@@ -104,7 +127,8 @@
return new UserTypeDetails.Builder()
.setName(USER_TYPE_FULL_SECONDARY)
.setBaseType(FLAG_FULL)
- .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS);
+ .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
+ .setDefaultRestrictions(getDefaultSecondaryUserRestrictions());
}
/**
@@ -115,13 +139,12 @@
.getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
final int flags = FLAG_GUEST | (ephemeralGuests ? FLAG_EPHEMERAL : 0);
- // TODO(b/142482943): Put UMS.initDefaultGuestRestrictions() here; then fetch them from here
-
return new UserTypeDetails.Builder()
.setName(USER_TYPE_FULL_GUEST)
.setBaseType(FLAG_FULL)
.setDefaultUserInfoPropertyFlags(flags)
- .setMaxAllowed(1);
+ .setMaxAllowed(1)
+ .setDefaultRestrictions(getDefaultGuestUserRestrictions());
}
/**
@@ -132,7 +155,8 @@
.setName(USER_TYPE_FULL_DEMO)
.setBaseType(FLAG_FULL)
.setDefaultUserInfoPropertyFlags(FLAG_DEMO)
- .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS);
+ .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
+ .setDefaultRestrictions(null);
}
/**
@@ -144,13 +168,15 @@
.setName(USER_TYPE_FULL_RESTRICTED)
.setBaseType(FLAG_FULL)
.setDefaultUserInfoPropertyFlags(FLAG_RESTRICTED)
- .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS);
+ .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
+ // NB: UserManagerService.createRestrictedProfile() applies hardcoded restrictions.
+ .setDefaultRestrictions(null);
}
/**
* Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SYSTEM} configuration.
*/
- private static UserTypeDetails.Builder getDefaultTypeSystemFull() {
+ private static UserTypeDetails.Builder getDefaultTypeFullSystem() {
return new UserTypeDetails.Builder()
.setName(USER_TYPE_FULL_SYSTEM)
.setBaseType(FLAG_SYSTEM | FLAG_FULL);
@@ -165,4 +191,193 @@
.setName(USER_TYPE_SYSTEM_HEADLESS)
.setBaseType(FLAG_SYSTEM);
}
+
+ private static Bundle getDefaultSecondaryUserRestrictions() {
+ final Bundle restrictions = new Bundle();
+ restrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
+ restrictions.putBoolean(UserManager.DISALLOW_SMS, true);
+ return restrictions;
+ }
+
+ private static Bundle getDefaultGuestUserRestrictions() {
+ // Guest inherits the secondary user's restrictions, plus has some extra ones.
+ final Bundle restrictions = getDefaultSecondaryUserRestrictions();
+ restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, true);
+ restrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
+ return restrictions;
+ }
+
+ /**
+ * Reads the given xml parser to obtain device user-type customization, and updates the given
+ * map of {@link UserTypeDetails.Builder}s accordingly.
+ * <p>
+ * The xml file can specify the attributes according to the set... methods below.
+ */
+ @VisibleForTesting
+ static void customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders,
+ XmlResourceParser parser) {
+ try {
+ XmlUtils.beginDocument(parser, "user-types");
+ for (XmlUtils.nextElement(parser);
+ parser.getEventType() != XmlResourceParser.END_DOCUMENT;
+ XmlUtils.nextElement(parser)) {
+ final boolean isProfile;
+ final String elementName = parser.getName();
+ if ("profile-type".equals(elementName)) {
+ isProfile = true;
+ } else if ("full-type".equals(elementName)) {
+ isProfile = false;
+ } else {
+ Slog.w(LOG_TAG, "Skipping unknown element " + elementName + " in "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
+ String typeName = parser.getAttributeValue(null, "name");
+ if (typeName == null) {
+ Slog.w(LOG_TAG, "Skipping user type with no name in "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ typeName.intern();
+
+ UserTypeDetails.Builder builder;
+ if (typeName.startsWith("android.")) {
+ // typeName refers to a AOSP-defined type which we are modifying.
+ Slog.i(LOG_TAG, "Customizing user type " + typeName);
+ builder = builders.get(typeName);
+ if (builder == null) {
+ throw new IllegalArgumentException("Illegal custom user type name "
+ + typeName + ": Non-AOSP user types cannot start with 'android.'");
+ }
+ final boolean isValid =
+ (isProfile && builder.getBaseType() == UserInfo.FLAG_PROFILE)
+ || (!isProfile && builder.getBaseType() == UserInfo.FLAG_FULL);
+ if (!isValid) {
+ throw new IllegalArgumentException("Wrong base type to customize user type "
+ + "(" + typeName + "), which is type "
+ + UserInfo.flagsToString(builder.getBaseType()));
+ }
+ } else if (isProfile) {
+ // typeName refers to a new OEM-defined profile type which we are defining.
+ Slog.i(LOG_TAG, "Creating custom user type " + typeName);
+ builder = new UserTypeDetails.Builder();
+ builder.setName(typeName);
+ builder.setBaseType(FLAG_PROFILE);
+ builders.put(typeName, builder);
+ } else {
+ throw new IllegalArgumentException("Creation of non-profile user type "
+ + "(" + typeName + ") is not currently supported.");
+ }
+
+ // Process the attributes (other than name).
+ if (isProfile) {
+ setIntAttribute(parser, "max-allowed-per-parent",
+ builder::setMaxAllowedPerParent);
+ setResAttribute(parser, "icon-badge", builder::setIconBadge);
+ setResAttribute(parser, "badge-plain", builder::setBadgePlain);
+ setResAttribute(parser, "badge-no-background", builder::setBadgeNoBackground);
+ }
+
+ // Process child elements.
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ final String childName = parser.getName();
+ if ("default-restrictions".equals(childName)) {
+ final Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser);
+ builder.setDefaultRestrictions(restrictions);
+ } else if (isProfile && "badge-labels".equals(childName)) {
+ setResAttributeArray(parser, builder::setBadgeLabels);
+ } else if (isProfile && "badge-colors".equals(childName)) {
+ setResAttributeArray(parser, builder::setBadgeColors);
+ } else {
+ Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in "
+ + parser.getPositionDescription());
+ }
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Slog.w(LOG_TAG, "Cannot read user type configuration file.", e);
+ }
+ }
+
+ /**
+ * If the given attribute exists, gets the int stored in it and performs the given fcn using it.
+ * The stored value must be an int or NumberFormatException will be thrown.
+ *
+ * @param parser xml parser from which to read the attribute
+ * @param attributeName name of the attribute
+ * @param fcn one-int-argument function,
+ * like {@link UserTypeDetails.Builder#setMaxAllowedPerParent(int)}
+ */
+ private static void setIntAttribute(XmlResourceParser parser, String attributeName,
+ Consumer<Integer> fcn) {
+ final String intValue = parser.getAttributeValue(null, attributeName);
+ if (intValue == null) {
+ return;
+ }
+ try {
+ fcn.accept(Integer.parseInt(intValue));
+ } catch (NumberFormatException e) {
+ Slog.e(LOG_TAG, "Cannot parse value of '" + intValue + "' for " + attributeName
+ + " in " + parser.getPositionDescription(), e);
+ throw e;
+ }
+ }
+
+ /**
+ * If the given attribute exists, gets the resId stored in it (or 0 if it is not a valid resId)
+ * and performs the given fcn using it.
+ *
+ * @param parser xml parser from which to read the attribute
+ * @param attributeName name of the attribute
+ * @param fcn one-argument function, like {@link UserTypeDetails.Builder#setIconBadge(int)}
+ */
+ private static void setResAttribute(XmlResourceParser parser, String attributeName,
+ Consumer<Integer> fcn) {
+ if (parser.getAttributeValue(null, attributeName) == null) {
+ // Attribute is not present, i.e. use the default value.
+ return;
+ }
+ final int resId = parser.getAttributeResourceValue(null, attributeName, Resources.ID_NULL);
+ fcn.accept(resId);
+ }
+
+ /**
+ * Gets the resIds stored in "item" elements (in their "res" attribute) at the current depth.
+ * Then performs the given fcn using the int[] array of these resIds.
+ * <p>
+ * Each xml element is expected to be of the form {@code <item res="someResValue" />}.
+ *
+ * @param parser xml parser from which to read the elements and their attributes
+ * @param fcn function, like {@link UserTypeDetails.Builder#setBadgeColors(int...)}
+ */
+ private static void setResAttributeArray(XmlResourceParser parser, Consumer<int[]> fcn)
+ throws IOException, XmlPullParserException {
+
+ ArrayList<Integer> resList = new ArrayList<>();
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ final String elementName = parser.getName();
+ if (!"item".equals(elementName)) {
+ Slog.w(LOG_TAG, "Skipping unknown child element " + elementName + " in "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ final int resId = parser.getAttributeResourceValue(null, "res", -1);
+ if (resId == -1) {
+ continue;
+ }
+ resList.add(resId);
+ }
+
+ int[] result = new int[resList.size()];
+ for (int i = 0; i < resList.size(); i++) {
+ result[i] = resList.get(i);
+ }
+ fcn.accept(result);
+ }
}
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 9668c21..565a85f 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -115,6 +115,12 @@
protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
}
+ @Override
+ public String toString() {
+ return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
+ + "}";
+ }
+
public String getName() {
return name;
}
@@ -170,7 +176,8 @@
if (this.perm == null) {
return false;
}
- return Objects.equals(this.perm.className, perm.className);
+ return Objects.equals(this.perm.getPackageName(), perm.getPackageName())
+ && Objects.equals(this.perm.className, perm.className);
}
public boolean isDynamic() {
@@ -273,6 +280,9 @@
public boolean isTelephony() {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_TELEPHONY) != 0;
}
+ public boolean isCompanion() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0;
+ }
public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
if (!origPackageName.equals(sourcePackageName)) {
@@ -406,7 +416,8 @@
r.append("DUP:");
r.append(p.getName());
}
- if (bp.perm != null && Objects.equals(bp.perm.className, p.className)) {
+ if (bp.perm != null && Objects.equals(bp.perm.getPackageName(), p.getPackageName())
+ && Objects.equals(bp.perm.className, p.className)) {
bp.protectionLevel = p.protectionLevel;
}
if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
@@ -643,20 +654,4 @@
}
return true;
}
-
- @Override
- public String toString() {
- return "BasePermission{" +
- "name='" + name + '\'' +
- ", type=" + type +
- ", sourcePackageName='" + sourcePackageName + '\'' +
- ", sourcePackageSetting=" + sourcePackageSetting +
- ", protectionLevel=" + protectionLevel +
- ", perm=" + perm +
- ", pendingPermissionInfo=" + pendingPermissionInfo +
- ", uid=" + uid +
- ", gids=" + Arrays.toString(gids) +
- ", perUser=" + perUser +
- '}';
- }
}
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 603b01a..6d6ec25 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -726,10 +726,12 @@
userId, STORAGE_PERMISSIONS);
// TextClassifier Service
- String textClassifierPackageName =
- mContext.getPackageManager().getSystemTextClassifierPackageName();
- if (!TextUtils.isEmpty(textClassifierPackageName)) {
- grantPermissionsToSystemPackage(textClassifierPackageName, userId,
+ final String[] packages = mContext.getPackageManager().getSystemTextClassifierPackages();
+ if (packages.length > 0) {
+ // We have a list of supported system TextClassifier package names, the first one
+ // package is the default system TextClassifier service. Grant permissions to default
+ // TextClassifier Service.
+ grantPermissionsToSystemPackage(packages[0], userId,
COARSE_BACKGROUND_LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS);
}
@@ -998,7 +1000,7 @@
private void revokeRuntimePermissions(String packageName, Set<String> permissions,
boolean systemFixed, int userId) {
PackageInfo pkg = getSystemPackageInfo(packageName);
- if (ArrayUtils.isEmpty(pkg.requestedPermissions)) {
+ if (pkg == null || ArrayUtils.isEmpty(pkg.requestedPermissions)) {
return;
}
Set<String> revokablePermissions = new ArraySet<>(Arrays.asList(pkg.requestedPermissions));
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
new file mode 100644
index 0000000..b809951
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -0,0 +1,281 @@
+/*
+ * 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.pm.permission;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.permission.PermissionControllerManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Class that handles one-time permissions for a user
+ */
+public class OneTimePermissionUserManager {
+
+ private static final String LOG_TAG = OneTimePermissionUserManager.class.getSimpleName();
+
+ private final @NonNull Context mContext;
+ private final @NonNull ActivityManager mActivityManager;
+ private final @NonNull AlarmManager mAlarmManager;
+ private final @NonNull PermissionControllerManager mPermissionControllerManager;
+
+ private final Object mLock = new Object();
+
+ /** Maps the uid to the PackageInactivityListener */
+ @GuardedBy("mLock")
+ private final SparseArray<PackageInactivityListener> mListeners = new SparseArray<>();
+
+ OneTimePermissionUserManager(@NonNull Context context) {
+ mContext = context;
+ mActivityManager = context.getSystemService(ActivityManager.class);
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class);
+ }
+
+ /**
+ * Starts a one-time permission session for a given package. A one-time permission session is
+ * ended if app becomes inactive. Inactivity is defined as the package's uid importance level
+ * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
+ * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
+ * until going > importanceToResetTimer.
+ * <p>
+ * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
+ * then the session is extended until either the importance goes above
+ * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
+ * will continue the session and reset the timer.
+ * </p>
+ * <p>
+ * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
+ * </p>
+ * <p>
+ * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout
+ * is invoked.
+ * </p>
+ * <p>
+ * Note that if there is currently an active session for a package a new one isn't created and
+ * the existing one isn't changed.
+ * </p>
+ * @param packageName The package to start a one-time permission session for
+ * @param timeoutMillis Number of milliseconds for an app to be in an inactive state
+ * @param importanceToResetTimer The least important level to uid must be to reset the timer
+ * @param importanceToKeepSessionAlive The least important level the uid must be to keep the
+ * session alive
+ *
+ * @hide
+ */
+ void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis,
+ int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ int uid;
+ try {
+ uid = mContext.getPackageManager().getPackageUid(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Unknown package name " + packageName, e);
+ return;
+ }
+
+ synchronized (mLock) {
+ PackageInactivityListener listener = mListeners.get(uid);
+ if (listener == null) {
+ listener = new PackageInactivityListener(uid, packageName, timeoutMillis,
+ importanceToResetTimer, importanceToKeepSessionAlive);
+ mListeners.put(uid, listener);
+ }
+ }
+ }
+
+ /**
+ * Stops the one-time permission session for the package. The callback to the end of session is
+ * not invoked. If there is no one-time session for the package then nothing happens.
+ *
+ * @param packageName Package to stop the one-time permission session for
+ */
+ void stopPackageOneTimeSession(@NonNull String packageName) {
+ int uid;
+ try {
+ uid = mContext.getPackageManager().getPackageUid(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Unknown package name " + packageName, e);
+ return;
+ }
+
+ synchronized (mLock) {
+ PackageInactivityListener listener = mListeners.get(uid);
+ if (listener != null) {
+ mListeners.remove(uid);
+ listener.cancel();
+ }
+ }
+ }
+
+ /**
+ * A class which watches a package for inactivity and notifies the permission controller when
+ * the package becomes inactive
+ */
+ private class PackageInactivityListener implements AlarmManager.OnAlarmListener {
+
+ private static final long TIMER_INACTIVE = -1;
+
+ private final int mUid;
+ private final @NonNull String mPackageName;
+ private final long mTimeout;
+ private final int mImportanceToResetTimer;
+ private final int mImportanceToKeepSessionAlive;
+
+ private boolean mIsAlarmSet;
+ private boolean mIsFinished;
+
+ private long mTimerStart = TIMER_INACTIVE;
+
+ private final ActivityManager.OnUidImportanceListener mStartTimerListener;
+ private final ActivityManager.OnUidImportanceListener mSessionKillableListener;
+ private final ActivityManager.OnUidImportanceListener mGoneListener;
+
+ private final Object mInnerLock = new Object();
+
+ private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
+ int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ mUid = uid;
+ mPackageName = packageName;
+ mTimeout = timeout;
+ mImportanceToResetTimer = importanceToResetTimer;
+ mImportanceToKeepSessionAlive = importanceToKeepSessionAlive;
+
+ mStartTimerListener =
+ (changingUid, importance) -> onImportanceChanged(changingUid, importance);
+ mSessionKillableListener =
+ (changingUid, importance) -> onImportanceChanged(changingUid, importance);
+ mGoneListener =
+ (changingUid, importance) -> onImportanceChanged(changingUid, importance);
+
+ mActivityManager.addOnUidImportanceListener(mStartTimerListener,
+ importanceToResetTimer);
+ mActivityManager.addOnUidImportanceListener(mSessionKillableListener,
+ importanceToKeepSessionAlive);
+ mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED);
+
+ onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName));
+ }
+
+ private void onImportanceChanged(int uid, int importance) {
+ if (uid != mUid) {
+ return;
+ }
+ synchronized (mInnerLock) {
+ if (importance > IMPORTANCE_CACHED) {
+ onPackageInactiveLocked();
+ return;
+ }
+ if (importance > mImportanceToResetTimer) {
+ if (mTimerStart == TIMER_INACTIVE) {
+ mTimerStart = System.currentTimeMillis();
+ }
+ } else {
+ mTimerStart = TIMER_INACTIVE;
+ }
+ if (importance > mImportanceToKeepSessionAlive) {
+ setAlarmLocked();
+ } else {
+ cancelAlarmLocked();
+ }
+ }
+ }
+
+ /**
+ * Stop watching the package for inactivity
+ */
+ private void cancel() {
+ synchronized (mInnerLock) {
+ mIsFinished = true;
+ cancelAlarmLocked();
+ mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
+ mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
+ mActivityManager.removeOnUidImportanceListener(mGoneListener);
+ }
+ }
+
+ /**
+ * Set the alarm which will callback when the package is inactive
+ */
+ @GuardedBy("mInnerLock")
+ private void setAlarmLocked() {
+ if (mIsAlarmSet) {
+ return;
+ }
+
+ long revokeTime = mTimerStart + mTimeout;
+ if (revokeTime > System.currentTimeMillis()) {
+ mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, revokeTime, LOG_TAG, this,
+ mContext.getMainThreadHandler());
+ mIsAlarmSet = true;
+ } else {
+ mIsAlarmSet = true;
+ onAlarm();
+ }
+ }
+
+ /**
+ * Cancel the alarm
+ */
+ @GuardedBy("mInnerLock")
+ private void cancelAlarmLocked() {
+ if (mIsAlarmSet) {
+ mAlarmManager.cancel(this);
+ mIsAlarmSet = false;
+ }
+ }
+
+ /**
+ * Called when the package is considered inactive. This is the end of the session
+ */
+ @GuardedBy("mInnerLock")
+ private void onPackageInactiveLocked() {
+ if (mIsFinished) {
+ return;
+ }
+ mIsFinished = true;
+ cancelAlarmLocked();
+ mContext.getMainThreadHandler().post(
+ () -> mPermissionControllerManager.notifyOneTimePermissionSessionTimeout(
+ mPackageName));
+ mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
+ mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
+ mActivityManager.removeOnUidImportanceListener(mGoneListener);
+ synchronized (mLock) {
+ mListeners.remove(mUid);
+ }
+ }
+
+ @Override
+ public void onAlarm() {
+ synchronized (mInnerLock) {
+ if (!mIsAlarmSet) {
+ return;
+ }
+ mIsAlarmSet = false;
+ onPackageInactiveLocked();
+ }
+ }
+ }
+}
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 5adb648..605f869 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -204,6 +204,10 @@
/** Permission controller: User space permission management */
private PermissionControllerManager mPermissionControllerManager;
+ /** Map of OneTimePermissionUserManagers keyed by userId */
+ private final SparseArray<OneTimePermissionUserManager> mOneTimePermissionUserManagers =
+ new SparseArray<>();
+
/** Default permission policy to provide proper behaviour out-of-the-box */
private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
@@ -321,7 +325,10 @@
public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
int uid) {
onPermissionUpdated(updatedUserIds, sync);
- mOnPermissionChangeListeners.onPermissionsChanged(uid);
+ for (int i = 0; i < updatedUserIds.length; i++) {
+ int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid));
+ mOnPermissionChangeListeners.onPermissionsChanged(userUid);
+ }
}
public void onInstallPermissionUpdatedNotifyListener(int uid) {
onInstallPermissionUpdated();
@@ -733,7 +740,8 @@
// Install and runtime permissions are stored in different places,
// so figure out what permission changed and persist the change.
if (permissionsState.getInstallPermissionState(permName) != null) {
- callback.onInstallPermissionUpdatedNotifyListener(pkg.getUid());
+ int userUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+ callback.onInstallPermissionUpdatedNotifyListener(userUid);
} else if (permissionsState.getRuntimePermissionState(permName, userId) != null
|| hadState) {
callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false,
@@ -1575,15 +1583,11 @@
}
// If shared user we just reset the state to which only this app contributed.
- final String sharedUserId =
- mPackageManagerInt.getSharedUserIdForPackage(pkg.getPackageName());
- final String[] pkgNames =
- mPackageManagerInt.getPackagesForSharedUserId(sharedUserId, userId);
- if (pkgNames != null && pkgNames.length > 0) {
+ final String[] pkgNames = mPackageManagerInt.getSharedUserPackagesForPackage(
+ pkg.getPackageName(), userId);
+ if (pkgNames.length > 0) {
boolean used = false;
- final int packageCount = pkgNames.length;
- for (int j = 0; j < packageCount; j++) {
- final String sharedPkgName = pkgNames[j];
+ for (String sharedPkgName : pkgNames) {
final AndroidPackage sharedPkg =
mPackageManagerInt.getPackage(sharedPkgName);
if (sharedPkg != null && !sharedPkg.getPackageName().equals(packageName)
@@ -3005,6 +3009,53 @@
SystemConfig.getInstance().getSplitPermissions());
}
+ private OneTimePermissionUserManager getOneTimePermissionUserManager(@UserIdInt int userId) {
+ synchronized (mLock) {
+ OneTimePermissionUserManager oneTimePermissionUserManager =
+ mOneTimePermissionUserManagers.get(userId);
+ if (oneTimePermissionUserManager == null) {
+ oneTimePermissionUserManager = new OneTimePermissionUserManager(
+ mContext.createContextAsUser(UserHandle.of(userId), /*flags*/ 0));
+ mOneTimePermissionUserManagers.put(userId, oneTimePermissionUserManager);
+ }
+ return oneTimePermissionUserManager;
+ }
+ }
+
+ @Override
+ public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
+ long timeoutMillis, int importanceToResetTimer, int importanceToKeepSessionAlive) {
+ mContext.enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ "Must be able to revoke runtime permissions to register permissions as one time.");
+ mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS,
+ "Must be able to access usage stats to register permissions as one time.");
+ packageName = Preconditions.checkNotNull(packageName);
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
+ timeoutMillis, importanceToResetTimer, importanceToKeepSessionAlive);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void stopOneTimePermissionSession(String packageName, @UserIdInt int userId) {
+ mContext.enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ "Must be able to revoke runtime permissions to remove permissions as one time.");
+ mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS,
+ "Must be able to access usage stats to remove permissions as one time.");
+ Preconditions.checkNotNull(packageName);
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ getOneTimePermissionUserManager(userId).stopPackageOneTimeSession(packageName);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean isNewPlatformPermissionForPackage(String perm, AndroidPackage pkg) {
boolean allowed = false;
final int NP = PackageParser.NEW_PERMISSIONS.length;
@@ -3275,6 +3326,13 @@
// Special permissions for the system telephony apps.
allowed = true;
}
+ if (!allowed && bp.isCompanion()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_COMPANION, UserHandle.USER_SYSTEM),
+ pkg.getPackageName())) {
+ // Special permissions for the system companion device manager.
+ allowed = true;
+ }
}
return allowed;
}
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index c779ebf..5271493 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -62,7 +62,6 @@
import com.android.internal.globalactions.ToggleAction;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
@@ -133,7 +132,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
context.registerReceiver(mBroadcastReceiver, filter);
ConnectivityManager cm = (ConnectivityManager)
@@ -231,7 +230,7 @@
mIsWaitingForEcmExit = true;
// Launch ECM exit dialog
Intent ecmDialogIntent =
- new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+ new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(ecmDialogIntent);
} else {
@@ -741,7 +740,7 @@
if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
mHandler.sendEmptyMessage(MESSAGE_DISMISS);
}
- } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
+ } 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)) &&
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 5f39f51..e7269a6 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -353,15 +353,14 @@
final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(
getUserContext(getContext(), UserHandle.of(userId)));
synchroniser.addPackage(pkg.packageName);
- final String[] sharedPkgNames = packageManagerInternal.getPackagesForSharedUserId(
- pkg.sharedUserId, userId);
- if (sharedPkgNames != null) {
- for (String sharedPkgName : sharedPkgNames) {
- final AndroidPackage sharedPkg = packageManagerInternal
- .getPackage(sharedPkgName);
- if (sharedPkg != null) {
- synchroniser.addPackage(sharedPkg.getPackageName());
- }
+ final String[] sharedPkgNames = packageManagerInternal.getSharedUserPackagesForPackage(
+ pkg.packageName, userId);
+
+ for (String sharedPkgName : sharedPkgNames) {
+ final AndroidPackage sharedPkg = packageManagerInternal
+ .getPackage(sharedPkgName);
+ if (sharedPkg != null) {
+ synchroniser.addPackage(sharedPkg.getPackageName());
}
}
synchroniser.syncPackages();
@@ -677,10 +676,19 @@
private void setUidMode(int opCode, int uid, int mode,
@NonNull String packageName) {
- final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
- .opToPublicName(opCode), uid, packageName);
- if (currentMode != mode) {
+ final int oldMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
+ opCode), uid, packageName);
+ if (oldMode != mode) {
mAppOpsManager.setUidMode(opCode, uid, mode);
+ final int newMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
+ opCode), uid, packageName);
+ if (newMode != mode) {
+ // Work around incorrectly-set package mode. It never makes sense for app ops
+ // related to runtime permissions, but can get in the way and we have to reset
+ // it.
+ mAppOpsManager.setMode(opCode, uid, packageName, AppOpsManager.opToDefaultMode(
+ opCode));
+ }
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8435a82..0d8f257 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -157,6 +157,7 @@
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UEventObserver;
import android.os.UserHandle;
import android.os.VibrationEffect;
@@ -4603,6 +4604,7 @@
public void screenTurningOn(final ScreenOnListener screenOnListener) {
if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on...");
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */);
updateScreenOffSleepToken(false);
mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
@@ -4665,6 +4667,7 @@
if (!mDefaultDisplayPolicy.finishScreenTurningOn()) {
return; // Spurious or not ready yet.
}
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */);
final boolean enableScreen;
final boolean awake = mDefaultDisplayPolicy.isAwake();
@@ -4949,6 +4952,7 @@
mBootMsgDialog.getWindow().setDimAmount(1);
WindowManager.LayoutParams lp = mBootMsgDialog.getWindow().getAttributes();
lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+ lp.setFitWindowInsetsTypes(0 /* types */);
mBootMsgDialog.getWindow().setAttributes(lp);
mBootMsgDialog.setCancelable(false);
mBootMsgDialog.show();
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 95a5f52..a2425a3 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -18,6 +18,7 @@
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -272,14 +273,6 @@
public WindowManager.LayoutParams getAttrs();
/**
- * Return whether this window needs the menu key shown. Must be called
- * with window lock held, because it may need to traverse down through
- * window list to determine the result.
- * @param bottom The bottom-most window to consider when determining this.
- */
- public boolean getNeedsMenuLw(WindowState bottom);
-
- /**
* Retrieve the current system UI visibility flags associated with
* this window.
*/
@@ -343,13 +336,6 @@
boolean isAnimatingLw();
/**
- * @return Whether the window can affect SystemUI flags, meaning that SystemUI (system bars,
- * for example) will be affected by the flags specified in this window. This is the
- * case when the surface is on screen but not exiting.
- */
- boolean canAffectSystemUiFlags();
-
- /**
* Is this window considered to be gone for purposes of layout?
*/
boolean isGoneForLayoutLw();
@@ -876,13 +862,15 @@
case TYPE_ACCESSIBILITY_OVERLAY:
// overlay put by accessibility services to intercept user interaction
return 30;
+ case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
+ return 31;
case TYPE_SECURE_SYSTEM_OVERLAY:
- return 31;
- case TYPE_BOOT_PROGRESS:
return 32;
+ case TYPE_BOOT_PROGRESS:
+ return 33;
case TYPE_POINTER:
// the (mouse) pointer layer
- return 33;
+ return 34;
default:
Slog.e("WindowManager", "Unknown window type: " + type);
return APPLICATION_LAYER;
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 6da8fb4..cc1cddd 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -41,12 +41,12 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.TimingsTraceLog;
import android.view.WindowManager;
-import com.android.internal.telephony.ITelephony;
import com.android.server.LocalServices;
import com.android.server.RescueParty;
import com.android.server.pm.PackageManagerService;
@@ -586,19 +586,15 @@
TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
boolean radioOff;
- final ITelephony phone =
- ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ TelephonyManager telephonyManager = mContext.getSystemService(
+ TelephonyManager.class);
- try {
- radioOff = phone == null || !phone.needMobileRadioShutdown();
- if (!radioOff) {
- Log.w(TAG, "Turning off cellular radios...");
- metricStarted(METRIC_RADIO);
- phone.shutdownMobileRadios();
- }
- } catch (RemoteException ex) {
- Log.e(TAG, "RemoteException during radio shutdown", ex);
- radioOff = true;
+ radioOff = telephonyManager == null
+ || !telephonyManager.isAnyRadioPoweredOn();
+ if (!radioOff) {
+ Log.w(TAG, "Turning off cellular radios...");
+ metricStarted(METRIC_RADIO);
+ telephonyManager.shutdownAllRadios();
}
Log.i(TAG, "Waiting for Radio...");
@@ -613,12 +609,7 @@
}
if (!radioOff) {
- try {
- radioOff = !phone.needMobileRadioShutdown();
- } catch (RemoteException ex) {
- Log.e(TAG, "RemoteException during radio shutdown", ex);
- radioOff = true;
- }
+ radioOff = !telephonyManager.isAnyRadioPoweredOn();
if (radioOff) {
Log.i(TAG, "Radio turned off.");
metricEnded(METRIC_RADIO);
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
new file mode 100644
index 0000000..acf3f79
--- /dev/null
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -0,0 +1,45 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsBatterySavingTestCases",
+ "options": [
+ {"exclude-annotation": "androidx.test.filters.LargeTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ },
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.power"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.power"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {
+ "exclude-filter": "com.android.server.power.PowerManagerServiceTest#testWakefulnessAwake_ShouldWakeUpWhenPluggedIn"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsBatterySavingTestCases"
+ },
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.power"}
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.power"}
+ ]
+ }
+ ]
+}
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 ff91299..a62bb74 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -773,9 +773,9 @@
// Handle triggering the notification to show/hide when appropriate
if (intReason == BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON) {
- runOnBgThread(this::triggerDynamicModeNotification);
+ triggerDynamicModeNotification();
} else if (!enable) {
- runOnBgThread(this::hideDynamicModeNotification);
+ hideDynamicModeNotification();
}
if (DEBUG) {
@@ -787,33 +787,42 @@
@VisibleForTesting
void triggerDynamicModeNotification() {
- NotificationManager manager = mContext.getSystemService(NotificationManager.class);
- ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID,
- R.string.dynamic_mode_notification_channel_name);
+ // The current lock is the PowerManager lock, which sits very low in the service lock
+ // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
+ runOnBgThread(() -> {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID,
+ R.string.dynamic_mode_notification_channel_name);
- 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_summary,
- Intent.ACTION_POWER_USAGE_SUMMARY),
- UserHandle.ALL);
+ 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_summary,
+ Intent.ACTION_POWER_USAGE_SUMMARY),
+ UserHandle.ALL);
+ });
}
@VisibleForTesting
void triggerStickyDisabledNotification() {
- NotificationManager manager = mContext.getSystemService(NotificationManager.class);
- ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID,
- R.string.battery_saver_notification_channel_name);
+ // The current lock is the PowerManager lock, which sits very low in the service lock
+ // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
+ runOnBgThread(() -> {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ 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,
- Settings.ACTION_BATTERY_SAVER_SETTINGS),
- UserHandle.ALL);
+ 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,
+ Settings.ACTION_BATTERY_SAVER_SETTINGS),
+ UserHandle.ALL);
+ });
}
private void ensureNotificationChannelExists(NotificationManager manager,
@@ -854,8 +863,12 @@
}
private void hideNotification(int notificationId) {
- NotificationManager manager = mContext.getSystemService(NotificationManager.class);
- manager.cancel(notificationId);
+ // The current lock is the PowerManager lock, which sits very low in the service lock
+ // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock.
+ runOnBgThread(() -> {
+ NotificationManager manager = mContext.getSystemService(NotificationManager.class);
+ manager.cancelAsUser(TAG, notificationId, UserHandle.ALL);
+ });
}
private void setStickyActive(boolean active) {
diff --git a/services/core/java/com/android/server/role/FinancialSmsManager.java b/services/core/java/com/android/server/role/FinancialSmsManager.java
deleted file mode 100644
index 2ec3993..0000000
--- a/services/core/java/com/android/server/role/FinancialSmsManager.java
+++ /dev/null
@@ -1,218 +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.role;
-
-import android.Manifest;
-import android.annotation.MainThread;
-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.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.sms.FinancialSmsService;
-import android.service.sms.IFinancialSmsService;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * This class binds to {@code FinancialSmsService}.
- */
-final class FinancialSmsManager {
-
- private static final String TAG = "FinancialSmsManager";
-
- private final Context mContext;
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private ServiceConnection mServiceConnection;
-
- @GuardedBy("mLock")
- private IFinancialSmsService mRemoteService;
-
- @GuardedBy("mLock")
- private ArrayList<Command> mQueuedCommands;
-
- FinancialSmsManager(Context context) {
- mContext = context;
- }
-
- @Nullable
- ServiceInfo getServiceInfo() {
- final String packageName =
- mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
- if (packageName == null) {
- Slog.w(TAG, "no external services package!");
- return null;
- }
-
- final Intent intent = new Intent(FinancialSmsService.ACTION_FINANCIAL_SERVICE_INTENT);
- intent.setPackage(packageName);
- final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- Slog.w(TAG, "No valid components found.");
- return null;
- }
- return resolveInfo.serviceInfo;
- }
-
- @Nullable
- private ComponentName getServiceComponentName() {
- final ServiceInfo serviceInfo = getServiceInfo();
- if (serviceInfo == null) return null;
-
- final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
- if (!Manifest.permission.BIND_FINANCIAL_SMS_SERVICE.equals(serviceInfo.permission)) {
- Slog.w(TAG, name.flattenToShortString() + " does not require permission "
- + Manifest.permission.BIND_FINANCIAL_SMS_SERVICE);
- return null;
- }
-
- return name;
- }
-
- void reset() {
- synchronized (mLock) {
- if (mServiceConnection != null) {
- mContext.unbindService(mServiceConnection);
- mServiceConnection = null;
- } else {
- Slog.d(TAG, "reset(): service is not bound. Do nothing.");
- }
- }
- }
-
- /**
- * Run a command, starting the service connection if necessary.
- */
- private void connectAndRun(@NonNull Command command) {
- synchronized (mLock) {
- if (mRemoteService != null) {
- try {
- command.run(mRemoteService);
- } catch (RemoteException e) {
- Slog.w(TAG, "exception calling service: " + e);
- }
- return;
- } else {
- if (mQueuedCommands == null) {
- mQueuedCommands = new ArrayList<>(1);
- }
- mQueuedCommands.add(command);
- // If we're already connected, don't create a new connection, just leave - the
- // command will be run when the service connects
- if (mServiceConnection != null) return;
- }
-
- // Create the connection
- mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mLock) {
- mRemoteService = IFinancialSmsService.Stub.asInterface(service);
- if (mQueuedCommands != null) {
- final int size = mQueuedCommands.size();
- for (int i = 0; i < size; i++) {
- final Command queuedCommand = mQueuedCommands.get(i);
- try {
- queuedCommand.run(mRemoteService);
- } catch (RemoteException e) {
- Slog.w(TAG, "exception calling " + name + ": " + e);
- }
- }
- mQueuedCommands = null;
- }
- }
- }
-
- @Override
- @MainThread
- public void onServiceDisconnected(ComponentName name) {
- synchronized (mLock) {
- mRemoteService = null;
- }
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- synchronized (mLock) {
- mRemoteService = null;
- }
- }
-
- @Override
- public void onNullBinding(ComponentName name) {
- synchronized (mLock) {
- mRemoteService = null;
- }
- }
- };
-
- final ComponentName component = getServiceComponentName();
- if (component != null) {
- final Intent intent = new Intent();
- intent.setComponent(component);
- final long token = Binder.clearCallingIdentity();
- try {
- mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
- UserHandle.getUserHandleForUid(UserHandle.getCallingUserId()));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
- }
-
- void getSmsMessages(RemoteCallback callback, @Nullable Bundle params) {
- connectAndRun((service) -> service.getSmsMessages(callback, params));
- }
-
- void dump(String prefix, PrintWriter pw) {
- final ComponentName impl = getServiceComponentName();
- pw.print(prefix); pw.print("User ID: "); pw.println(UserHandle.getCallingUserId());
- pw.print(prefix); pw.print("Queued commands: ");
- if (mQueuedCommands == null) {
- pw.println("N/A");
- } else {
- pw.println(mQueuedCommands.size());
- }
- pw.print(prefix); pw.print("Implementation: ");
- if (impl == null) {
- pw.println("N/A");
- return;
- }
- pw.println(impl.flattenToShortString());
- }
-
- private interface Command {
- void run(IFinancialSmsService service) throws RemoteException;
- }
-}
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index a4eef9b..c4522e0 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -50,7 +50,6 @@
import android.os.ShellCallback;
import android.os.UserHandle;
import android.os.UserManagerInternal;
-import android.service.sms.FinancialSmsService;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 8b79c3f..6898e1c 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -418,6 +418,7 @@
if (isStaged()) {
parentParams.setStaged();
}
+ parentParams.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
int parentSessionId = packageInstaller.createSession(parentParams);
PackageInstaller.Session parentSession = packageInstaller.openSession(
@@ -484,6 +485,7 @@
synchronized (mLock) {
mState = ROLLBACK_STATE_AVAILABLE;
mRestoreUserDataInProgress = false;
+ info.setCommittedSessionId(-1);
}
sendFailure(context, statusReceiver,
RollbackManager.STATUS_FAILURE_INSTALL,
@@ -500,7 +502,6 @@
mRestoreUserDataInProgress = false;
}
- info.setCommittedSessionId(parentSessionId);
info.getCausePackages().addAll(causePackages);
RollbackStore.deletePackageCodePaths(this);
RollbackStore.saveRollback(this);
@@ -528,6 +529,7 @@
);
mState = ROLLBACK_STATE_COMMITTED;
+ info.setCommittedSessionId(parentSessionId);
mRestoreUserDataInProgress = true;
parentSession.commit(receiver.getIntentSender());
} catch (IOException e) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 3f5e2a4..6cdfcff 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -58,6 +58,7 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
+import com.android.server.SystemConfig;
import com.android.server.Watchdog;
import com.android.server.pm.Installer;
@@ -897,11 +898,11 @@
}
@Override
- public boolean notifyStagedSession(int sessionId) {
+ public int notifyStagedSession(int sessionId) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("notifyStagedSession may only be called by the system.");
}
- final LinkedBlockingQueue<Boolean> result = new LinkedBlockingQueue<>();
+ final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>();
// NOTE: We post this runnable on the RollbackManager's binder thread because we'd prefer
// to preserve the invariant that all operations that modify state happen there.
@@ -911,7 +912,7 @@
final PackageInstaller.SessionInfo session = installer.getSessionInfo(sessionId);
if (session == null) {
Slog.e(TAG, "No matching install session for: " + sessionId);
- result.offer(false);
+ result.offer(-1);
return;
}
@@ -923,7 +924,7 @@
if (!session.isMultiPackage()) {
if (!enableRollbackForPackageSession(newRollback.rollback, session)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
- result.offer(false);
+ result.offer(-1);
return;
}
} else {
@@ -932,25 +933,30 @@
installer.getSessionInfo(childSessionId);
if (childSession == null) {
Slog.e(TAG, "No matching child install session for: " + childSessionId);
- result.offer(false);
+ result.offer(-1);
return;
}
if (!enableRollbackForPackageSession(newRollback.rollback, childSession)) {
Slog.e(TAG, "Unable to enable rollback for session: " + sessionId);
- result.offer(false);
+ result.offer(-1);
return;
}
}
}
- result.offer(completeEnableRollback(newRollback, true) != null);
+ Rollback rollback = completeEnableRollback(newRollback, true);
+ if (rollback == null) {
+ result.offer(-1);
+ } else {
+ result.offer(rollback.info.getRollbackId());
+ }
});
try {
return result.take();
} catch (InterruptedException ie) {
Slog.e(TAG, "Interrupted while waiting for notifyStagedSession response");
- return false;
+ return -1;
}
}
@@ -1003,11 +1009,19 @@
installerPackageName) == PackageManager.PERMISSION_GRANTED;
// For now only allow rollbacks for modules or for testing.
- return (isModule(packageName) && manageRollbacksGranted)
+ return (isRollbackWhitelisted(packageName) && manageRollbacksGranted)
|| testManageRollbacksGranted;
}
/**
+ * Returns true is this package is eligible for enabling rollback.
+ */
+ private boolean isRollbackWhitelisted(String packageName) {
+ // TODO: Remove #isModule when the white list is ready.
+ return SystemConfig.getInstance().getRollbackWhitelistedPackages().contains(packageName)
+ || isModule(packageName);
+ }
+ /**
* Returns true if the package name is the name of a module.
*/
private boolean isModule(String packageName) {
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index a626166..bb09584 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -61,7 +61,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
/**
* {@link PackageHealthObserver} for {@link RollbackManagerService}.
@@ -74,10 +73,6 @@
private static final String TAG = "RollbackPackageHealthObserver";
private static final String NAME = "rollback-observer";
private static final int INVALID_ROLLBACK_ID = -1;
- // TODO: make the following values configurable via DeviceConfig
- private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS =
- TimeUnit.SECONDS.toMillis(30);
- private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
private final Context mContext;
private final Handler mHandler;
@@ -85,13 +80,9 @@
// Staged rollback ids that have been committed but their session is not yet ready
@GuardedBy("mPendingStagedRollbackIds")
private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
- // this field is initialized in the c'tor and then only accessed from mHandler thread, so
- // no need to guard with a lock
- private long mNumberOfNativeCrashPollsRemaining;
RollbackPackageHealthObserver(Context context) {
mContext = context;
- mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
handlerThread.start();
mHandler = handlerThread.getThreadHandler();
@@ -102,9 +93,15 @@
}
@Override
- public int onHealthCheckFailed(VersionedPackage failedPackage) {
- if (getAvailableRollback(mContext.getSystemService(RollbackManager.class), failedPackage)
- == null) {
+ public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
+ @FailureReasons int failureReason) {
+ // For native crashes, we will roll back any available rollbacks
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
+ && !mContext.getSystemService(RollbackManager.class)
+ .getAvailableRollbacks().isEmpty()) {
+ return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+ }
+ if (getAvailableRollback(failedPackage) == null) {
// Don't handle the notification, no rollbacks available for the package
return PackageHealthObserverImpact.USER_IMPACT_NONE;
} else {
@@ -114,52 +111,21 @@
}
@Override
- public boolean execute(VersionedPackage failedPackage, @FailureReasons int rollbackReason) {
- RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- VersionedPackage moduleMetadataPackage = getModuleMetadataPackage();
- RollbackInfo rollback = getAvailableRollback(rollbackManager, failedPackage);
- int reasonToLog = mapFailureReasonToMetric(rollbackReason);
+ public boolean execute(@Nullable VersionedPackage failedPackage,
+ @FailureReasons int rollbackReason) {
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ rollbackAll();
+ return true;
+ }
+ RollbackInfo rollback = getAvailableRollback(failedPackage);
if (rollback == null) {
Slog.w(TAG, "Expected rollback but no valid rollback found for package: [ "
+ failedPackage.getPackageName() + "] with versionCode: ["
+ failedPackage.getVersionCode() + "]");
return false;
}
-
- logEvent(moduleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
- reasonToLog, failedPackage.getPackageName());
- LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
- int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
- RollbackManager.STATUS_FAILURE);
- if (status == RollbackManager.STATUS_SUCCESS) {
- if (rollback.isStaged()) {
- int rollbackId = rollback.getRollbackId();
- synchronized (mPendingStagedRollbackIds) {
- mPendingStagedRollbackIds.add(rollbackId);
- }
- BroadcastReceiver listener =
- listenForStagedSessionReady(rollbackManager, rollbackId,
- moduleMetadataPackage);
- handleStagedSessionChange(rollbackManager, rollbackId, listener,
- moduleMetadataPackage);
- } else {
- logEvent(moduleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
- reasonToLog, failedPackage.getPackageName());
- }
- } else {
- logEvent(moduleMetadataPackage,
- StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
- reasonToLog, failedPackage.getPackageName());
- }
- });
-
- mHandler.post(() ->
- rollbackManager.commitRollback(rollback.getRollbackId(),
- Collections.singletonList(failedPackage),
- rollbackReceiver.getIntentSender()));
+ rollbackPackage(rollback, failedPackage, rollbackReason);
// Assume rollback executed successfully
return true;
}
@@ -188,10 +154,10 @@
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
String moduleMetadataPackageName = getModuleMetadataPackageName();
- VersionedPackage newModuleMetadataPackage = getModuleMetadataPackage();
- if (getAvailableRollback(rollbackManager, newModuleMetadataPackage) != null) {
- scheduleCheckAndMitigateNativeCrashes();
+ if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
+ // TODO(gavincorkery): Call into Package Watchdog from outside the observer
+ PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
}
int rollbackId = popLastStagedRollbackId();
@@ -242,8 +208,8 @@
}
}
- private RollbackInfo getAvailableRollback(RollbackManager rollbackManager,
- VersionedPackage failedPackage) {
+ private RollbackInfo getAvailableRollback(VersionedPackage failedPackage) {
+ RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
boolean hasFailedPackage = packageRollback.getPackageName().equals(
@@ -285,7 +251,7 @@
}
private BroadcastReceiver listenForStagedSessionReady(RollbackManager rollbackManager,
- int rollbackId, VersionedPackage moduleMetadataPackage) {
+ int rollbackId, @Nullable VersionedPackage moduleMetadataPackage) {
BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -300,7 +266,7 @@
}
private void handleStagedSessionChange(RollbackManager rollbackManager, int rollbackId,
- BroadcastReceiver listener, VersionedPackage moduleMetadataPackage) {
+ BroadcastReceiver listener, @Nullable VersionedPackage moduleMetadataPackage) {
PackageInstaller packageInstaller =
mContext.getPackageManager().getPackageInstaller();
List<RollbackInfo> recentRollbacks =
@@ -382,36 +348,102 @@
}
}
+
/**
- * This method should be only called on mHandler thread, since it modifies
- * {@link #mNumberOfNativeCrashPollsRemaining} and we want to keep this class lock free.
+ * Returns true if the package name is the name of a module.
*/
- private void checkAndMitigateNativeCrashes() {
- mNumberOfNativeCrashPollsRemaining--;
- // Check if native watchdog reported a crash
- if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
- execute(getModuleMetadataPackage(), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
- // we stop polling after an attempt to execute rollback, regardless of whether the
- // attempt succeeds or not
- } else {
- if (mNumberOfNativeCrashPollsRemaining > 0) {
- mHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
- NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
- }
+ private boolean isModule(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ return pm.getModuleInfo(packageName, 0) != null;
+ } catch (PackageManager.NameNotFoundException ignore) {
+ return false;
+ }
+ }
+
+ private VersionedPackage getVersionedPackage(String packageName) {
+ try {
+ return new VersionedPackage(packageName, mContext.getPackageManager().getPackageInfo(
+ packageName, 0 /* flags */).getLongVersionCode());
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
}
}
/**
- * Since this method can eventually trigger a RollbackManager rollback, it should be called
- * only once boot has completed {@code onBootCompleted} and not earlier, because the install
- * session must be entirely completed before we try to rollback.
+ * Rolls back the session that owns {@code failedPackage}
+ *
+ * @param rollback {@code rollbackInfo} of the {@code failedPackage}
+ * @param failedPackage the package that needs to be rolled back
*/
- private void scheduleCheckAndMitigateNativeCrashes() {
- Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
- + "and mitigate native crashes");
- mHandler.post(()->checkAndMitigateNativeCrashes());
+ private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
+ @FailureReasons int rollbackReason) {
+ final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ int reasonToLog = mapFailureReasonToMetric(rollbackReason);
+ final String failedPackageToLog;
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ failedPackageToLog = SystemProperties.get(
+ "sys.init.updatable_crashing_process_name", "");
+ } else {
+ failedPackageToLog = failedPackage.getPackageName();
+ }
+ final VersionedPackage logPackage = isModule(failedPackage.getPackageName())
+ ? getModuleMetadataPackage()
+ : null;
+
+ logEvent(logPackage,
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
+ reasonToLog, failedPackageToLog);
+ final LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
+ int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
+ RollbackManager.STATUS_FAILURE);
+ if (status == RollbackManager.STATUS_SUCCESS) {
+ if (rollback.isStaged()) {
+ int rollbackId = rollback.getRollbackId();
+ synchronized (mPendingStagedRollbackIds) {
+ mPendingStagedRollbackIds.add(rollbackId);
+ }
+ BroadcastReceiver listener =
+ listenForStagedSessionReady(rollbackManager, rollbackId,
+ logPackage);
+ handleStagedSessionChange(rollbackManager, rollbackId, listener,
+ logPackage);
+ } else {
+ logEvent(logPackage,
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
+ reasonToLog, failedPackageToLog);
+ }
+ } else {
+ logEvent(logPackage,
+ StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
+ reasonToLog, failedPackageToLog);
+ }
+ });
+
+ mHandler.post(() ->
+ rollbackManager.commitRollback(rollback.getRollbackId(),
+ Collections.singletonList(failedPackage),
+ rollbackReceiver.getIntentSender()));
}
+ private void rollbackAll() {
+ Slog.i(TAG, "Rolling back all available rollbacks");
+ RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
+ List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
+
+ for (RollbackInfo rollback : rollbacks) {
+ String samplePackageName = rollback.getPackages().get(0).getPackageName();
+ VersionedPackage sampleVersionedPackage = getVersionedPackage(samplePackageName);
+ if (sampleVersionedPackage == null) {
+ Slog.e(TAG, "Failed to rollback " + samplePackageName);
+ continue;
+ }
+ rollbackPackage(rollback, sampleVersionedPackage,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ }
+ }
+
+
private int mapFailureReasonToMetric(@FailureReasons int failureReason) {
switch (failureReason) {
case PackageWatchdog.FAILURE_REASON_NATIVE_CRASH:
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
new file mode 100644
index 0000000..0bbb179
--- /dev/null
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -0,0 +1,170 @@
+/*
+ * 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.security;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.security.Credentials;
+import android.security.IFileIntegrityService;
+import android.security.KeyStore;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A {@link SystemService} that provides file integrity related operations.
+ * @hide
+ */
+public class FileIntegrityService extends SystemService {
+ private static final String TAG = "FileIntegrityService";
+
+ private static CertificateFactory sCertFactory;
+
+ private Collection<X509Certificate> mTrustedCertificates = new ArrayList<X509Certificate>();
+
+ private final IBinder mService = new IFileIntegrityService.Stub() {
+ @Override
+ public boolean isApkVeritySupported() {
+ return SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
+ }
+
+ @Override
+ public boolean isAppSourceCertificateTrusted(byte[] certificateBytes) {
+ enforceAnyCallingPermissions(
+ android.Manifest.permission.REQUEST_INSTALL_PACKAGES,
+ android.Manifest.permission.INSTALL_PACKAGES);
+ try {
+ if (!isApkVeritySupported()) {
+ return false;
+ }
+
+ return mTrustedCertificates.contains(toCertificate(certificateBytes));
+ } catch (CertificateException e) {
+ Slog.e(TAG, "Failed to convert the certificate: " + e);
+ return false;
+ }
+ }
+
+ private void enforceAnyCallingPermissions(String ...permissions) {
+ for (String permission : permissions) {
+ if (getContext().checkCallingPermission(permission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ throw new SecurityException("Insufficient permission");
+ }
+ };
+
+ public FileIntegrityService(final Context context) {
+ super(context);
+ try {
+ sCertFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory");
+ }
+ }
+
+ @Override
+ public void onStart() {
+ loadAllCertificates();
+ publishBinderService(Context.FILE_INTEGRITY_SERVICE, mService);
+ }
+
+ private void loadAllCertificates() {
+ // A better alternative to load certificates would be to read from .fs-verity kernel
+ // keyring, which fsverity_init loads to during earlier boot time from the same sources
+ // below. But since the read operation from keyring is not provided in kernel, we need to
+ // duplicate the same loading logic here.
+
+ // Load certificates trusted by the device manufacturer.
+ loadCertificatesFromDirectory("/product/etc/security/fsverity");
+
+ // Load certificates trusted by the device owner.
+ loadCertificatesFromKeystore(KeyStore.getInstance());
+ }
+
+ private void loadCertificatesFromDirectory(String path) {
+ try {
+ File[] files = new File(path).listFiles();
+ if (files == null) {
+ return;
+ }
+
+ for (File cert : files) {
+ collectCertificate(Files.readAllBytes(cert.toPath()));
+ }
+ } catch (IOException e) {
+ Slog.wtf(TAG, "Failed to load fs-verity certificate from " + path, e);
+ }
+ }
+
+ private void loadCertificatesFromKeystore(KeyStore keystore) {
+ for (final String alias : keystore.list(Credentials.APP_SOURCE_CERTIFICATE,
+ Process.FSVERITY_CERT_UID)) {
+ byte[] certificateBytes = keystore.get(Credentials.APP_SOURCE_CERTIFICATE + alias,
+ Process.FSVERITY_CERT_UID, false /* suppressKeyNotFoundWarning */);
+ if (certificateBytes == null) {
+ Slog.w(TAG, "The retrieved fs-verity certificate is null, ignored " + alias);
+ continue;
+ }
+ collectCertificate(certificateBytes);
+ }
+ }
+
+ /**
+ * Tries to convert {@code bytes} into an X.509 certificate and store in memory.
+ * Errors need to be surpressed in order fo the next certificates to still be collected.
+ */
+ private void collectCertificate(@Nullable byte[] bytes) {
+ try {
+ mTrustedCertificates.add(toCertificate(bytes));
+ } catch (CertificateException | AssertionError e) {
+ Slog.e(TAG, "Invalid certificate, ignored: " + e);
+ }
+ }
+
+ /**
+ * Converts byte array into one X.509 certificate. If multiple certificate is defined, ignore
+ * the rest. The rational is to make it harder to smuggle.
+ */
+ @NonNull
+ private static X509Certificate toCertificate(@Nullable byte[] bytes)
+ throws CertificateException {
+ Certificate certificate = sCertFactory.generateCertificate(new ByteArrayInputStream(bytes));
+ if (!(certificate instanceof X509Certificate)) {
+ throw new CertificateException("Expected to contain an X.509 certificate");
+ }
+ return (X509Certificate) certificate;
+ }
+}
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
similarity index 60%
copy from core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
copy to services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
index cd988dc..3fa5230 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.os.incremental;
-
-import android.os.incremental.NamedParcelFileDescriptor;
+package com.android.server.soundtrigger_middleware;
/**
- * Class for holding data loader configuration parameters.
- * @hide
+ * An implementation of SoundTriggerMiddlewareImpl.AudioSessionProvider that ties to native
+ * AudioSystem module via JNI.
*/
-parcelable IncrementalDataLoaderParamsParcel {
- @utf8InCpp String packageName;
- @utf8InCpp String staticArgs;
- NamedParcelFileDescriptor[] dynamicArgs;
+class AudioSessionProviderImpl extends SoundTriggerMiddlewareImpl.AudioSessionProvider {
+ @Override
+ public native AudioSession acquireSession();
+
+ @Override
+ public native void releaseSession(int sessionHandle);
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
new file mode 100644
index 0000000..9b22f33
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -0,0 +1,390 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.media.audio.common.AudioConfig;
+import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+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
+ * types.
+ *
+ * @hide
+ */
+class ConversionUtil {
+ static @NonNull
+ SoundTriggerModuleProperties hidl2aidlProperties(
+ @NonNull ISoundTriggerHw.Properties hidlProperties) {
+ SoundTriggerModuleProperties aidlProperties = new SoundTriggerModuleProperties();
+ aidlProperties.implementor = hidlProperties.implementor;
+ aidlProperties.description = hidlProperties.description;
+ aidlProperties.version = hidlProperties.version;
+ aidlProperties.uuid = hidl2aidlUuid(hidlProperties.uuid);
+ aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
+ aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
+ aidlProperties.maxUsers = hidlProperties.maxUsers;
+ aidlProperties.recognitionModes = hidlProperties.recognitionModes;
+ aidlProperties.captureTransition = hidlProperties.captureTransition;
+ aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
+ aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
+ aidlProperties.triggerInEvent = hidlProperties.triggerInEvent;
+ aidlProperties.powerConsumptionMw = hidlProperties.powerConsumptionMw;
+ return aidlProperties;
+ }
+
+ static @NonNull
+ String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
+ if (hidlUuid.node == null || hidlUuid.node.length != 6) {
+ throw new IllegalArgumentException("UUID.node must be of length 6.");
+ }
+ return String.format(UuidUtil.FORMAT,
+ hidlUuid.timeLow,
+ hidlUuid.timeMid,
+ hidlUuid.versionAndTimeHigh,
+ hidlUuid.variantAndClockSeqHigh,
+ hidlUuid.node[0],
+ hidlUuid.node[1],
+ hidlUuid.node[2],
+ hidlUuid.node[3],
+ hidlUuid.node[4],
+ hidlUuid.node[5]);
+ }
+
+ static @NonNull
+ Uuid aidl2hidlUuid(@NonNull String aidlUuid) {
+ Matcher matcher = UuidUtil.PATTERN.matcher(aidlUuid);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Illegal format for UUID: " + aidlUuid);
+ }
+ Uuid hidlUuid = new Uuid();
+ hidlUuid.timeLow = Integer.parseUnsignedInt(matcher.group(1), 16);
+ hidlUuid.timeMid = (short) Integer.parseUnsignedInt(matcher.group(2), 16);
+ hidlUuid.versionAndTimeHigh = (short) Integer.parseUnsignedInt(matcher.group(3), 16);
+ hidlUuid.variantAndClockSeqHigh = (short) Integer.parseUnsignedInt(matcher.group(4), 16);
+ hidlUuid.node = new byte[]{(byte) Integer.parseUnsignedInt(matcher.group(5), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(6), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(7), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(8), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(9), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(10), 16)};
+ return hidlUuid;
+ }
+
+ static int aidl2hidlSoundModelType(int aidlType) {
+ switch (aidlType) {
+ case SoundModelType.GENERIC:
+ return android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC;
+ case SoundModelType.KEYPHRASE:
+ return android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE;
+ default:
+ throw new IllegalArgumentException("Unknown sound model type: " + aidlType);
+ }
+ }
+
+ static int hidl2aidlSoundModelType(int hidlType) {
+ switch (hidlType) {
+ case android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC:
+ return SoundModelType.GENERIC;
+ case android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE:
+ return SoundModelType.KEYPHRASE;
+ default:
+ throw new IllegalArgumentException("Unknown sound model type: " + hidlType);
+ }
+ }
+
+ static @NonNull
+ ISoundTriggerHw.Phrase aidl2hidlPhrase(@NonNull Phrase aidlPhrase) {
+ ISoundTriggerHw.Phrase hidlPhrase = new ISoundTriggerHw.Phrase();
+ hidlPhrase.id = aidlPhrase.id;
+ hidlPhrase.recognitionModes = aidl2hidlRecognitionModes(aidlPhrase.recognitionModes);
+ for (int aidlUser : aidlPhrase.users) {
+ hidlPhrase.users.add(aidlUser);
+ }
+ hidlPhrase.locale = aidlPhrase.locale;
+ hidlPhrase.text = aidlPhrase.text;
+ return hidlPhrase;
+ }
+
+ static int aidl2hidlRecognitionModes(int aidlModes) {
+ int hidlModes = 0;
+
+ if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ }
+ return hidlModes;
+ }
+
+ static int hidl2aidlRecognitionModes(int hidlModes) {
+ int aidlModes = 0;
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER) != 0) {
+ aidlModes |= RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION)
+ != 0) {
+ aidlModes |= RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION)
+ != 0) {
+ aidlModes |= RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER) != 0) {
+ aidlModes |= RecognitionMode.GENERIC_TRIGGER;
+ }
+ return aidlModes;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.SoundModel aidl2hidlSoundModel(@NonNull SoundModel aidlModel) {
+ ISoundTriggerHw.SoundModel hidlModel = new ISoundTriggerHw.SoundModel();
+ hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
+ hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
+ hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
+ hidlModel.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlModel.data,
+ "SoundTrigger SoundModel");
+ return hidlModel;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.PhraseSoundModel aidl2hidlPhraseSoundModel(
+ @NonNull PhraseSoundModel aidlModel) {
+ ISoundTriggerHw.PhraseSoundModel hidlModel = new ISoundTriggerHw.PhraseSoundModel();
+ hidlModel.common = aidl2hidlSoundModel(aidlModel.common);
+ for (Phrase aidlPhrase : aidlModel.phrases) {
+ hidlModel.phrases.add(aidl2hidlPhrase(aidlPhrase));
+ }
+ return hidlModel;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+ @NonNull RecognitionConfig aidlConfig) {
+ ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
+ hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+ for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
+ hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+ }
+ hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+ "SoundTrigger RecognitionConfig");
+ return hidlConfig;
+ }
+
+ static @NonNull
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra aidl2hidlPhraseRecognitionExtra(
+ @NonNull PhraseRecognitionExtra aidlExtra) {
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ hidlExtra.id = aidlExtra.id;
+ hidlExtra.recognitionModes = aidl2hidlRecognitionModes(aidlExtra.recognitionModes);
+ hidlExtra.confidenceLevel = aidlExtra.confidenceLevel;
+ hidlExtra.levels.ensureCapacity(aidlExtra.levels.length);
+ for (ConfidenceLevel aidlLevel : aidlExtra.levels) {
+ hidlExtra.levels.add(aidl2hidlConfidenceLevel(aidlLevel));
+ }
+ return hidlExtra;
+ }
+
+ static @NonNull
+ PhraseRecognitionExtra hidl2aidlPhraseRecognitionExtra(
+ @NonNull android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra) {
+ PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+ aidlExtra.id = hidlExtra.id;
+ aidlExtra.recognitionModes = hidl2aidlRecognitionModes(hidlExtra.recognitionModes);
+ aidlExtra.confidenceLevel = hidlExtra.confidenceLevel;
+ aidlExtra.levels = new ConfidenceLevel[hidlExtra.levels.size()];
+ for (int i = 0; i < hidlExtra.levels.size(); ++i) {
+ aidlExtra.levels[i] = hidl2aidlConfidenceLevel(hidlExtra.levels.get(i));
+ }
+ return aidlExtra;
+ }
+
+ static @NonNull
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel aidl2hidlConfidenceLevel(
+ @NonNull ConfidenceLevel aidlLevel) {
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ hidlLevel.userId = aidlLevel.userId;
+ hidlLevel.levelPercent = aidlLevel.levelPercent;
+ return hidlLevel;
+ }
+
+ static @NonNull
+ ConfidenceLevel hidl2aidlConfidenceLevel(
+ @NonNull android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel) {
+ ConfidenceLevel aidlLevel = new ConfidenceLevel();
+ aidlLevel.userId = hidlLevel.userId;
+ aidlLevel.levelPercent = hidlLevel.levelPercent;
+ return aidlLevel;
+ }
+
+ static int hidl2aidlRecognitionStatus(int hidlStatus) {
+ switch (hidlStatus) {
+ case ISoundTriggerHwCallback.RecognitionStatus.SUCCESS:
+ return RecognitionStatus.SUCCESS;
+ case ISoundTriggerHwCallback.RecognitionStatus.ABORT:
+ return RecognitionStatus.ABORTED;
+ case ISoundTriggerHwCallback.RecognitionStatus.FAILURE:
+ return RecognitionStatus.FAILURE;
+ case 3: // This doesn't have a constant in HIDL.
+ return RecognitionStatus.FORCED;
+ default:
+ throw new IllegalArgumentException("Unknown recognition status: " + hidlStatus);
+ }
+ }
+
+ static @NonNull
+ RecognitionEvent hidl2aidlRecognitionEvent(@NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+ RecognitionEvent aidlEvent = new RecognitionEvent();
+ aidlEvent.status = hidl2aidlRecognitionStatus(hidlEvent.status);
+ aidlEvent.type = hidl2aidlSoundModelType(hidlEvent.type);
+ aidlEvent.captureAvailable = hidlEvent.captureAvailable;
+ // hidlEvent.captureSession is never a valid field.
+ aidlEvent.captureSession = -1;
+ aidlEvent.captureDelayMs = hidlEvent.captureDelayMs;
+ aidlEvent.capturePreambleMs = hidlEvent.capturePreambleMs;
+ aidlEvent.triggerInData = hidlEvent.triggerInData;
+ aidlEvent.audioConfig = hidl2aidlAudioConfig(hidlEvent.audioConfig);
+ aidlEvent.data = new byte[hidlEvent.data.size()];
+ for (int i = 0; i < aidlEvent.data.length; ++i) {
+ aidlEvent.data[i] = hidlEvent.data.get(i);
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ RecognitionEvent hidl2aidlRecognitionEvent(
+ @NonNull ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+ RecognitionEvent aidlEvent = hidl2aidlRecognitionEvent(hidlEvent.header);
+ // Data needs to get overridden with 2.1 data.
+ aidlEvent.data = HidlMemoryUtil.hidlMemoryToByteArray(hidlEvent.data);
+ return aidlEvent;
+ }
+
+ static @NonNull
+ PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(@NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+ PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+ aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+ aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+ for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+ aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+ hidlEvent.phraseExtras.get(i));
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(
+ @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+ PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+ aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+ aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+ for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+ aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+ hidlEvent.phraseExtras.get(i));
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ AudioConfig hidl2aidlAudioConfig(
+ @NonNull android.hardware.audio.common.V2_0.AudioConfig hidlConfig) {
+ AudioConfig aidlConfig = new AudioConfig();
+ // TODO(ytai): channelMask and format might need a more careful conversion to make sure the
+ // constants match.
+ aidlConfig.sampleRateHz = hidlConfig.sampleRateHz;
+ aidlConfig.channelMask = hidlConfig.channelMask;
+ aidlConfig.format = hidlConfig.format;
+ aidlConfig.offloadInfo = hidl2aidlOffloadInfo(hidlConfig.offloadInfo);
+ aidlConfig.frameCount = hidlConfig.frameCount;
+ return aidlConfig;
+ }
+
+ static @NonNull
+ AudioOffloadInfo hidl2aidlOffloadInfo(
+ @NonNull android.hardware.audio.common.V2_0.AudioOffloadInfo hidlInfo) {
+ AudioOffloadInfo aidlInfo = new AudioOffloadInfo();
+ // TODO(ytai): channelMask, format, streamType and usage might need a more careful
+ // conversion to make sure the constants match.
+ aidlInfo.sampleRateHz = hidlInfo.sampleRateHz;
+ aidlInfo.channelMask = hidlInfo.channelMask;
+ aidlInfo.format = hidlInfo.format;
+ aidlInfo.streamType = hidlInfo.streamType;
+ aidlInfo.bitRatePerSecond = hidlInfo.bitRatePerSecond;
+ aidlInfo.durationMicroseconds = hidlInfo.durationMicroseconds;
+ aidlInfo.hasVideo = hidlInfo.hasVideo;
+ aidlInfo.isStreaming = hidlInfo.isStreaming;
+ aidlInfo.bitWidth = hidlInfo.bitWidth;
+ aidlInfo.bufferSize = hidlInfo.bufferSize;
+ aidlInfo.usage = hidlInfo.usage;
+ return aidlInfo;
+ }
+
+ @Nullable
+ static ModelParameterRange hidl2aidlModelParameterRange(
+ android.hardware.soundtrigger.V2_3.ModelParameterRange hidlRange) {
+ if (hidlRange == null) {
+ return null;
+ }
+ ModelParameterRange aidlRange = new ModelParameterRange();
+ aidlRange.minInclusive = hidlRange.start;
+ aidlRange.maxInclusive = hidlRange.end;
+ return aidlRange;
+ }
+
+ static int aidl2hidlModelParameter(int aidlParam) {
+ switch (aidlParam) {
+ case ModelParameter.THRESHOLD_FACTOR:
+ return android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR;
+
+ default:
+ return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalException.java b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
new file mode 100644
index 0000000..8b3e708
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalException.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 com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a non-zero status code returned by a HAL invocation.
+ * Depending on the operation that threw the error, the integrity of the HAL implementation and the
+ * client's tolerance to error, this error may or may not be recoverable. The HAL itself is expected
+ * to retain the state it had prior to the invocation (so, unless the error is a result of a HAL
+ * bug, normal operation may resume).
+ * <p>
+ * The reason why this is a RuntimeException, even though the HAL interface allows returning them
+ * is because we expect none of them to actually occur as part of correct usage of the HAL.
+ *
+ * @hide
+ */
+public class HalException extends RuntimeException {
+ public final int errorCode;
+
+ public HalException(int errorCode, @NonNull String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public HalException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
new file mode 100644
index 0000000..f0a0d83
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -0,0 +1,77 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.os.HidlMemoryUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Utilities for maintaining data compatibility between different minor versions of soundtrigger@2.x
+ * HAL.
+ * Note that some of these conversion utilities are destructive, i.e. mutate their input (for the
+ * sake of simplifying code and reducing copies).
+ */
+class Hw2CompatUtil {
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel convertSoundModel_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 = soundModel.header;
+ // Note: this mutates the input!
+ model_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(soundModel.data);
+ return model_2_0;
+ }
+
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent convertRecognitionEvent_2_0_to_2_1(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+ event_2_1.header = event;
+ event_2_1.data = HidlMemoryUtil.byteListToHidlMemory(event_2_1.header.data,
+ "SoundTrigger RecognitionEvent");
+ // Note: this mutates the input!
+ event_2_1.header.data = new ArrayList<>();
+ return event_2_1;
+ }
+
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent convertPhraseRecognitionEvent_2_0_to_2_1(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+ event_2_1 =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ event_2_1.common = convertRecognitionEvent_2_0_to_2_1(event.common);
+ event_2_1.phraseExtras = event.phraseExtras;
+ return event_2_1;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel convertPhraseSoundModel_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel();
+ model_2_0.common = convertSoundModel_2_1_to_2_0(soundModel.common);
+ model_2_0.phrases = soundModel.phrases;
+ 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) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
+ config.header;
+ // Note: this mutates the input!
+ config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+ return config_2_0;
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
new file mode 100644
index 0000000..81252c9
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.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.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;
+
+/**
+ * This interface mimics android.hardware.soundtrigger.V2_x.ISoundTriggerHw and
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHwCallback, with a few key differences:
+ * <ul>
+ * <li>Methods in the original interface generally have a status return value and potentially a
+ * second return value which is the actual return value. This is reflected via a synchronous
+ * callback, which is not very pleasant to work with. This interface replaces that pattern with
+ * the convention that a HalException is thrown for non-OK status, and then we can use the
+ * return value for the actual return value.
+ * <li>This interface will always include all the methods from the latest 2.x version (and thus
+ * from every 2.x version) interface, with the convention that unsupported methods throw a
+ * {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * <li>Cases where the original interface had multiple versions of a method representing the exact
+ * thing, or there exists a trivial conversion between the new and old version, this interface
+ * represents only the latest version, without any _version suffixes.
+ * <li>Removes some of the obscure IBinder methods.
+ * <li>No RemoteExceptions are specified. Some implementations of this interface may rethrow
+ * RemoteExceptions as RuntimeExceptions, some can guarantee handling them somehow and never throw
+ * them.
+ * <li>soundModelCallback has been removed, since nobody cares about it. Implementations are free
+ * to silently discard it.
+ * </ul>
+ * For cases where the client wants to explicitly handle specific versions of the underlying driver
+ * interface, they may call {@link #interfaceDescriptor()}.
+ * <p>
+ * <b>Note to maintainers</b>: This class must always be kept in sync with the latest 2.x version,
+ * so that clients have access to the entire functionality without having to burden themselves with
+ * compatibility, as much as possible.
+ */
+public interface ISoundTriggerHw2 {
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+ */
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback)
+ */
+ int loadSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadPhraseSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback)
+ */
+ int loadPhraseSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#unloadSoundModel(int)
+ */
+ void unloadSoundModel(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopRecognition(int)
+ */
+ void stopRecognition(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopAllRecognitions()
+ */
+ void stopAllRecognitions();
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
+ */
+ void startRecognition(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getModelState(int)
+ */
+ void getModelState(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getParameter(int, int,
+ * ISoundTriggerHw.getParameterCallback)
+ */
+ int getModelParameter(int modelHandle, int param);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#setParameter(int, int, int)
+ */
+ void setModelParameter(int modelHandle, int param, int value);
+
+ /**
+ * @return null if not supported.
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#queryParameter(int, int,
+ * ISoundTriggerHw.queryParameterCallback)
+ */
+ ModelParameterRange queryParameter(int modelHandle, int param);
+
+ /**
+ * @see IHwBinder#linkToDeath(IHwBinder.DeathRecipient, long)
+ */
+ boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie);
+
+ /**
+ * @see IHwBinder#unlinkToDeath(IHwBinder.DeathRecipient)
+ */
+ boolean unlinkToDeath(IHwBinder.DeathRecipient recipient);
+
+ /**
+ * @see IBase#interfaceDescriptor()
+ */
+ String interfaceDescriptor() throws android.os.RemoteException;
+
+ interface Callback {
+ /**
+ * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#recognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent,
+ * int)
+ */
+ void recognitionCallback(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#phraseRecognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent,
+ * int)
+ */
+ void phraseRecognitionCallback(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie);
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
new file mode 100644
index 0000000..e1fb226
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * An internal server error.
+ * <p>
+ * This exception wraps any exception thrown from a service implementation, which is a result of a
+ * bug in the server implementation (or any of its dependencies).
+ * <p>
+ * Specifically, this type is excluded from the set of whitelisted exceptions that binder would
+ * tunnel to the client process, since these exceptions are ambiguous regarding whether the client
+ * had done something wrong or the server is buggy. For example, a client getting an
+ * IllegalArgumentException cannot easily determine whether they had provided illegal arguments to
+ * the method they were calling, or whether the method implementation provided illegal arguments to
+ * some method it was calling due to a bug.
+ *
+ * @hide
+ */
+public class InternalServerError extends RuntimeException {
+ public InternalServerError(@NonNull Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
new file mode 100644
index 0000000..8361850
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a fault which:
+ * <ul>
+ * <li>Could not have been anticipated by a caller (i.e. is not a violation of any preconditions).
+ * <li>Is guaranteed to not have been caused any meaningful state change in the callee. The caller
+ * may continue operation as if the call has never been made.
+ * </ul>
+ * <p>
+ * Some recoverable faults are permanent and some are transient / circumstantial, the specific error
+ * code can provide more information about the possible recovery options.
+ * <p>
+ * The reason why this is a RuntimeException is to allow it to go through interfaces defined by
+ * AIDL, which we have no control over.
+ *
+ * @hide
+ */
+public class RecoverableException extends RuntimeException {
+ public final int errorCode;
+
+ public RecoverableException(int errorCode, @NonNull String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public RecoverableException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
new file mode 100644
index 0000000..4a852c4
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -0,0 +1,470 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * An implementation of {@link ISoundTriggerHw2}, on top of any
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHw implementation. This class hides away some of
+ * the details involved with retaining backward compatibility and adapts to the more pleasant syntax
+ * exposed by {@link ISoundTriggerHw2}, compared to the bare driver interface.
+ * <p>
+ * Exception handling:
+ * <ul>
+ * <li>All {@link RemoteException}s get rethrown as {@link RuntimeException}.
+ * <li>All HAL malfunctions get thrown as {@link HalException}.
+ * <li>All unsupported operations get thrown as {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * </ul>
+ */
+final class SoundTriggerHw2Compat implements ISoundTriggerHw2 {
+ private final @NonNull
+ IHwBinder mBinder;
+ private final @NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw mUnderlying_2_0;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw mUnderlying_2_1;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw mUnderlying_2_2;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw mUnderlying_2_3;
+
+ public SoundTriggerHw2Compat(
+ @NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw underlying) {
+ this(underlying.asBinder());
+ }
+
+ public SoundTriggerHw2Compat(IHwBinder binder) {
+ Objects.requireNonNull(binder);
+
+ mBinder = binder;
+
+ // We want to share the proxy instances rather than create a separate proxy for every
+ // version, so we go down the versions in descending order to find the latest one supported,
+ // and then simply up-cast it to obtain all the versions that are earlier.
+
+ // Attempt 2.3
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3 =
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.asInterface(binder);
+ if (as2_3 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = as2_3;
+ return;
+ }
+
+ // Attempt 2.2
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2 =
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw.asInterface(binder);
+ if (as2_2 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = as2_2;
+ mUnderlying_2_3 = null;
+ return;
+ }
+
+ // Attempt 2.1
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1 =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.asInterface(binder);
+ if (as2_1 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = as2_1;
+ mUnderlying_2_2 = mUnderlying_2_3 = null;
+ return;
+ }
+
+ // Attempt 2.0
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0 =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.asInterface(binder);
+ if (as2_0 != null) {
+ mUnderlying_2_0 = as2_0;
+ mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = null;
+ return;
+ }
+
+ throw new RuntimeException("Binder doesn't support ISoundTriggerHw@2.0");
+ }
+
+ private static void handleHalStatus(int status, String methodName) {
+ if (status != 0) {
+ throw new HalException(status, methodName);
+ }
+ }
+
+ @Override
+ public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+ properties =
+ new AtomicReference<>();
+ as2_0().getProperties(
+ (r, p) -> {
+ retval.set(r);
+ properties.set(p);
+ });
+ handleHalStatus(retval.get(), "getProperties");
+ return properties.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public int loadSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ Callback callback, int cookie) {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ try {
+ as2_1().loadSoundModel_2_1(soundModel, new SoundTriggerCallback(callback), cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return loadSoundModel_2_0(soundModel, callback, cookie);
+ }
+ handleHalStatus(retval.get(), "loadSoundModel_2_1");
+ return handle.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public int loadPhraseSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ Callback callback, int cookie) {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ try {
+ as2_1().loadPhraseSoundModel_2_1(soundModel, new SoundTriggerCallback(callback),
+ cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return loadPhraseSoundModel_2_0(soundModel, callback, cookie);
+ }
+ handleHalStatus(retval.get(), "loadSoundModel_2_1");
+ return handle.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void unloadSoundModel(int modelHandle) {
+ try {
+ int retval = as2_0().unloadSoundModel(modelHandle);
+ handleHalStatus(retval, "unloadSoundModel");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ try {
+ int retval = as2_0().stopRecognition(modelHandle);
+ handleHalStatus(retval, "stopRecognition");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+
+ }
+
+ @Override
+ public void stopAllRecognitions() {
+ try {
+ int retval = as2_0().stopAllRecognitions();
+ handleHalStatus(retval, "stopAllRecognitions");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.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");
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ startRecognition_2_0(modelHandle, config, callback, cookie);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void getModelState(int modelHandle) {
+ try {
+ int retval = as2_2().getModelState(modelHandle);
+ handleHalStatus(retval, "getModelState");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int param) {
+ AtomicInteger status = new AtomicInteger(-1);
+ AtomicInteger value = new AtomicInteger(0);
+ try {
+ as2_3().getParameter(modelHandle, param,
+ (s, v) -> {
+ status.set(s);
+ value.set(v);
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ handleHalStatus(status.get(), "getParameter");
+ return value.get();
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int param, int value) {
+ try {
+ int retval = as2_3().setParameter(modelHandle, param, value);
+ handleHalStatus(retval, "setParameter");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ }
+
+ @Override
+ public android.hardware.soundtrigger.V2_3.ModelParameterRange queryParameter(int modelHandle,
+ int param) {
+ AtomicInteger status = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_3.OptionalModelParameterRange>
+ optionalRange =
+ new AtomicReference<>();
+ try {
+ as2_3().queryParameter(modelHandle, param,
+ (s, r) -> {
+ status.set(s);
+ optionalRange.set(r);
+ });
+ } catch (NotSupported e) {
+ // For older drivers, we consider no model parameter to be supported.
+ return null;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ handleHalStatus(status.get(), "queryParameter");
+ return (optionalRange.get().getDiscriminator()
+ == android.hardware.soundtrigger.V2_3.OptionalModelParameterRange.hidl_discriminator.range)
+ ?
+ optionalRange.get().range() : null;
+ }
+
+ @Override
+ public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+ return mBinder.linkToDeath(recipient, cookie);
+ }
+
+ @Override
+ public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+ return mBinder.unlinkToDeath(recipient);
+ }
+
+ @Override
+ public String interfaceDescriptor() throws RemoteException {
+ return as2_0().interfaceDescriptor();
+ }
+
+ private int loadSoundModel_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ Callback callback, int cookie)
+ throws RemoteException {
+ // Convert the soundModel to V2.0.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 =
+ Hw2CompatUtil.convertSoundModel_2_1_to_2_0(soundModel);
+
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ as2_0().loadSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie, (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ handleHalStatus(retval.get(), "loadSoundModel");
+ return handle.get();
+ }
+
+ private int loadPhraseSoundModel_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ Callback callback, int cookie)
+ throws RemoteException {
+ // Convert the soundModel to V2.0.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+ Hw2CompatUtil.convertPhraseSoundModel_2_1_to_2_0(soundModel);
+
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ as2_0().loadPhraseSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ handleHalStatus(retval.get(), "loadSoundModel");
+ return handle.get();
+ }
+
+ private void startRecognition_2_0(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.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);
+ int retval = as2_0().startRecognition(modelHandle, config_2_0,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition");
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0() {
+ return mUnderlying_2_0;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1() throws NotSupported {
+ if (mUnderlying_2_1 == null) {
+ throw new NotSupported("Underlying driver version < 2.1");
+ }
+ return mUnderlying_2_1;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2() throws NotSupported {
+ if (mUnderlying_2_2 == null) {
+ throw new NotSupported("Underlying driver version < 2.2");
+ }
+ return mUnderlying_2_2;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3() throws NotSupported {
+ if (mUnderlying_2_3 == null) {
+ throw new NotSupported("Underlying driver version < 2.3");
+ }
+ return mUnderlying_2_3;
+ }
+
+ /**
+ * A checked exception representing the requested interface version not being supported.
+ * At the public interface layer, use {@link #throwAsRecoverableException()} to propagate it to
+ * the caller if the request cannot be fulfilled.
+ */
+ private static class NotSupported extends Exception {
+ NotSupported(String message) {
+ super(message);
+ }
+
+ /**
+ * Throw this as a recoverable exception.
+ *
+ * @return Never actually returns anything. Always throws. Used so that caller can write
+ * throw e.throwAsRecoverableException().
+ */
+ RecoverableException throwAsRecoverableException() {
+ throw new RecoverableException(Status.OPERATION_NOT_SUPPORTED, getMessage());
+ }
+ }
+
+ private static class SoundTriggerCallback extends
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.Stub {
+ private final @NonNull
+ Callback mDelegate;
+
+ private SoundTriggerCallback(
+ @NonNull Callback delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ @Override
+ public void recognitionCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie) {
+ mDelegate.recognitionCallback(event, cookie);
+ }
+
+ @Override
+ public void phraseRecognitionCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie) {
+ mDelegate.phraseRecognitionCallback(event, cookie);
+ }
+
+ @Override
+ public void soundModelCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent event,
+ int cookie) {
+ // Nobody cares.
+ }
+
+ @Override
+ public void recognitionCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+ Hw2CompatUtil.convertRecognitionEvent_2_0_to_2_1(event);
+ mDelegate.recognitionCallback(event_2_1, cookie);
+ }
+
+ @Override
+ public void phraseRecognitionCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+ event_2_1 = Hw2CompatUtil.convertPhraseRecognitionEvent_2_0_to_2_1(event);
+ mDelegate.phraseRecognitionCallback(event_2_1, cookie);
+ }
+
+ @Override
+ public void soundModelCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent event,
+ int cookie) {
+ // Nobody cares.
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
new file mode 100644
index 0000000..9d51b65
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.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.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an implementation of the ISoundTriggerMiddlewareService interface.
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle invalid
+ * usage, and such usage will result in undefined behavior. If this service is to be offered to an
+ * untrusted client, it must be wrapped with input and state validation.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status}
+ * constants. Any other exception thrown should be regarded as a bug in the implementation or one
+ * of its dependencies (assuming correct usage).
+ * <li>The implementation is designed for testibility by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies on
+ * Android runtime.
+ * <li>The implementation is thread-safe.
+ * </ul>
+ *
+ * @hide
+ */
+public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService {
+ static private final String TAG = "SoundTriggerMiddlewareImpl";
+ private final SoundTriggerModule[] mModules;
+
+ /**
+ * Interface to the audio system, which can allocate capture session handles.
+ * SoundTrigger uses those sessions in order to associate a recognition session with an optional
+ * capture from the same device that triggered the recognition.
+ */
+ public static abstract class AudioSessionProvider {
+ public static final class AudioSession {
+ final int mSessionHandle;
+ final int mIoHandle;
+ final int mDeviceHandle;
+
+ AudioSession(int sessionHandle, int ioHandle, int deviceHandle) {
+ mSessionHandle = sessionHandle;
+ mIoHandle = ioHandle;
+ mDeviceHandle = deviceHandle;
+ }
+ }
+
+ public abstract AudioSession acquireSession();
+
+ public abstract void releaseSession(int sessionHandle);
+ }
+
+ /**
+ * Most generic constructor - gets an array of HAL driver instances.
+ */
+ public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+ @NonNull AudioSessionProvider audioSessionProvider) {
+ List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+
+ for (int i = 0; i < halServices.length; ++i) {
+ ISoundTriggerHw service = halServices[i];
+ try {
+ modules.add(new SoundTriggerModule(service, audioSessionProvider));
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+ }
+ }
+
+ mModules = modules.toArray(new SoundTriggerModule[modules.size()]);
+ }
+
+ /**
+ * Convenience constructor - gets a single HAL driver instance.
+ */
+ public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+ @NonNull AudioSessionProvider audioSessionProvider) {
+ this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ SoundTriggerModuleDescriptor[] result = new SoundTriggerModuleDescriptor[mModules.length];
+
+ for (int i = 0; i < mModules.length; ++i) {
+ SoundTriggerModuleDescriptor desc = new SoundTriggerModuleDescriptor();
+ desc.handle = i;
+ desc.properties = mModules[i].getProperties();
+ result[i] = desc;
+ }
+ return result;
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+ return mModules[handle].attach(callback);
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ for (SoundTriggerModule module : mModules) {
+ module.setExternalCaptureState(active);
+ }
+ }
+
+ @Override
+ public @NonNull
+ IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not inteded to be used directly with Binder.");
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
new file mode 100644
index 0000000..a7cfe10
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -0,0 +1,709 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+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.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.SystemService;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes
+ * it as a Binder service and enforces permissions and correct usage by the client, as well as makes
+ * sure that exceptions representing a server malfunction do not get sent to the client.
+ * <p>
+ * This is intended to extract the non-business logic out of the underlying implementation and thus
+ * make it easier to maintain each one of those separate aspects. A design trade-off is being made
+ * here, in that this class would need to essentially eavesdrop on all the client-server
+ * communication and retain all state known to the client, while the client doesn't necessarily care
+ * about all of it, and while the server has its own representation of this information. However,
+ * in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
+ * There is also some additional cost in employing a simplistic locking mechanism here, but
+ * following the same line of reasoning, the benefits in code simplicity outweigh it.
+ * <p>
+ * Every public method in this class, overriding an interface method, must follow the following
+ * pattern:
+ * <code><pre>
+ * @Override public T method(S arg) {
+ * // Permission check.
+ * checkPermissions();
+ * // Input validation.
+ * ValidationUtil.validateS(arg);
+ * synchronized (this) {
+ * // State validation.
+ * if (...state is not valid for this call...) {
+ * throw new IllegalStateException("State is invalid because...");
+ * }
+ * // From here on, every exception isn't client's fault.
+ * try {
+ * T result = mDelegate.method(arg);
+ * // Update state.;
+ * ...
+ * return result;
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * }
+ * }
+ * </pre></code>
+ * Following this patterns ensures a consistent and rigorous handling of all aspects associated
+ * with client-server separation.
+ * <p>
+ * <b>Exception handling approach:</b><br>
+ * We make sure all client faults (permissions, argument and state validation) happen first, and
+ * would throw {@link SecurityException}, {@link IllegalArgumentException}/
+ * {@link NullPointerException} or {@link
+ * IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
+ * will get sent back to the client.<br>
+ * Once this is done, any subsequent fault is considered a server fault. Only {@link
+ * RecoverableException}s thrown by the implementation are special-cased: they would get sent back
+ * to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
+ * exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
+ * that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
+ * exception handler on the server side, typically resulting in rebooting the server.
+ * <p>
+ * <b>Exposing this service as a System Service:</b><br>
+ * Insert this line into {@link com.android.server.SystemServer}:
+ * <code><pre>
+ * mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ * </pre></code>
+ *
+ * {@hide}
+ */
+public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
+ static private final String TAG = "SoundTriggerMiddlewareService";
+
+ final ISoundTriggerMiddlewareService mDelegate;
+ final Context mContext;
+ Set<Integer> mModuleHandles;
+
+ /**
+ * Constructor for internal use only. Could be exposed for testing purposes in the future.
+ * Users should access this class via {@link Lifecycle}.
+ */
+ private SoundTriggerMiddlewareService(
+ @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
+ mDelegate = delegate;
+ mContext = context;
+ }
+
+ /**
+ * Generic exception handling for exceptions thrown by the underlying implementation.
+ *
+ * Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
+ * by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
+ * (<b>not</b> passed by Binder to the caller).
+ * <p>
+ * Typical usage:
+ * <code><pre>
+ * try {
+ * ... Do server operations ...
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * </pre></code>
+ */
+ private static @NonNull
+ RuntimeException handleException(@NonNull Exception e) {
+ if (e instanceof RecoverableException) {
+ throw new ServiceSpecificException(((RecoverableException) e).errorCode,
+ e.getMessage());
+ }
+ throw new InternalServerError(e);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
+ mModuleHandles = new HashSet<>(result.length);
+ for (SoundTriggerModuleDescriptor desc : result) {
+ mModuleHandles.add(desc.handle);
+ }
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(callback.asBinder());
+
+ synchronized (this) {
+ // State validation.
+ if (mModuleHandles == null) {
+ throw new IllegalStateException(
+ "Client must call listModules() prior to attaching.");
+ }
+ if (!mModuleHandles.contains(handle)) {
+ throw new IllegalArgumentException("Invalid handle: " + handle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModuleService moduleService = new ModuleService(callback);
+ moduleService.attach(mDelegate.attach(handle, moduleService));
+ return moduleService;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ // Permission check.
+ checkPreemptPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setExternalCaptureState(active);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller 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.");
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller 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.");
+ }
+
+ /** State of a sound model. */
+ static class ModelState {
+ /** Activity state of a sound model. */
+ enum Activity {
+ /** Model is loaded, recognition is inactive. */
+ LOADED,
+ /** Model is loaded, recognition is active. */
+ ACTIVE
+ }
+
+ /** Activity state. */
+ public Activity activityState = Activity.LOADED;
+
+ /**
+ * A map of known parameter support. A missing key means we don't know yet whether the
+ * parameter is supported. A null value means it is known to not be supported. A non-null
+ * value indicates the valid value range.
+ */
+ private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
+
+ /**
+ * Check that the given parameter is known to be supported for this model.
+ *
+ * @param modelParam The parameter key.
+ */
+ public void checkSupported(int modelParam) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ }
+
+ /**
+ * Check that the given parameter is known to be supported for this model and that the given
+ * value is a valid value for it.
+ *
+ * @param modelParam The parameter key.
+ * @param value The value.
+ */
+ public void checkSupported(int modelParam, int value) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
+ "value");
+ }
+
+ /**
+ * Update support state for the given parameter for this model.
+ *
+ * @param modelParam The parameter key.
+ * @param range The parameter value range, or null if not supported.
+ */
+ public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+ parameterSupport.put(modelParam, range);
+ }
+ }
+
+ /**
+ * 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];
+ }
+
+ mService = new SoundTriggerMiddlewareService(
+ new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
+ getContext());
+ publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+ }
+ }
+
+ /**
+ * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
+ * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
+ */
+ private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
+ DeathRecipient {
+ private final ISoundTriggerCallback mCallback;
+ private ISoundTriggerModule mDelegate;
+ private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+
+ ModuleService(@NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void attach(@NonNull ISoundTriggerModule delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateGenericModel(model);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validatePhraseModel(model);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadPhraseModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for unloading: " + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.unloadModel(modelHandle);
+ mLoadedModels.remove(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateRecognitionConfig(config);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for starting recognition: "
+ + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.startRecognition(modelHandle, config);
+ modelState.activityState = ModelState.Activity.ACTIVE;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // stopRecognition is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.stopRecognition(modelHandle);
+ modelState.activityState = ModelState.Activity.LOADED;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // forceRecognitionEvent is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam, value);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ return mDelegate.getModelParameter(modelHandle, modelParam);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
+ modelParam);
+ modelState.updateParameterSupport(modelParam, result);
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void detach() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has already been detached.");
+ }
+ if (!mLoadedModels.isEmpty()) {
+ throw new IllegalStateException("Cannot detach while models are loaded.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ private void detachInternal() {
+ try {
+ mDelegate.detach();
+ mDelegate = null;
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Callbacks
+
+ @Override
+ public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
+ synchronized (this) {
+ if (event.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onRecognition(modelHandle, event);
+ } 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);
+ }
+ }
+ }
+
+ @Override
+ public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
+ synchronized (this) {
+ if (event.common.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onPhraseRecognition(modelHandle, event);
+ } 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);
+ }
+ }
+ }
+
+ @Override
+ public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ 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);
+ }
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ // This is called whenever our client process dies.
+ synchronized (this) {
+ try {
+ // Gracefully stop all active recognitions and unload the models.
+ for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
+ if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
+ mDelegate.stopRecognition(entry.getKey());
+ }
+ mDelegate.unloadModel(entry.getKey());
+ }
+ // Detach.
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(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
new file mode 100644
index 0000000..81789e1
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -0,0 +1,545 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is an implementation of a single module of the ISoundTriggerMiddlewareService interface,
+ * exposing itself through the {@link ISoundTriggerModule} interface, possibly to multiple separate
+ * clients.
+ * <p>
+ * Typical usage is to query the module capabilities using {@link #getProperties()} and then to use
+ * the module through an {@link ISoundTriggerModule} instance, obtained via {@link
+ * #attach(ISoundTriggerCallback)}. Every such interface is its own session and state is not shared
+ * between sessions (i.e. cannot use a handle obtained from one session through another).
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle
+ * invalid usage, and such usage will result in undefined behavior. If this service is to be
+ * offered to an untrusted client, it must be wrapped with input and state validation.
+ * <li>The underlying driver is assumed to be correct. This implementation does not attempt to
+ * 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>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().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status} constants. Any other exception
+ * thrown should be regarded as a bug in the implementation or one of its dependencies
+ * (assuming correct usage).
+ * <li>The implementation is designed for testability by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies
+ * on Android runtime.
+ * <li>The implementation is thread-safe. This is achieved by a simplistic model, where all entry-
+ * points (both client API and driver callbacks) obtain a lock on the SoundTriggerModule instance
+ * for their entire scope. Any other method can be assumed to be running with the lock already
+ * obtained, so no further locking should be done. While this is not necessarily the most efficient
+ * synchronization strategy, it is very easy to reason about and this code is likely not on any
+ * performance-critical
+ * path.
+ * </ul>
+ *
+ * @hide
+ */
+class SoundTriggerModule {
+ static private final String TAG = "SoundTriggerModule";
+ @NonNull private final ISoundTriggerHw2 mHalService;
+ @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
+ private final Set<Session> mActiveSessions = new HashSet<>();
+ private int mNumLoadedModels = 0;
+ private SoundTriggerModuleProperties mProperties = null;
+ private boolean mRecognitionAvailable;
+
+ /**
+ * Ctor.
+ *
+ * @param halService The underlying HAL driver.
+ */
+ SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+ @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
+ assert halService != null;
+ mHalService = new SoundTriggerHw2Compat(halService);
+ mAudioSessionProvider = audioSessionProvider;
+ mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
+
+ // We conservatively assume that external capture is active until explicitly told otherwise.
+ mRecognitionAvailable = mProperties.concurrentCapture;
+ }
+
+ /**
+ * Establish a client session with this module.
+ *
+ * This module may be shared by multiple clients, each will get its own session. While resources
+ * are shared between the clients, each session has its own state and data should not be shared
+ * across sessions.
+ *
+ * @param callback The client callback, which will be used for all messages. This is a oneway
+ * callback, so will never block, throw an unchecked exception or return a
+ * value.
+ * @return The interface through which this module can be controlled.
+ */
+ synchronized @NonNull
+ Session attach(@NonNull ISoundTriggerCallback callback) {
+ Log.d(TAG, "attach()");
+ Session session = new Session(callback);
+ mActiveSessions.add(session);
+ return session;
+ }
+
+ /**
+ * Query the module's properties.
+ *
+ * @return The properties structure.
+ */
+ synchronized @NonNull
+ SoundTriggerModuleProperties getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Notify the module that external capture has started / finished, using the same input device
+ * used for recognition.
+ * If the underlying driver does not support recognition while capturing, capture will be
+ * aborted, and the recognition callback will receive and abort event. In addition, all active
+ * clients will be notified of the change in state.
+ *
+ * @param active true iff external capture is active.
+ */
+ 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;
+ }
+ mRecognitionAvailable = !active;
+ if (!mRecognitionAvailable) {
+ // Our module does not support recognition while a capture is active -
+ // need to abort all active recognitions.
+ for (Session session : mActiveSessions) {
+ session.abortActiveRecognitions();
+ }
+ }
+ for (Session session : mActiveSessions) {
+ session.notifyRecognitionAvailability();
+ }
+ }
+
+ /**
+ * Remove session from the list of active sessions.
+ *
+ * @param session The session to remove.
+ */
+ private void removeSession(@NonNull Session session) {
+ mActiveSessions.remove(session);
+ }
+
+ /** State of a single sound model. */
+ private enum ModelState {
+ /** Initial state, until load() is called. */
+ INIT,
+ /** Model is loaded, but recognition is not active. */
+ LOADED,
+ /** Model is loaded and recognition is active. */
+ ACTIVE
+ }
+
+ /**
+ * A single client session with this module.
+ *
+ * This is the main interface used to interact with this module.
+ */
+ private class Session implements ISoundTriggerModule {
+ private ISoundTriggerCallback mCallback;
+ private Map<Integer, Model> mLoadedModels = new HashMap<>();
+
+ /**
+ * Ctor.
+ *
+ * @param callback The client callback interface.
+ */
+ private Session(@NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ notifyRecognitionAvailability();
+ }
+
+ @Override
+ public void detach() {
+ Log.d(TAG, "detach()");
+ synchronized (SoundTriggerModule.this) {
+ removeSession(this);
+ }
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ Log.d(TAG, String.format("loadModel(model=%s)", model));
+ synchronized (SoundTriggerModule.this) {
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model);
+ ++mNumLoadedModels;
+ return result;
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
+ synchronized (SoundTriggerModule.this) {
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model);
+ ++mNumLoadedModels;
+ Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+ return result;
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).unload();
+ --mNumLoadedModels;
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ Log.d(TAG,
+ String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).startRecognition(config);
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ Log.d(TAG, String.format("stopRecognition(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).stopRecognition();
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).forceRecognitionEvent();
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value)
+ throws RemoteException {
+ Log.d(TAG,
+ String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
+ modelParam, value));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).setParameter(modelParam, value);
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
+ modelParam));
+ synchronized (SoundTriggerModule.this) {
+ return mLoadedModels.get(modelHandle).getParameter(modelParam);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
+ modelParam));
+ synchronized (SoundTriggerModule.this) {
+ return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
+ }
+ }
+
+ /**
+ * Abort all currently active recognitions.
+ */
+ private void abortActiveRecognitions() {
+ for (Model model : mLoadedModels.values()) {
+ model.abortActiveRecognition();
+ }
+ }
+
+ private void notifyRecognitionAvailability() {
+ try {
+ mCallback.onRecognitionAvailabilityChange(mRecognitionAvailable);
+ } 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);
+ }
+ }
+
+ @Override
+ public @NonNull
+ IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not intended to be used directly with Binder.");
+ }
+
+ /**
+ * A single sound model in the system.
+ *
+ * All model-based operations are delegated to this class and implemented here.
+ */
+ private class Model implements ISoundTriggerHw2.Callback {
+ public int mHandle;
+ private ModelState mState = ModelState.INIT;
+ private int mModelType = SoundModelType.UNKNOWN;
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;
+
+ private @NonNull
+ ModelState getState() {
+ return mState;
+ }
+
+ private void setState(@NonNull ModelState state) {
+ mState = state;
+ 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);
+
+ mSession = mAudioSessionProvider.acquireSession();
+ try {
+ mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
+ } catch (Exception e) {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ throw e;
+ }
+
+ setState(ModelState.LOADED);
+ mLoadedModels.put(mHandle, this);
+ return mHandle;
+ }
+
+ private int load(@NonNull PhraseSoundModel model) {
+ mModelType = model.common.type;
+ ISoundTriggerHw.PhraseSoundModel hidlModel =
+ ConversionUtil.aidl2hidlPhraseSoundModel(model);
+
+ mSession = mAudioSessionProvider.acquireSession();
+ try {
+ mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
+ } catch (Exception e) {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ throw e;
+ }
+
+ setState(ModelState.LOADED);
+ mLoadedModels.put(mHandle, this);
+ return mHandle;
+ }
+
+ private void unload() {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ mHalService.unloadSoundModel(mHandle);
+ mLoadedModels.remove(mHandle);
+ }
+
+ private void startRecognition(@NonNull RecognitionConfig config) {
+ if (!mRecognitionAvailable) {
+ // Recognition is unavailable - send an abort event immediately.
+ notifyAbort();
+ return;
+ }
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+ ConversionUtil.aidl2hidlRecognitionConfig(config);
+ hidlConfig.header.captureDevice = mSession.mDeviceHandle;
+ hidlConfig.header.captureHandle = mSession.mIoHandle;
+ mHalService.startRecognition(mHandle, hidlConfig, this, 0);
+ setState(ModelState.ACTIVE);
+ }
+
+ private void stopRecognition() {
+ if (getState() == ModelState.LOADED) {
+ // This call is idempotent in order to avoid races.
+ return;
+ }
+ mHalService.stopRecognition(mHandle);
+ setState(ModelState.LOADED);
+ }
+
+ /** Request a forced recognition event. Will do nothing if recognition is inactive. */
+ private void forceRecognitionEvent() {
+ if (getState() != ModelState.ACTIVE) {
+ // This call is idempotent in order to avoid races.
+ return;
+ }
+ mHalService.getModelState(mHandle);
+ }
+
+
+ private void setParameter(int modelParam, int value) {
+ mHalService.setModelParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam), value);
+ }
+
+ private int getParameter(int modelParam) {
+ return mHalService.getModelParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam));
+ }
+
+ @Nullable
+ private ModelParameterRange queryModelParameterSupport(int modelParam) {
+ return ConversionUtil.hidl2aidlModelParameterRange(
+ mHalService.queryParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam)));
+ }
+
+ /** Abort the recognition, if active. */
+ private void abortActiveRecognition() {
+ // If we're inactive, do nothing.
+ if (getState() != ModelState.ACTIVE) {
+ return;
+ }
+ // Stop recognition.
+ stopRecognition();
+
+ // Notify the client that recognition has been aborted.
+ notifyAbort();
+ }
+
+ /** Notify the client that recognition has been aborted. */
+ private void notifyAbort() {
+ try {
+ switch (mModelType) {
+ case SoundModelType.GENERIC: {
+ android.media.soundtrigger_middleware.RecognitionEvent event =
+ new android.media.soundtrigger_middleware.RecognitionEvent();
+ event.status =
+ android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+ mCallback.onRecognition(mHandle, event);
+ }
+ break;
+
+ case SoundModelType.KEYPHRASE: {
+ android.media.soundtrigger_middleware.PhraseRecognitionEvent event =
+ new android.media.soundtrigger_middleware.PhraseRecognitionEvent();
+ event.common =
+ new android.media.soundtrigger_middleware.RecognitionEvent();
+ event.common.status =
+ android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+ mCallback.onPhraseRecognition(mHandle, event);
+ }
+ break;
+
+ default:
+ Log.e(TAG, "Unknown model type: " + mModelType);
+
+ }
+ } 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);
+ }
+ }
+
+ @Override
+ public void recognitionCallback(
+ @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
+ int cookie) {
+ Log.d(TAG, String.format("recognitionCallback_2_1(event=%s, cookie=%d)",
+ recognitionEvent, cookie));
+ synchronized (SoundTriggerModule.this) {
+ android.media.soundtrigger_middleware.RecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
+ aidlEvent.captureSession = mSession.mSessionHandle;
+ try {
+ mCallback.onRecognition(mHandle, aidlEvent);
+ } 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);
+ }
+ if (aidlEvent.status
+ != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+ setState(ModelState.LOADED);
+ }
+ }
+ }
+
+ @Override
+ public void phraseRecognitionCallback(
+ @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
+ int cookie) {
+ Log.d(TAG, String.format("phraseRecognitionCallback_2_1(event=%s, cookie=%d)",
+ phraseRecognitionEvent, cookie));
+ synchronized (SoundTriggerModule.this) {
+ android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
+ aidlEvent.common.captureSession = mSession.mSessionHandle;
+ try {
+ mCallback.onPhraseRecognition(mHandle, aidlEvent);
+ } 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);
+ }
+ if (aidlEvent.common.status
+ != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+ setState(ModelState.LOADED);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
new file mode 100644
index 0000000..9ed894b
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.soundtrigger_middleware"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
new file mode 100644
index 0000000..80f69d0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
@@ -0,0 +1,44 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for representing UUIDs as strings.
+ *
+ * @hide
+ */
+public class UuidUtil {
+ /**
+ * Regex pattern that can be used to validate / extract the various fields of a string-formatted
+ * UUID.
+ */
+ static final Pattern PATTERN = Pattern.compile("^([a-fA-F0-9]{8})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})$");
+
+ /** Printf-style pattern for formatting the various fields of a UUID as a string. */
+ static final String FORMAT = "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x";
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
new file mode 100644
index 0000000..4898e6b
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
@@ -0,0 +1,117 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for asserting the validity of various data types used by this module.
+ * Each of the methods below would throw an {@link IllegalArgumentException} if its input is
+ * invalid. The input's validity is determined irrespective of any context. In cases where the valid
+ * value space is further limited by state, it is the caller's responsibility to assert.
+ *
+ * @hide
+ */
+public class ValidationUtil {
+ static void validateUuid(@Nullable String uuid) {
+ Preconditions.checkNotNull(uuid);
+ Matcher matcher = UuidUtil.PATTERN.matcher(uuid);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(
+ "Illegal format for UUID: " + uuid);
+ }
+ }
+
+ static void validateGenericModel(@Nullable SoundModel model) {
+ validateModel(model, SoundModelType.GENERIC);
+ }
+
+ static void validateModel(@Nullable SoundModel model, int expectedType) {
+ Preconditions.checkNotNull(model);
+ if (model.type != expectedType) {
+ throw new IllegalArgumentException("Invalid type");
+ }
+ validateUuid(model.uuid);
+ validateUuid(model.vendorUuid);
+ Preconditions.checkNotNull(model.data);
+ }
+
+ static void validatePhraseModel(@Nullable PhraseSoundModel model) {
+ Preconditions.checkNotNull(model);
+ validateModel(model.common, SoundModelType.KEYPHRASE);
+ Preconditions.checkNotNull(model.phrases);
+ for (Phrase phrase : model.phrases) {
+ Preconditions.checkNotNull(phrase);
+ if ((phrase.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+ | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+ throw new IllegalArgumentException("Invalid recognitionModes");
+ }
+ Preconditions.checkNotNull(phrase.users);
+ Preconditions.checkNotNull(phrase.locale);
+ Preconditions.checkNotNull(phrase.text);
+ }
+ }
+
+ static void validateRecognitionConfig(@Nullable RecognitionConfig config) {
+ Preconditions.checkNotNull(config);
+ Preconditions.checkNotNull(config.phraseRecognitionExtras);
+ for (PhraseRecognitionExtra extra : config.phraseRecognitionExtras) {
+ Preconditions.checkNotNull(extra);
+ if ((extra.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+ | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+ throw new IllegalArgumentException("Invalid recognitionModes");
+ }
+ if (extra.confidenceLevel < 0 || extra.confidenceLevel > 100) {
+ throw new IllegalArgumentException("Invalid confidenceLevel");
+ }
+ Preconditions.checkNotNull(extra.levels);
+ for (ConfidenceLevel level : extra.levels) {
+ Preconditions.checkNotNull(level);
+ if (level.levelPercent < 0 || level.levelPercent > 100) {
+ throw new IllegalArgumentException("Invalid confidenceLevel");
+ }
+ }
+ }
+ Preconditions.checkNotNull(config.data);
+ }
+
+ static void validateModelParameter(int modelParam) {
+ switch (modelParam) {
+ case ModelParameter.THRESHOLD_FACTOR:
+ return;
+
+ default:
+ throw new IllegalArgumentException("Invalid model parameter");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index d76836f..aa3ab63 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -43,7 +43,6 @@
import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
-import java.io.IOException;
/**
* Controls storage sessions for users initiated by the {@link StorageManagerService}.
@@ -101,18 +100,10 @@
connection = new StorageUserConnection(mContext, userId, this);
mConnections.put(userId, connection);
}
- Slog.i(TAG, "Creating session with id: " + sessionId);
- connection.createSession(sessionId, new ParcelFileDescriptor(deviceFd),
+ Slog.i(TAG, "Creating and starting session with id: " + sessionId);
+ connection.startSession(sessionId, new ParcelFileDescriptor(deviceFd),
vol.getPath().getPath(), vol.getInternalPath().getPath());
}
-
- // At boot, a volume can be mounted before user is unlocked, in that case, we create it
- // above and save it so that we can restart all sessions when the user is unlocked
- if (mExternalStorageServiceComponent != null) {
- connection.startSession(sessionId);
- } else {
- Slog.i(TAG, "Controller not initialised, session not started " + sessionId);
- }
}
/**
@@ -179,23 +170,32 @@
* a session will be ignored.
*/
public void onUnlockUser(int userId) throws ExternalStorageServiceException {
+ Slog.i(TAG, "On user unlock " + userId);
+ if (shouldHandle(null) && userId == 0) {
+ initExternalStorageServiceComponent();
+ }
+ }
+
+ /**
+ * Called when a user is in the process is being stopped.
+ *
+ * Does nothing if {@link #shouldHandle} is {@code false}
+ *
+ * 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 {
if (!shouldHandle(null)) {
return;
}
-
- Slog.i(TAG, "On user unlock " + userId);
- if (userId == 0) {
- initExternalStorageServiceComponent();
- }
-
StorageUserConnection connection = null;
synchronized (mLock) {
connection = mConnections.get(userId);
}
if (connection != null) {
- Slog.i(TAG, "Restarting all sessions for user: " + userId);
- connection.startAllSessions();
+ Slog.i(TAG, "Removing all sessions for user: " + userId);
+ connection.removeAllSessions();
} else {
Slog.w(TAG, "No connection found for user: " + userId);
}
@@ -308,34 +308,6 @@
}
/**
- * Throws an {@link IllegalStateException} if {@code path} is not ready to be accessed by
- * {@code userId}.
- */
- // TODO(b/144332951): This is not used because it is racy. Right after checking a path
- // we can call into vold with that path and the FUSE daemon can go down. Improve or remove
- public void checkPathReadyForUser(int userId, String path) {
- if (!mIsFuseEnabled) {
- return;
- }
-
- if (mIsResetting) {
- throw new IllegalStateException("Connection resetting for user " + userId
- + " with path " + path);
- }
-
- StorageUserConnection connection = null;
- synchronized (mLock) {
- connection = mConnections.get(userId);
- }
-
- if (connection == null) {
- throw new IllegalStateException("Connection not ready for user " + userId
- + " with path " + path);
- }
- connection.checkPathReady(path);
- }
-
- /**
* Returns {@code true} if {@code vol} is an emulated or public volume,
* {@code false} otherwise
**/
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index 7c47730..10514ad 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -34,6 +34,7 @@
import android.os.ParcelableException;
import android.os.RemoteCallback;
import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
import android.service.storage.ExternalStorageService;
import android.service.storage.IExternalStorageService;
import android.text.TextUtils;
@@ -41,6 +42,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
import java.io.IOException;
import java.util.HashMap;
@@ -73,42 +75,25 @@
}
/**
- * Creates and stores a storage {@link Session}.
+ * Creates and starts a storage {@link Session}.
*
* They must also be cleaned up with {@link #removeSession}.
*
* @throws IllegalArgumentException if a {@code Session} with {@code sessionId} already exists
*/
- public void createSession(String sessionId, ParcelFileDescriptor pfd, String upperPath,
- String lowerPath) {
+ public void startSession(String sessionId, ParcelFileDescriptor pfd, String upperPath,
+ String lowerPath) throws ExternalStorageServiceException {
Preconditions.checkNotNull(sessionId);
Preconditions.checkNotNull(pfd);
Preconditions.checkNotNull(upperPath);
Preconditions.checkNotNull(lowerPath);
- synchronized (mLock) {
- Preconditions.checkArgument(!mSessions.containsKey(sessionId));
- mSessions.put(sessionId, new Session(sessionId, pfd, upperPath, lowerPath));
- }
- }
-
- /**
- * Starts an already created storage {@link Session} for {@code sessionId}.
- *
- * It is safe to call this multiple times, however if the session is already started,
- * subsequent calls will be ignored.
- *
- * @throws ExternalStorageServiceException if the session failed to start
- **/
- public void startSession(String sessionId) throws ExternalStorageServiceException {
- Session session;
- synchronized (mLock) {
- session = mSessions.get(sessionId);
- }
-
prepareRemote();
synchronized (mLock) {
- mActiveConnection.startSessionLocked(session);
+ Preconditions.checkArgument(!mSessions.containsKey(sessionId));
+ Session session = new Session(sessionId, upperPath, lowerPath);
+ mSessions.put(sessionId, session);
+ mActiveConnection.startSessionLocked(session, pfd);
}
}
@@ -121,16 +106,10 @@
**/
public Session removeSession(String sessionId) {
synchronized (mLock) {
- Session session = mSessions.remove(sessionId);
- if (session != null) {
- session.close();
- return session;
- }
- return null;
+ return mSessions.remove(sessionId);
}
}
-
/**
* Removes a session and waits for exit
*
@@ -150,26 +129,30 @@
}
}
- /** Starts all available sessions for a user without blocking. Any failures will be ignored. */
- public void startAllSessions() {
- try {
- prepareRemote();
- } catch (ExternalStorageServiceException e) {
- Slog.e(TAG, "Failed to start all sessions for user: " + mUserId, e);
- return;
- }
-
+ /** Restarts all available sessions for a user without blocking.
+ *
+ * Any failures will be ignored.
+ **/
+ public void resetUserSessions() {
synchronized (mLock) {
- Slog.i(TAG, "Starting " + mSessions.size() + " sessions for user: " + mUserId + "...");
- for (Session session : mSessions.values()) {
- try {
- mActiveConnection.startSessionLocked(session);
- } catch (IllegalStateException | ExternalStorageServiceException e) {
- // TODO: Don't crash process? We could get into process crash loop
- Slog.e(TAG, "Failed to start " + session, e);
- }
+ if (mSessions.isEmpty()) {
+ // Nothing to reset if we have no sessions to restart; we typically
+ // hit this path if the user was consciously shut down.
+ return;
}
}
+ StorageManagerInternal sm = LocalServices.getService(StorageManagerInternal.class);
+ sm.resetUser(mUserId);
+ }
+
+ /**
+ * Removes all sessions, without waiting.
+ */
+ public void removeAllSessions() {
+ synchronized (mLock) {
+ Slog.i(TAG, "Removing " + mSessions.size() + " sessions for user: " + mUserId + "...");
+ mSessions.clear();
+ }
}
/**
@@ -180,20 +163,6 @@
mActiveConnection.close();
}
- /** Throws an {@link IllegalArgumentException} if {@code path} is not ready for access */
- public void checkPathReady(String path) {
- synchronized (mLock) {
- for (Session session : mSessions.values()) {
- if (session.upperPath != null && path.startsWith(session.upperPath)) {
- if (mActiveConnection.isActiveLocked(session)) {
- return;
- }
- }
- }
- throw new IllegalStateException("Path not ready " + path);
- }
- }
-
/** Returns all created sessions. */
public Set<String> getAllSessionIds() {
synchronized (mLock) {
@@ -269,26 +238,37 @@
return true;
}
- public void startSessionLocked(Session session) throws ExternalStorageServiceException {
+ public void startSessionLocked(Session session, ParcelFileDescriptor fd)
+ throws ExternalStorageServiceException {
if (!isActiveLocked(session)) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ // ignore
+ }
return;
}
CountDownLatch latch = new CountDownLatch(1);
- try (ParcelFileDescriptor dupedPfd = session.pfd.dup()) {
+ try {
mRemote.startSession(session.sessionId,
FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE,
- dupedPfd, session.upperPath, session.lowerPath, new RemoteCallback(result ->
+ fd, session.upperPath, session.lowerPath, new RemoteCallback(result ->
setResultLocked(latch, result)));
waitForLatch(latch, "start_session " + session);
maybeThrowExceptionLocked();
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to start session: " + session, e);
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ // Ignore
+ }
}
}
public void endSessionLocked(Session session) throws ExternalStorageServiceException {
- session.close();
if (!isActiveLocked(session)) {
// Nothing to end, not started yet
return;
@@ -390,7 +370,7 @@
// will be called for any required mounts.
// Notify StorageManagerService so it can restart all necessary sessions
close();
- new Thread(StorageUserConnection.this::startAllSessions).start();
+ resetUserSessions();
}
};
}
@@ -411,29 +391,18 @@
}
}
- private static final class Session implements AutoCloseable {
+ private static final class Session {
public final String sessionId;
- public final ParcelFileDescriptor pfd;
public final String lowerPath;
public final String upperPath;
- Session(String sessionId, ParcelFileDescriptor pfd, String upperPath, String lowerPath) {
+ Session(String sessionId, String upperPath, String lowerPath) {
this.sessionId = sessionId;
- this.pfd = pfd;
this.upperPath = upperPath;
this.lowerPath = lowerPath;
}
@Override
- public void close() {
- try {
- pfd.close();
- } catch (IOException e) {
- Slog.i(TAG, "Failed to close session: " + this);
- }
- }
-
- @Override
public String toString() {
return "[SessionId: " + sessionId + ". UpperPath: " + upperPath + ". LowerPath: "
+ lowerPath + "]";
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 7d905ba..6a986b9 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -24,15 +24,12 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.service.textclassifier.ITextClassifierCallback;
import android.service.textclassifier.ITextClassifierService;
import android.service.textclassifier.TextClassifierService;
@@ -41,7 +38,6 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.textclassifier.ConfigParser;
import android.view.textclassifier.ConversationActions;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
@@ -143,7 +139,7 @@
private TextClassificationManagerService(Context context) {
mContext = Preconditions.checkNotNull(context);
mLock = new Object();
- mSettingsListener = new TextClassifierSettingsListener(mContext, this);
+ mSettingsListener = new TextClassifierSettingsListener(mContext);
}
private void startListenSettings() {
@@ -171,10 +167,8 @@
Slog.d(LOG_TAG, "Unable to bind TextClassifierService at suggestSelection.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "suggestSelection.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
+ "suggestSelection")) {
return;
}
userState.mService.onSuggestSelection(sessionId, request, callback);
@@ -203,10 +197,7 @@
Slog.d(LOG_TAG, "Unable to bind TextClassifierService at classifyText.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "classifyText.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(), "classifyText")) {
return;
}
userState.mService.onClassifyText(sessionId, request, callback);
@@ -235,10 +226,8 @@
Slog.d(LOG_TAG, "Unable to bind TextClassifierService at generateLinks.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "generateLinks.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
+ "generateLinks")) {
return;
}
userState.mService.onGenerateLinks(sessionId, request, callback);
@@ -262,10 +251,8 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "selectionEvent.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
+ "selectionEvent")) {
return;
}
userState.mService.onSelectionEvent(sessionId, event);
@@ -293,10 +280,8 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "textClassifierEvent.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
+ "textClassifierEvent")) {
return;
}
userState.mService.onTextClassifierEvent(sessionId, event);
@@ -325,10 +310,8 @@
Slog.d(LOG_TAG, "Unable to bind TextClassifierService at detectLanguage.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "detectLanguage.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
+ "detectLanguage")) {
return;
}
userState.mService.onDetectLanguage(sessionId, request, callback);
@@ -358,10 +341,8 @@
"Unable to bind TextClassifierService at suggestConversationActions.");
callback.onFailure();
} else if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "suggestConversationActions.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
+ "suggestConversationActions")) {
return;
}
userState.mService.onSuggestConversationActions(sessionId, request, callback);
@@ -387,10 +368,8 @@
synchronized (mLock) {
UserState userState = getUserStateLocked(userId);
if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "createTextClassificationSession.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
+ "createTextClassificationSession")) {
return;
}
userState.mService.onCreateTextClassificationSession(
@@ -422,10 +401,8 @@
UserState userState = getUserStateLocked(userId);
if (userState.isBoundLocked()) {
- if (!userState.isRequestAcceptedLocked(Binder.getCallingUid())) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + "destroyTextClassificationSession.");
+ if (!userState.checkRequestAcceptedLocked(Binder.getCallingUid(),
+ "destroyTextClassificationSession")) {
return;
}
userState.mService.onDestroyTextClassificationSession(sessionId);
@@ -488,6 +465,29 @@
}
}
+ private void unbindServiceIfNecessary() {
+ final ComponentName serviceComponentName =
+ TextClassifierService.getServiceComponentName(mContext);
+ if (serviceComponentName == null) {
+ // It should not occur if we had defined default service names in config.xml
+ Slog.w(LOG_TAG, "No default configured system TextClassifierService.");
+ return;
+ }
+ synchronized (mLock) {
+ final int size = mUserStates.size();
+ for (int i = 0; i < size; i++) {
+ UserState userState = mUserStates.valueAt(i);
+ // Only unbind for a new service
+ if (userState.isCurrentlyBoundToComponentLocked(serviceComponentName)) {
+ return;
+ }
+ if (userState.isBoundLocked()) {
+ userState.unbindLocked();
+ }
+ }
+ }
+ }
+
private static final class PendingRequest implements IBinder.DeathRecipient {
private final int mUid;
@@ -582,48 +582,6 @@
}
}
- private TextClassificationConstants getTextClassifierSettings(Context context) {
- synchronized (mLock) {
- if (mSettings == null) {
- mSettings = new TextClassificationConstants(
- () -> Settings.Global.getString(
- context.getContentResolver(),
- Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
- }
- return mSettings;
- }
- }
-
- private void invalidateSettings() {
- synchronized (mLock) {
- mSettings = null;
- }
- }
-
- private void unbindServiceIfNeeded() {
- final ComponentName serviceComponentName =
- TextClassifierService.getServiceComponentName(mContext,
- getTextClassifierSettings(mContext));
- if (serviceComponentName == null) {
- // It should not occur if we had defined default service name in config
- Slog.w(LOG_TAG, "No default configured system TextClassifierService.");
- return;
- }
- synchronized (mLock) {
- final int size = mUserStates.size();
- for (int i = 0; i < size; i++) {
- UserState userState = mUserStates.valueAt(i);
- // Only unbind for a new service
- if (userState.isServiceCurrentBoundLocked(serviceComponentName)) {
- return;
- }
- if (userState.isBoundLocked()) {
- userState.unbindLocked();
- }
- }
- }
- }
-
private final class UserState {
@UserIdInt final int mUserId;
@GuardedBy("mLock")
@@ -635,9 +593,9 @@
@GuardedBy("mLock")
boolean mBinding;
@GuardedBy("mLock")
- ComponentName mBoundServiceComponent = null;
+ ComponentName mBoundComponentName = null;
@GuardedBy("mLock")
- boolean mIsBoundToDefaultService;
+ boolean mBoundToDefaultTrustService;
@GuardedBy("mLock")
int mBoundServiceUid;
@@ -660,10 +618,7 @@
PendingRequest request;
while ((request = mPendingRequests.poll()) != null) {
if (isBoundLocked()) {
- if (!isRequestAcceptedLocked(request.mUid)) {
- Slog.d(LOG_TAG,
- "Only allow to see own content for non-default service at "
- + request.mName);
+ if (!checkRequestAcceptedLocked(request.mUid, request.mName)) {
return;
}
request.mRequest.run();
@@ -687,15 +642,15 @@
}
@GuardedBy("mLock")
- private boolean isServiceCurrentBoundLocked(@NonNull ComponentName componentName) {
- return (mBoundServiceComponent != null
- && mBoundServiceComponent.getPackageName().equals(
+ private boolean isCurrentlyBoundToComponentLocked(@NonNull ComponentName componentName) {
+ return (mBoundComponentName != null
+ && mBoundComponentName.getPackageName().equals(
componentName.getPackageName()));
}
@GuardedBy("mLock")
private void unbindLocked() {
- Slog.d(LOG_TAG, "unbinding to " + mBoundServiceComponent + " for " + mUserId);
+ Slog.v(LOG_TAG, "unbinding from " + mBoundComponentName + " for " + mUserId);
mContext.unbindService(mConnection);
mConnection.cleanupService();
mConnection = null;
@@ -716,8 +671,7 @@
final long identity = Binder.clearCallingIdentity();
try {
final ComponentName componentName =
- TextClassifierService.getServiceComponentName(mContext,
- getTextClassifierSettings(mContext));
+ TextClassifierService.getServiceComponentName(mContext);
if (componentName == null) {
// Might happen if the storage is encrypted and the user is not unlocked
return false;
@@ -742,8 +696,8 @@
pw.printPair("context", mContext);
pw.printPair("userId", mUserId);
synchronized (mLock) {
- pw.printPair("BoundServiceComponent", mBoundServiceComponent);
- pw.printPair("isBoundToDefaultService", mIsBoundToDefaultService);
+ pw.printPair("boundComponentName", mBoundComponentName);
+ pw.printPair("boundToDefaultTrustService", mBoundToDefaultTrustService);
pw.printPair("boundServiceUid", mBoundServiceUid);
pw.printPair("binding", mBinding);
pw.printPair("numberRequests", mPendingRequests.size());
@@ -751,14 +705,17 @@
}
@GuardedBy("mLock")
- private boolean isRequestAcceptedLocked(int requestUid) {
- if (mIsBoundToDefaultService) {
+ private boolean checkRequestAcceptedLocked(int requestUid, @NonNull String methodName) {
+ if (mBoundToDefaultTrustService || (requestUid == mBoundServiceUid)) {
return true;
}
- return (requestUid == mBoundServiceUid);
+ Slog.w(LOG_TAG, String.format(
+ "[%s] Non-default TextClassifierServices may only see text from the same uid.",
+ methodName));
+ return false;
}
- private boolean isDefaultService(@NonNull ComponentName currentService) {
+ private boolean isDefaultTrustService(@NonNull ComponentName currentService) {
final String[] defaultServiceNames =
mContext.getPackageManager().getSystemTextClassifierPackages();
final String servicePackageName = currentService.getPackageName();
@@ -789,16 +746,16 @@
}
@GuardedBy("mLock")
- private void updateServiceInfoLocked(@Nullable ComponentName componentName, int userId) {
- mBoundServiceComponent = componentName;
- mIsBoundToDefaultService = (mBoundServiceComponent != null && isDefaultService(
- mBoundServiceComponent));
- mBoundServiceUid = getServiceUid(mBoundServiceComponent, userId);
+ private void updateServiceInfoLocked(int userId, @Nullable ComponentName componentName) {
+ mBoundComponentName = componentName;
+ mBoundToDefaultTrustService = (mBoundComponentName != null && isDefaultTrustService(
+ mBoundComponentName));
+ mBoundServiceUid = getServiceUid(mBoundComponentName, userId);
}
private final class TextClassifierServiceConnection implements ServiceConnection {
- @UserIdInt final int mUserId;
+ @UserIdInt private final int mUserId;
TextClassifierServiceConnection(int userId) {
mUserId = userId;
@@ -840,60 +797,42 @@
synchronized (mLock) {
mService = service;
mBinding = false;
- updateServiceInfoLocked(name, mUserId);
+ updateServiceInfoLocked(mUserId, name);
handlePendingRequestsLocked();
}
}
}
}
- private final class TextClassifierSettingsListener extends ContentObserver
- implements DeviceConfig.OnPropertiesChangedListener {
+ private final class TextClassifierSettingsListener implements
+ DeviceConfig.OnPropertiesChangedListener {
@NonNull private final Context mContext;
+ @NonNull private final TextClassificationConstants mSettings;
@Nullable private String mServicePackageName;
- TextClassifierSettingsListener(Context context, TextClassificationManagerService service) {
- super(null);
+ TextClassifierSettingsListener(Context context) {
mContext = context;
- mServicePackageName =
- getTextClassifierSettings(mContext).getTextClassifierServiceName();
+ mSettings = TextClassificationManager.getSettings(mContext);
+ mServicePackageName = mSettings.getTextClassifierServicePackageOverride();
}
public void registerObserver() {
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.TEXT_CLASSIFIER_CONSTANTS),
- false /* notifyForDescendants */,
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ mContext.getMainExecutor(),
this);
- if (ConfigParser.ENABLE_DEVICE_CONFIG) {
- DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
- mContext.getMainExecutor(),
- this);
- }
- }
-
- private void updateChange() {
- final String overrideServiceName = getTextClassifierSettings(
- mContext).getTextClassifierServiceName();
- if (TextUtils.equals(overrideServiceName, mServicePackageName)) {
- return;
- }
- mServicePackageName = overrideServiceName;
- unbindServiceIfNeeded();
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- invalidateSettings();
- updateChange();
}
@Override
public void onPropertiesChanged(DeviceConfig.Properties properties) {
- invalidateSettings();
- updateChange();
+ final String overrideServiceName = mSettings.getTextClassifierServicePackageOverride();
+
+ if (TextUtils.equals(overrideServiceName, mServicePackageName)) {
+ return;
+ }
+ mServicePackageName = overrideServiceName;
+ unbindServiceIfNecessary();
}
}
}
diff --git a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
deleted file mode 100644
index 340fe3d..0000000
--- a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * 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.android.server.timedetector;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.timedetector.ManualTimeSuggestion;
-import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
-import android.util.LocalLog;
-import android.util.Slog;
-import android.util.TimestampedValue;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.IndentingPrintWriter;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * An implementation of TimeDetectorStrategy that passes only NITZ suggestions to
- * {@link AlarmManager}.
- *
- * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
- */
-public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy {
-
- private static final boolean DBG = false;
- private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
-
- @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Origin {}
-
- /** Used when a time value originated from a telephony signal. */
- @Origin
- private static final int ORIGIN_PHONE = 1;
-
- /** Used when a time value originated from a user / manual settings. */
- @Origin
- private static final int ORIGIN_MANUAL = 2;
-
- /**
- * 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
- * there is something other than this class setting the system clock automatically.
- */
- private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
-
- // A log for changes made to the system clock and why.
- @NonNull private final LocalLog mTimeChangesLog = new LocalLog(30);
-
- // @NonNull after initialize()
- private Callback mCallback;
-
- // Last phone suggestion.
- @Nullable private PhoneTimeSuggestion mLastPhoneSuggestion;
-
- // Information about the last time signal received: Used when toggling auto-time.
- @Nullable private TimestampedValue<Long> mLastAutoSystemClockTime;
- private boolean mLastAutoSystemClockTimeSendNetworkBroadcast;
-
- // System clock state.
- @Nullable private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
-
- @Override
- public void initialize(@NonNull Callback callback) {
- mCallback = callback;
- }
-
- @Override
- public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
- // NITZ logic
-
- // Empty suggestions are just ignored as we don't currently keep track of suggestion origin.
- if (timeSuggestion.getUtcTime() == null) {
- return;
- }
-
- boolean timeSuggestionIsValid =
- validateNewPhoneSuggestion(timeSuggestion, mLastPhoneSuggestion);
- if (!timeSuggestionIsValid) {
- return;
- }
- // Always store the last NITZ value received, regardless of whether we go on to use it to
- // update the system clock. This is so that we can validate future phone suggestions.
- mLastPhoneSuggestion = timeSuggestion;
-
- // System clock update logic.
- final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
- setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, timeSuggestion);
- }
-
- @Override
- public synchronized void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
- final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
- setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion);
- }
-
- private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion,
- @Nullable PhoneTimeSuggestion lastSuggestion) {
-
- if (lastSuggestion != null) {
- long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
- newSuggestion.getUtcTime(), lastSuggestion.getUtcTime());
- if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) {
- // Out of order or bogus.
- Slog.w(LOG_TAG, "Bad NITZ signal received."
- + " referenceTimeDifference=" + referenceTimeDifference
- + " lastSuggestion=" + lastSuggestion
- + " newSuggestion=" + newSuggestion);
- return false;
- }
- }
- return true;
- }
-
- @GuardedBy("this")
- private void setSystemClockIfRequired(
- @Origin int origin, TimestampedValue<Long> time, Object cause) {
- // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only
- // when setting the time using NITZ.
- boolean sendNetworkBroadcast = origin == ORIGIN_PHONE;
-
- boolean isOriginAutomatic = isOriginAutomatic(origin);
- if (isOriginAutomatic) {
- // Store the last auto time candidate we've seen in all cases so we can set the system
- // clock when/if time detection is off but later enabled.
- mLastAutoSystemClockTime = time;
- mLastAutoSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast;
-
- if (!mCallback.isAutoTimeDetectionEnabled()) {
- if (DBG) {
- Slog.d(LOG_TAG, "Auto time detection is not enabled."
- + " origin=" + origin
- + ", time=" + time
- + ", cause=" + cause);
- }
- return;
- }
- } else {
- if (mCallback.isAutoTimeDetectionEnabled()) {
- if (DBG) {
- Slog.d(LOG_TAG, "Auto time detection is enabled."
- + " origin=" + origin
- + ", time=" + time
- + ", cause=" + cause);
- }
- return;
- }
- }
-
- mCallback.acquireWakeLock();
- try {
- long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
- long actualTimeMillis = mCallback.systemClockMillis();
-
- if (isOriginAutomatic) {
- // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
- // may be setting the clock.
- if (mLastAutoSystemClockTimeSet != null) {
- long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
- mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
- long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis);
- if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
- Slog.w(LOG_TAG,
- "System clock has not tracked elapsed real time clock. A clock may"
- + " be inaccurate or something unexpectedly set the system"
- + " clock."
- + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
- + " expectedTimeMillis=" + expectedTimeMillis
- + " actualTimeMillis=" + actualTimeMillis);
- }
- }
- }
-
- adjustAndSetDeviceSystemClock(
- time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, cause);
- } finally {
- mCallback.releaseWakeLock();
- }
- }
-
- private static boolean isOriginAutomatic(@Origin int origin) {
- return origin == ORIGIN_PHONE;
- }
-
- @Override
- public synchronized void handleAutoTimeDetectionChanged() {
- // If automatic time detection is enabled we update the system clock instantly if we can.
- // Conversely, if automatic time detection is disabled we leave the clock as it is.
- boolean enabled = mCallback.isAutoTimeDetectionEnabled();
- if (enabled) {
- if (mLastAutoSystemClockTime != null) {
- // Only send the network broadcast if the last candidate would have caused one.
- final boolean sendNetworkBroadcast = mLastAutoSystemClockTimeSendNetworkBroadcast;
-
- mCallback.acquireWakeLock();
- try {
- long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
- long actualTimeMillis = mCallback.systemClockMillis();
-
- final String reason = "Automatic time detection enabled.";
- adjustAndSetDeviceSystemClock(mLastAutoSystemClockTime, sendNetworkBroadcast,
- elapsedRealtimeMillis, actualTimeMillis, reason);
- } finally {
- mCallback.releaseWakeLock();
- }
- }
- } else {
- // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
- // it should be in future.
- mLastAutoSystemClockTimeSet = null;
- }
- }
-
- @Override
- public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- ipw.println("TimeDetectorStrategy:");
- ipw.increaseIndent(); // level 1
-
- ipw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion);
- ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
- ipw.println("mLastAutoSystemClockTime=" + mLastAutoSystemClockTime);
- ipw.println("mLastAutoSystemClockTimeSendNetworkBroadcast="
- + mLastAutoSystemClockTimeSendNetworkBroadcast);
-
-
- ipw.println("Time change log:");
- ipw.increaseIndent(); // level 2
- mTimeChangesLog.dump(ipw);
- ipw.decreaseIndent(); // level 2
-
- ipw.decreaseIndent(); // level 1
- ipw.flush();
- }
-
- @GuardedBy("this")
- private void adjustAndSetDeviceSystemClock(
- TimestampedValue<Long> newTime, boolean sendNetworkBroadcast,
- long elapsedRealtimeMillis, long actualSystemClockMillis, Object cause) {
-
- // Adjust for the time that has elapsed since the signal was received.
- long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
-
- // Check if the new signal would make sufficient difference to the system clock. If it's
- // below the threshold then ignore it.
- long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
- long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis();
- if (absTimeDifference < systemClockUpdateThreshold) {
- if (DBG) {
- Slog.d(LOG_TAG, "Not setting system clock. New time and"
- + " system clock are close enough."
- + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
- + " newTime=" + newTime
- + " cause=" + cause
- + " systemClockUpdateThreshold=" + systemClockUpdateThreshold
- + " absTimeDifference=" + absTimeDifference);
- }
- return;
- }
-
- mCallback.setSystemClock(newSystemClockMillis);
- String logMsg = "Set system clock using time=" + newTime
- + " cause=" + cause
- + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
- + " newSystemClockMillis=" + newSystemClockMillis;
- if (DBG) {
- Slog.d(LOG_TAG, logMsg);
- }
- mTimeChangesLog.log(logMsg);
-
- // CLOCK_PARANOIA : Record the last time this class set the system clock.
- mLastAutoSystemClockTimeSet = newTime;
-
- if (sendNetworkBroadcast) {
- // 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(TelephonyIntents.ACTION_NETWORK_SET_TIME);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("time", newSystemClockMillis);
- mCallback.sendStickyBroadcast(intent);
- }
- }
-}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 34400ff..172367a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -31,7 +31,6 @@
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.SystemService;
-import com.android.server.timedetector.TimeDetectorStrategy.Callback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -58,17 +57,16 @@
@NonNull private final Handler mHandler;
@NonNull private final Context mContext;
- @NonNull private final Callback mCallback;
@NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
private static TimeDetectorService create(@NonNull Context context) {
- TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy();
+ TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl();
TimeDetectorStrategyCallbackImpl callback = new TimeDetectorStrategyCallbackImpl(context);
- timeDetector.initialize(callback);
+ timeDetectorStrategy.initialize(callback);
Handler handler = FgThread.getHandler();
TimeDetectorService timeDetectorService =
- new TimeDetectorService(context, handler, callback, timeDetector);
+ new TimeDetectorService(context, handler, timeDetectorStrategy);
// Wire up event listening.
ContentResolver contentResolver = context.getContentResolver();
@@ -85,10 +83,9 @@
@VisibleForTesting
public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
- @NonNull Callback callback, @NonNull TimeDetectorStrategy timeDetectorStrategy) {
+ @NonNull TimeDetectorStrategy timeDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
- mCallback = Objects.requireNonNull(callback);
mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy);
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
new file mode 100644
index 0000000..c50248d
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -0,0 +1,511 @@
+/*
+ * 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.android.server.timedetector;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.PhoneTimeSuggestion;
+import android.content.Intent;
+import android.telephony.TelephonyManager;
+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 java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
+ * {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used
+ * unless the data becomes too stale.
+ *
+ * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
+ */
+public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
+
+ private static final boolean DBG = false;
+ private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
+
+ /** A score value used to indicate "no score", either due to validation failure or age. */
+ private static final int PHONE_INVALID_SCORE = -1;
+ /** The number of buckets phone suggestions can be put in by age. */
+ private static final int PHONE_BUCKET_COUNT = 24;
+ /** 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. */
+ @VisibleForTesting
+ static final long PHONE_MAX_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS;
+
+ @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Origin {}
+
+ /** Used when a time value originated from a telephony signal. */
+ @Origin
+ private static final int ORIGIN_PHONE = 1;
+
+ /** Used when a time value originated from a user / manual settings. */
+ @Origin
+ private static final int ORIGIN_MANUAL = 2;
+
+ /**
+ * 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
+ * there is something other than this class setting the system clock.
+ */
+ private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000;
+
+ /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
+ private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
+
+ // A log for changes made to the system clock and why.
+ @NonNull
+ private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
+
+ // @NonNull after initialize()
+ private Callback mCallback;
+
+ // Used to store the last time the system clock state was set automatically. It is used to
+ // detect (and log) issues with the realtime clock or whether the clock is being set without
+ // going through this strategy code.
+ @GuardedBy("this")
+ @Nullable
+ private TimestampedValue<Long> mLastAutoSystemClockTimeSet;
+
+ /**
+ * A mapping from phoneId to a time suggestion. We typically expect one or two mappings: devices
+ * will have a small number of telephony devices and phoneIds are assumed to be stable.
+ */
+ @GuardedBy("this")
+ private ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId =
+ new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+
+ @Override
+ public void initialize(@NonNull Callback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public synchronized void suggestManualTime(@NonNull ManualTimeSuggestion suggestion) {
+ final TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
+
+ if (!validateSuggestionTime(newUtcTime, suggestion)) {
+ return;
+ }
+
+ String cause = "Manual time suggestion received: suggestion=" + suggestion;
+ setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, cause);
+ }
+
+ @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,
+ // so we can continue relying on previous suggestions when we lose connectivity. This is
+ // unlike time zone, where a user may lose connectivity when boarding a flight and where we
+ // do want to "forget" old signals. Suggestions that are too old are discarded later in the
+ // detection algorithm.
+ if (timeSuggestion.getUtcTime() == null) {
+ return;
+ }
+
+ // Perform validation / input filtering and record the validated suggestion against the
+ // phoneId.
+ if (!validateAndStorePhoneSuggestion(timeSuggestion)) {
+ return;
+ }
+
+ // Now perform auto time detection. The new suggestion may be used to modify the system
+ // clock.
+ String reason = "New phone time suggested. timeSuggestion=" + timeSuggestion;
+ doAutoTimeDetection(reason);
+ }
+
+ @Override
+ public synchronized void handleAutoTimeDetectionChanged() {
+ boolean enabled = mCallback.isAutoTimeDetectionEnabled();
+ // When automatic time detection is enabled we update the system clock instantly if we can.
+ // Conversely, when automatic time detection is disabled we leave the clock as it is.
+ if (enabled) {
+ String reason = "Auto time zone detection setting enabled.";
+ doAutoTimeDetection(reason);
+ } else {
+ // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what
+ // it should be in future.
+ mLastAutoSystemClockTimeSet = null;
+ }
+ }
+
+ @Override
+ public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("TimeDetectorStrategy:");
+ ipw.increaseIndent(); // level 1
+
+ ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
+
+ ipw.println("Time change log:");
+ ipw.increaseIndent(); // level 2
+ mTimeChangesLog.dump(ipw);
+ ipw.decreaseIndent(); // level 2
+
+ ipw.println("Phone suggestion history:");
+ ipw.increaseIndent(); // level 2
+ mSuggestionByPhoneId.dump(ipw);
+ ipw.decreaseIndent(); // level 2
+
+ ipw.decreaseIndent(); // level 1
+ ipw.flush();
+ }
+
+ @GuardedBy("this")
+ private boolean validateAndStorePhoneSuggestion(@NonNull PhoneTimeSuggestion suggestion) {
+ TimestampedValue<Long> newUtcTime = suggestion.getUtcTime();
+ if (!validateSuggestionTime(newUtcTime, suggestion)) {
+ // There's probably nothing useful we can do: elsewhere we assume that reference
+ // times are in the past so just stop here.
+ return false;
+ }
+
+ int phoneId = suggestion.getPhoneId();
+ PhoneTimeSuggestion previousSuggestion = mSuggestionByPhoneId.get(phoneId);
+ if (previousSuggestion != null) {
+ // We can log / discard suggestions with obvious issues with the reference time clock.
+ if (previousSuggestion.getUtcTime() == null
+ || previousSuggestion.getUtcTime().getValue() == null) {
+ // This should be impossible given we only store validated suggestions.
+ Slog.w(LOG_TAG, "Previous suggestion is null or has a null time."
+ + " previousSuggestion=" + previousSuggestion
+ + ", suggestion=" + suggestion);
+ return false;
+ }
+
+ long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
+ newUtcTime, previousSuggestion.getUtcTime());
+ if (referenceTimeDifference < 0) {
+ // The reference time is before the previously received suggestion. Ignore it.
+ Slog.w(LOG_TAG, "Out of order phone suggestion received."
+ + " referenceTimeDifference=" + referenceTimeDifference
+ + " previousSuggestion=" + previousSuggestion
+ + " suggestion=" + suggestion);
+ return false;
+ }
+ }
+
+ // Store the latest suggestion.
+ mSuggestionByPhoneId.put(phoneId, suggestion);
+ return true;
+ }
+
+ private boolean validateSuggestionTime(
+ @NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion) {
+ if (newUtcTime.getValue() == null) {
+ Slog.w(LOG_TAG, "Suggested time value is null. suggestion=" + suggestion);
+ return false;
+ }
+
+ // We can validate the suggestion against the reference time clock.
+ long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+ if (elapsedRealtimeMillis < newUtcTime.getReferenceTimeMillis()) {
+ // elapsedRealtime clock went backwards?
+ Slog.w(LOG_TAG, "New reference time is in the future? Ignoring."
+ + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ + ", suggestion=" + suggestion);
+ return false;
+ }
+ return true;
+ }
+
+ @GuardedBy("this")
+ private void doAutoTimeDetection(@NonNull String detectionReason) {
+ if (!mCallback.isAutoTimeDetectionEnabled()) {
+ // Avoid doing unnecessary work with this (race-prone) check.
+ return;
+ }
+
+ 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);
+ }
+ return;
+ }
+
+ final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime();
+ String cause = "Found good suggestion."
+ + ", bestPhoneSuggestion=" + bestPhoneSuggestion
+ + ", detectionReason=" + detectionReason;
+ setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause);
+ }
+
+ @GuardedBy("this")
+ @Nullable
+ private PhoneTimeSuggestion findBestPhoneSuggestion() {
+ long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+
+ // Phone time suggestions are assumed to be derived from NITZ or NITZ-like signals. These
+ // have a number of limitations:
+ // 1) No guarantee of accuracy ("accuracy of the time information is in the order of
+ // minutes") [1]
+ // 2) No guarantee of regular signals ("dependent on the handset crossing radio network
+ // boundaries") [1]
+ //
+ // [1] https://en.wikipedia.org/wiki/NITZ
+ //
+ // Generally, when there are suggestions from multiple phoneIds they should usually
+ // approximately agree. In cases where signals *are* inaccurate we don't want to vacillate
+ // between signals from two phoneIds. However, it is known for NITZ signals to be incorrect
+ // occasionally, which means we also don't want to stick forever with one phoneId. Without
+ // cross-referencing across sources (e.g. the current device time, NTP), or doing some kind
+ // of statistical analysis of consistency within and across phoneIds, we can't know which
+ // suggestions are more correct.
+ //
+ // For simplicity, we try to value recency, then consistency of phoneId.
+ //
+ // The heuristic works as follows:
+ // Recency: The most recent suggestion from each phone is scored. The score is based on a
+ // discrete age bucket, i.e. so signals received around the same time will be in the same
+ // bucket, thus applying a loose reference time ordering. The suggestion with the highest
+ // score is used.
+ // Consistency: If there a multiple suggestions with the same score, the suggestion with the
+ // lowest phoneId is always taken.
+ //
+ // In the trivial case with a single ID this will just mean that the latest received
+ // suggestion is used.
+
+ PhoneTimeSuggestion bestSuggestion = null;
+ int bestScore = PHONE_INVALID_SCORE;
+ for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
+ Integer phoneId = mSuggestionByPhoneId.keyAt(i);
+ PhoneTimeSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i);
+ if (candidateSuggestion == null) {
+ // Unexpected - null suggestions should never be stored.
+ Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId."
+ + " phoneId=" + phoneId);
+ continue;
+ } else if (candidateSuggestion.getUtcTime() == null) {
+ // Unexpected - we do not store empty suggestions.
+ Slog.w(LOG_TAG, "Latest suggestion unexpectedly empty. "
+ + " candidateSuggestion=" + candidateSuggestion);
+ continue;
+ }
+
+ int candidateScore = scorePhoneSuggestion(elapsedRealtimeMillis, candidateSuggestion);
+ if (candidateScore == PHONE_INVALID_SCORE) {
+ // Expected: This means the suggestion is obviously invalid or just too old.
+ continue;
+ }
+
+ // Higher scores are better.
+ if (bestSuggestion == null || bestScore < candidateScore) {
+ bestSuggestion = candidateSuggestion;
+ bestScore = candidateScore;
+ } else if (bestScore == candidateScore) {
+ // Tie! Use the suggestion with the lowest phoneId.
+ int candidatePhoneId = candidateSuggestion.getPhoneId();
+ int bestPhoneId = bestSuggestion.getPhoneId();
+ if (candidatePhoneId < bestPhoneId) {
+ bestSuggestion = candidateSuggestion;
+ }
+ }
+ }
+ return bestSuggestion;
+ }
+
+ 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.
+ 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. "
+ + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ + ", timeSuggestion=" + timeSuggestion);
+ return PHONE_INVALID_SCORE;
+ }
+
+ long ageMillis = elapsedRealtimeMillis - referenceTimeMillis;
+
+ // 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) {
+ 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;
+ }
+
+ @GuardedBy("this")
+ private void setSystemClockIfRequired(
+ @Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) {
+
+ boolean isOriginAutomatic = isOriginAutomatic(origin);
+ if (isOriginAutomatic) {
+ if (!mCallback.isAutoTimeDetectionEnabled()) {
+ if (DBG) {
+ Slog.d(LOG_TAG, "Auto time detection is not enabled."
+ + " origin=" + origin
+ + ", time=" + time
+ + ", cause=" + cause);
+ }
+ return;
+ }
+ } else {
+ if (mCallback.isAutoTimeDetectionEnabled()) {
+ if (DBG) {
+ Slog.d(LOG_TAG, "Auto time detection is enabled."
+ + " origin=" + origin
+ + ", time=" + time
+ + ", cause=" + cause);
+ }
+ return;
+ }
+ }
+
+ mCallback.acquireWakeLock();
+ try {
+ setSystemClockUnderWakeLock(origin, time, cause);
+ } finally {
+ mCallback.releaseWakeLock();
+ }
+ }
+
+ private static boolean isOriginAutomatic(@Origin int origin) {
+ return origin == ORIGIN_PHONE;
+ }
+
+ @GuardedBy("this")
+ private void setSystemClockUnderWakeLock(
+ int origin, @NonNull TimestampedValue<Long> newTime, @NonNull Object cause) {
+
+ long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+ boolean isOriginAutomatic = isOriginAutomatic(origin);
+ long actualSystemClockMillis = mCallback.systemClockMillis();
+ if (isOriginAutomatic) {
+ // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
+ // may be setting the clock.
+ if (mLastAutoSystemClockTimeSet != null) {
+ long expectedTimeMillis = TimeDetectorStrategy.getTimeAt(
+ mLastAutoSystemClockTimeSet, elapsedRealtimeMillis);
+ long absSystemClockDifference =
+ Math.abs(expectedTimeMillis - actualSystemClockMillis);
+ if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) {
+ Slog.w(LOG_TAG,
+ "System clock has not tracked elapsed real time clock. A clock may"
+ + " be inaccurate or something unexpectedly set the system"
+ + " clock."
+ + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ + " expectedTimeMillis=" + expectedTimeMillis
+ + " actualTimeMillis=" + actualSystemClockMillis
+ + " cause=" + cause);
+ }
+ }
+ }
+
+ // Adjust for the time that has elapsed since the signal was received.
+ long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis);
+
+ // Check if the new signal would make sufficient difference to the system clock. If it's
+ // below the threshold then ignore it.
+ long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
+ long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis();
+ if (absTimeDifference < systemClockUpdateThreshold) {
+ if (DBG) {
+ Slog.d(LOG_TAG, "Not setting system clock. New time and"
+ + " system clock are close enough."
+ + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ + " newTime=" + newTime
+ + " cause=" + cause
+ + " systemClockUpdateThreshold=" + systemClockUpdateThreshold
+ + " absTimeDifference=" + absTimeDifference);
+ }
+ return;
+ }
+
+ mCallback.setSystemClock(newSystemClockMillis);
+ String logMsg = "Set system clock using time=" + newTime
+ + " cause=" + cause
+ + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ + " newSystemClockMillis=" + newSystemClockMillis;
+ if (DBG) {
+ Slog.d(LOG_TAG, logMsg);
+ }
+ mTimeChangesLog.log(logMsg);
+
+ // CLOCK_PARANOIA : Record the last time this class set the system clock due to an auto-time
+ // signal, or clear the record it is being done manually.
+ if (isOriginAutomatic(origin)) {
+ mLastAutoSystemClockTimeSet = newTime;
+ } 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);
+ }
+ }
+
+ /**
+ * Returns the current best phone suggestion. Not intended for general use: it is used during
+ * tests to check strategy behavior.
+ */
+ @VisibleForTesting
+ @Nullable
+ public synchronized PhoneTimeSuggestion findBestPhoneSuggestionForTests() {
+ return findBestPhoneSuggestion();
+ }
+
+ /**
+ * A method used to inspect state during tests. Not intended for general use.
+ */
+ @VisibleForTesting
+ @Nullable
+ public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
+ return mSuggestionByPhoneId.get(phoneId);
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/ArrayMapWithHistory.java b/services/core/java/com/android/server/timezonedetector/ArrayMapWithHistory.java
new file mode 100644
index 0000000..3274f0e
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/ArrayMapWithHistory.java
@@ -0,0 +1,187 @@
+/*
+ * 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.timezonedetector;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * A partial decorator for {@link ArrayMap} that records historic values for each mapping for
+ * debugging later with {@link #dump(IndentingPrintWriter)}.
+ *
+ * <p>This class is only intended for use in {@link TimeZoneDetectorStrategy} and
+ * {@link com.android.server.timedetector.TimeDetectorStrategy} so only provides the parts of the
+ * {@link ArrayMap} API needed. If it is ever extended to include deletion methods like
+ * {@link ArrayMap#remove(Object)} some thought would need to be given to the correct
+ * {@link ArrayMap#containsKey(Object)} behavior for the history. Like {@link ArrayMap}, it is not
+ * thread-safe.
+ *
+ * @param <K> the type of the key
+ * @param <V> the type of the value
+ */
+public final class ArrayMapWithHistory<K, V> {
+ private static final String TAG = "ArrayMapWithHistory";
+
+ /** The size the linked list against each value is allowed to grow to. */
+ private final int mMaxHistorySize;
+
+ @Nullable
+ private ArrayMap<K, ReferenceWithHistory<V>> mMap;
+
+ /**
+ * Creates an instance that records, at most, the specified number of values against each key.
+ */
+ public ArrayMapWithHistory(@IntRange(from = 1) int maxHistorySize) {
+ if (maxHistorySize < 1) {
+ throw new IllegalArgumentException("maxHistorySize < 1: " + maxHistorySize);
+ }
+ mMaxHistorySize = maxHistorySize;
+ }
+
+ /**
+ * See {@link ArrayMap#put(K, V)}.
+ */
+ @Nullable
+ public V put(@Nullable K key, @Nullable V value) {
+ if (mMap == null) {
+ mMap = new ArrayMap<>();
+ }
+
+ ReferenceWithHistory<V> valueHolder = mMap.get(key);
+ if (valueHolder == null) {
+ valueHolder = new ReferenceWithHistory<>(mMaxHistorySize);
+ mMap.put(key, valueHolder);
+ } else if (valueHolder.getHistoryCount() == 0) {
+ Log.w(TAG, "History for \"" + key + "\" was unexpectedly empty");
+ }
+
+ return valueHolder.set(value);
+ }
+
+ /**
+ * See {@link ArrayMap#get(Object)}.
+ */
+ @Nullable
+ public V get(@Nullable Object key) {
+ if (mMap == null) {
+ return null;
+ }
+
+ ReferenceWithHistory<V> valueHolder = mMap.get(key);
+ if (valueHolder == null) {
+ return null;
+ } else if (valueHolder.getHistoryCount() == 0) {
+ Log.w(TAG, "History for \"" + key + "\" was unexpectedly empty");
+ }
+ return valueHolder.get();
+ }
+
+ /**
+ * See {@link ArrayMap#size()}.
+ */
+ public int size() {
+ return mMap == null ? 0 : mMap.size();
+ }
+
+ /**
+ * See {@link ArrayMap#keyAt(int)}.
+ */
+ @Nullable
+ public K keyAt(int index) {
+ if (mMap == null) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ return mMap.keyAt(index);
+ }
+
+ /**
+ * See {@link ArrayMap#valueAt(int)}.
+ */
+ @Nullable
+ public V valueAt(int index) {
+ if (mMap == null) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+
+ ReferenceWithHistory<V> valueHolder = mMap.valueAt(index);
+ if (valueHolder == null || valueHolder.getHistoryCount() == 0) {
+ Log.w(TAG, "valueAt(" + index + ") was unexpectedly null or empty");
+ return null;
+ }
+ return valueHolder.get();
+ }
+
+ /**
+ * Dumps the content of the map, including historic values, using the supplied writer.
+ */
+ public void dump(@NonNull IndentingPrintWriter ipw) {
+ if (mMap == null) {
+ ipw.println("{Empty}");
+ } else {
+ for (int i = 0; i < mMap.size(); i++) {
+ ipw.println("key idx: " + i + "=" + mMap.keyAt(i));
+ ReferenceWithHistory<V> value = mMap.valueAt(i);
+ ipw.println("val idx: " + i + "=" + value);
+ ipw.increaseIndent();
+
+ ipw.println("Historic values=[");
+ ipw.increaseIndent();
+ value.dump(ipw);
+ ipw.decreaseIndent();
+ ipw.println("]");
+
+ ipw.decreaseIndent();
+ }
+ }
+ ipw.flush();
+ }
+
+ /**
+ * Internal method intended for tests that returns the number of historic values associated with
+ * the supplied key currently. If there is no mapping for the key then {@code 0} is returned.
+ */
+ @VisibleForTesting
+ public int getHistoryCountForKeyForTests(@Nullable K key) {
+ if (mMap == null) {
+ return 0;
+ }
+
+ ReferenceWithHistory<V> valueHolder = mMap.get(key);
+ if (valueHolder == null) {
+ return 0;
+ } else if (valueHolder.getHistoryCount() == 0) {
+ Log.w(TAG, "getValuesSizeForKeyForTests(\"" + key + "\") was unexpectedly empty");
+ return 0;
+ } else {
+ return valueHolder.getHistoryCount();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ArrayMapWithHistory{"
+ + "mHistorySize=" + mMaxHistorySize
+ + ", mMap=" + mMap
+ + '}';
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
new file mode 100644
index 0000000..8bd1035
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
@@ -0,0 +1,118 @@
+/*
+ * 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.timezonedetector;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.LinkedList;
+
+/**
+ * A class that behaves like the following definition, except it stores the history of values set
+ * that can be dumped for debugging with {@link #dump(IndentingPrintWriter)}.
+ *
+ * <pre>{@code
+ * private static class Ref<V> {
+ * private V mValue;
+ *
+ * public V get() {
+ * return mValue;
+ * }
+ *
+ * public V set(V value) {
+ * V previous = mValue;
+ * mValue = value;
+ * return previous;
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>This class is not thread-safe.
+ *
+ * @param <V> the type of the value
+ */
+public final class ReferenceWithHistory<V> {
+
+ /** The size the history linked list is allowed to grow to. */
+ private final int mMaxHistorySize;
+
+ @Nullable
+ private LinkedList<V> mValues;
+
+ /**
+ * Creates an instance that records, at most, the specified number of values.
+ */
+ public ReferenceWithHistory(@IntRange(from = 1) int maxHistorySize) {
+ if (maxHistorySize < 1) {
+ throw new IllegalArgumentException("maxHistorySize < 1: " + maxHistorySize);
+ }
+ this.mMaxHistorySize = maxHistorySize;
+ }
+
+ /** Returns the current value, or {@code null} if it has never been set. */
+ @Nullable
+ public V get() {
+ return (mValues == null || mValues.isEmpty()) ? null : mValues.getFirst();
+ }
+
+ /** Sets the current value. Returns the previous value, or {@code null}. */
+ @Nullable
+ public V set(@Nullable V newValue) {
+ if (mValues == null) {
+ mValues = new LinkedList<>();
+ }
+
+ V previous = get();
+
+ mValues.addFirst(newValue);
+ if (mValues.size() > mMaxHistorySize) {
+ mValues.removeLast();
+ }
+ return previous;
+ }
+
+ /**
+ * Dumps the content of the reference, including historic values, using the supplied writer.
+ */
+ public void dump(@NonNull IndentingPrintWriter ipw) {
+ if (mValues == null) {
+ ipw.println("{Empty}");
+ } else {
+ int i = 0;
+ for (V value : mValues) {
+ ipw.println(i + ": " + value);
+ i++;
+ }
+ }
+ ipw.flush();
+ }
+
+ /**
+ * Returns the number of historic entries stored currently.
+ */
+ public int getHistoryCount() {
+ return mValues == null ? 0 : mValues.size();
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(get());
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index e034ad4..adf6d7e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -20,13 +20,9 @@
import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings;
-import com.android.internal.telephony.TelephonyIntents;
-
/**
* The real implementation of {@link TimeZoneDetectorStrategy.Callback}.
*/
@@ -66,16 +62,8 @@
}
@Override
- public void setDeviceTimeZone(String zoneId, boolean sendNetworkBroadcast) {
+ public void setDeviceTimeZone(String zoneId) {
AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
alarmManager.setTimeZone(zoneId);
-
- if (sendNetworkBroadcast) {
- // TODO Nothing in the platform appears to listen for this. Remove it.
- Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("time-zone", zoneId);
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
- }
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 5db12c7..b4a4399 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -27,7 +27,6 @@
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.PhoneTimeZoneSuggestion;
import android.content.Context;
-import android.util.ArrayMap;
import android.util.LocalLog;
import android.util.Slog;
@@ -38,8 +37,6 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.LinkedList;
-import java.util.Map;
import java.util.Objects;
/**
@@ -86,7 +83,7 @@
/**
* Sets the device's time zone.
*/
- void setDeviceTimeZone(@NonNull String zoneId, boolean sendNetworkBroadcast);
+ void setDeviceTimeZone(@NonNull String zoneId);
}
private static final String LOG_TAG = "TimeZoneDetectorStrategy";
@@ -172,17 +169,16 @@
* (for use during debugging).
*/
@NonNull
- private final LocalLog mTimeZoneChangesLog = new LocalLog(30);
+ private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
/**
- * A mapping from phoneId to a linked list of phone time zone suggestions (the head being the
- * latest). We typically expect one or two entries in this Map: devices will have a small number
- * of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with
- * the ID will not exceed {@link #KEEP_PHONE_SUGGESTION_HISTORY_SIZE} in size.
+ * A mapping from phoneId to a phone time zone suggestion. We typically expect one or two
+ * mappings: devices will have a small number of telephony devices and phoneIds are assumed to
+ * be stable.
*/
@GuardedBy("this")
- private ArrayMap<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> mSuggestionByPhoneId =
- new ArrayMap<>();
+ private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionByPhoneId =
+ new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);
/**
* Creates a new instance of {@link TimeZoneDetectorStrategy}.
@@ -226,16 +222,7 @@
new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
// Store the suggestion against the correct phoneId.
- LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
- mSuggestionByPhoneId.get(suggestion.getPhoneId());
- if (suggestions == null) {
- suggestions = new LinkedList<>();
- mSuggestionByPhoneId.put(suggestion.getPhoneId(), suggestions);
- }
- suggestions.addFirst(scoredSuggestion);
- if (suggestions.size() > KEEP_PHONE_SUGGESTION_HISTORY_SIZE) {
- suggestions.removeLast();
- }
+ mSuggestionByPhoneId.put(suggestion.getPhoneId(), scoredSuggestion);
// Now perform auto time zone detection. The new suggestion may be used to modify the time
// zone setting.
@@ -333,7 +320,6 @@
Objects.requireNonNull(newZoneId);
Objects.requireNonNull(cause);
- boolean sendNetworkBroadcast = (origin == ORIGIN_PHONE);
boolean isOriginAutomatic = isOriginAutomatic(origin);
if (isOriginAutomatic) {
if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
@@ -373,12 +359,11 @@
return;
}
- mCallback.setDeviceTimeZone(newZoneId, sendNetworkBroadcast);
+ mCallback.setDeviceTimeZone(newZoneId);
String msg = "Set device time zone."
+ " origin=" + origin
+ ", currentZoneId=" + currentZoneId
+ ", newZoneId=" + newZoneId
- + ", sendNetworkBroadcast" + sendNetworkBroadcast
+ ", cause=" + cause;
if (DBG) {
Slog.d(LOG_TAG, msg);
@@ -387,7 +372,7 @@
}
private static boolean isOriginAutomatic(@Origin int origin) {
- return origin == ORIGIN_PHONE;
+ return origin != ORIGIN_MANUAL;
}
@GuardedBy("this")
@@ -400,13 +385,7 @@
// rate-limit so age is not a strong indicator of confidence. Instead, the callers are
// expected to withdraw suggestions they no longer have confidence in.
for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
- LinkedList<QualifiedPhoneTimeZoneSuggestion> phoneSuggestions =
- mSuggestionByPhoneId.valueAt(i);
- if (phoneSuggestions == null) {
- // Unexpected
- continue;
- }
- QualifiedPhoneTimeZoneSuggestion candidateSuggestion = phoneSuggestions.getFirst();
+ QualifiedPhoneTimeZoneSuggestion candidateSuggestion = mSuggestionByPhoneId.valueAt(i);
if (candidateSuggestion == null) {
// Unexpected
continue;
@@ -458,15 +437,17 @@
* Dumps internal state such as field values.
*/
public synchronized void dumpState(PrintWriter pw, String[] args) {
- pw.println("TimeZoneDetectorStrategy:");
- pw.println("mCallback.isTimeZoneDetectionEnabled()="
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("TimeZoneDetectorStrategy:");
+
+ ipw.increaseIndent(); // level 1
+ ipw.println("mCallback.isTimeZoneDetectionEnabled()="
+ mCallback.isAutoTimeZoneDetectionEnabled());
- pw.println("mCallback.isDeviceTimeZoneInitialized()="
+ ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+ mCallback.isDeviceTimeZoneInitialized());
- pw.println("mCallback.getDeviceTimeZone()="
+ ipw.println("mCallback.getDeviceTimeZone()="
+ mCallback.getDeviceTimeZone());
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("Time zone change log:");
ipw.increaseIndent(); // level 2
mTimeZoneChangesLog.dump(ipw);
@@ -474,21 +455,10 @@
ipw.println("Phone suggestion history:");
ipw.increaseIndent(); // level 2
- for (Map.Entry<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> entry
- : mSuggestionByPhoneId.entrySet()) {
- ipw.println("Phone " + entry.getKey());
-
- ipw.increaseIndent(); // level 3
- for (QualifiedPhoneTimeZoneSuggestion suggestion : entry.getValue()) {
- ipw.println(suggestion);
- }
- ipw.decreaseIndent(); // level 3
- }
+ mSuggestionByPhoneId.dump(ipw);
ipw.decreaseIndent(); // level 2
ipw.decreaseIndent(); // level 1
ipw.flush();
-
- pw.flush();
}
/**
@@ -496,12 +466,7 @@
*/
@VisibleForTesting
public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
- LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
- mSuggestionByPhoneId.get(phoneId);
- if (suggestions == null) {
- return null;
- }
- return suggestions.getFirst();
+ return mSuggestionByPhoneId.get(phoneId);
}
/**
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
old mode 100644
new mode 100755
index 8e66b14..18ed51a
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2943,6 +2943,16 @@
if (state != null) {
setStateLocked(inputId, state, mCurrentUserId);
}
+ UserState userState = getOrCreateUserStateLocked(mCurrentUserId);
+ // Broadcast the event to all hardware inputs.
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ if (!serviceState.isHardware || serviceState.service == null) continue;
+ try {
+ serviceState.service.notifyHdmiDeviceUpdated(deviceInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in notifyHdmiDeviceUpdated", e);
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/utils/TEST_MAPPING b/services/core/java/com/android/server/utils/TEST_MAPPING
new file mode 100644
index 0000000..bb7cea9
--- /dev/null
+++ b/services/core/java/com/android/server/utils/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.utils"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/utils/quota/Categorizer.java b/services/core/java/com/android/server/utils/quota/Categorizer.java
new file mode 100644
index 0000000..bf24991
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Categorizer.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.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Identifies the {@link Category} that each UPTC belongs in.
+ *
+ * @see Uptc
+ */
+public interface Categorizer {
+ /**
+ * Return the {@link Category} that this UPTC belongs to.
+ *
+ * @see Uptc
+ */
+ @NonNull
+ Category getCategory(int userId, @NonNull String packageName, @Nullable String tag);
+}
diff --git a/services/core/java/com/android/server/utils/quota/Category.java b/services/core/java/com/android/server/utils/quota/Category.java
new file mode 100644
index 0000000..d8459d7
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Category.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 com.android.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.CategoryProto;
+
+/**
+ * A category as defined by the (system) client. Categories are used to put UPTCs in different
+ * groups. A sample group of Categories could be the various App Standby buckets or foreground vs
+ * background.
+ *
+ * @see Uptc
+ */
+public final class Category {
+ @NonNull
+ private final String mName;
+
+ private final int mHash;
+
+ /** Construct a new Category with the specified name. */
+ public Category(@NonNull String name) {
+ mName = name;
+ mHash = name.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other instanceof Category) {
+ return this.mName.equals(((Category) other).mName);
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return mHash;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "Category{" + mName + "}";
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(CategoryProto.NAME, mName);
+ proto.end(token);
+ }
+}
diff --git a/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java b/services/core/java/com/android/server/utils/quota/QuotaChangeListener.java
new file mode 100644
index 0000000..b673050
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/QuotaChangeListener.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.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Listener that is notified when a UPTC goes in and out of quota.
+ *
+ * @see Uptc
+ */
+public interface QuotaChangeListener {
+ /**
+ * Called when the UPTC reaches its quota or comes back into quota.
+ *
+ * @see Uptc
+ */
+ void onQuotaStateChanged(int userId, @NonNull String packageName, @Nullable String tag);
+}
diff --git a/services/core/java/com/android/server/utils/quota/Uptc.java b/services/core/java/com/android/server/utils/quota/Uptc.java
new file mode 100644
index 0000000..4077544
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/Uptc.java
@@ -0,0 +1,88 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.UptcProto;
+
+import java.util.Objects;
+
+/**
+ * A data object that represents a userId-packageName-tag combination (UPTC). The tag can be any
+ * desired String.
+ */
+final class Uptc {
+ public final int userId;
+ @NonNull
+ public final String packageName;
+ @Nullable
+ public final String tag;
+
+ private final int mHash;
+
+ /** Construct a new Uptc with the specified values. */
+ Uptc(int userId, @NonNull String packageName, @Nullable String tag) {
+ this.userId = userId;
+ this.packageName = packageName;
+ this.tag = tag;
+
+ mHash = 31 * userId
+ + 31 * packageName.hashCode()
+ + tag == null ? 0 : (31 * tag.hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return string(userId, packageName, tag);
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ proto.write(UptcProto.USER_ID, userId);
+ proto.write(UptcProto.NAME, packageName);
+ proto.write(UptcProto.TAG, tag);
+
+ proto.end(token);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof Uptc) {
+ final Uptc other = (Uptc) obj;
+ return userId == other.userId
+ && Objects.equals(packageName, other.packageName)
+ && Objects.equals(tag, other.tag);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHash;
+ }
+
+ /** Standardize the output of a UPTC. */
+ static String string(int userId, @NonNull String packageName, @Nullable String tag) {
+ return "<" + userId + ">" + packageName + (tag == null ? "" : ("::" + tag));
+ }
+}
diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java
new file mode 100644
index 0000000..7b49913
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/UptcMap.java
@@ -0,0 +1,163 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.SparseArrayMap;
+
+import java.util.function.Consumer;
+
+/**
+ * A SparseArrayMap of ArrayMaps, which is suitable for holding userId-packageName-tag combination
+ * (UPTC)->object associations. Tags are any desired String.
+ *
+ * @see Uptc
+ */
+class UptcMap<T> {
+ private final SparseArrayMap<ArrayMap<String, T>> mData = new SparseArrayMap<>();
+
+ public void add(int userId, @NonNull String packageName, @Nullable String tag,
+ @Nullable T obj) {
+ ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data == null) {
+ data = new ArrayMap<>();
+ mData.add(userId, packageName, data);
+ }
+ data.put(tag, obj);
+ }
+
+ public void clear() {
+ mData.clear();
+ }
+
+ public boolean contains(int userId, @NonNull String packageName) {
+ return mData.contains(userId, packageName);
+ }
+
+ public boolean contains(int userId, @NonNull String packageName, @Nullable String tag) {
+ // This structure never inserts a null ArrayMap, so if get(userId, packageName) returns
+ // null, the UPTC was never inserted.
+ ArrayMap<String, T> data = mData.get(userId, packageName);
+ return data != null && data.containsKey(tag);
+ }
+
+ /** Removes all the data for the user, if there was any. */
+ public void delete(int userId) {
+ mData.delete(userId);
+ }
+
+ /** Removes the data for the user, package, and tag, if there was any. */
+ public void delete(int userId, @NonNull String packageName, @Nullable String tag) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data != null) {
+ data.remove(tag);
+ if (data.size() == 0) {
+ mData.delete(userId, packageName);
+ }
+ }
+ }
+
+ /** Removes the data for the user and package, if there was any. */
+ public ArrayMap<String, T> delete(int userId, @NonNull String packageName) {
+ return mData.delete(userId, packageName);
+ }
+
+ /**
+ * Returns the set of tag -> object mappings for the given userId and packageName
+ * combination.
+ */
+ @Nullable
+ public ArrayMap<String, T> get(int userId, @NonNull String packageName) {
+ return mData.get(userId, packageName);
+ }
+
+ /** Returns the saved object for the given UPTC. */
+ @Nullable
+ public T get(int userId, @NonNull String packageName, @Nullable String tag) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ return data != null ? data.get(tag) : null;
+ }
+
+ /**
+ * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId,
+ * or a negative number if the specified userId is not mapped.
+ */
+ 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);
+ }
+
+ /** Returns the userId at the given index. */
+ public int getUserIdAtIndex(int index) {
+ return mData.keyAt(index);
+ }
+
+ /** Returns the package name at the given index. */
+ @NonNull
+ public 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) {
+ // 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);
+ }
+
+ /** Returns the size of the outer (userId) array. */
+ public int userCount() {
+ return mData.numMaps();
+ }
+
+ /** Returns the number of packages saved for a given userId. */
+ public int packageCountForUser(int userId) {
+ return mData.numElementsForKey(userId);
+ }
+
+ /** Returns the number of tags saved for a given userId-packageName combination. */
+ public int tagCountForUserAndPackage(int userId, @NonNull String packageName) {
+ final ArrayMap data = mData.get(userId, packageName);
+ return data != null ? data.size() : 0;
+ }
+
+ /** Returns the value T at the given user, package, and tag indices. */
+ @Nullable
+ public T valueAt(int userIndex, int packageIndex, int tagIndex) {
+ final ArrayMap<String, T> data = mData.valueAt(userIndex, packageIndex);
+ return data != null ? data.valueAt(tagIndex) : null;
+ }
+
+ public void forEach(Consumer<T> consumer) {
+ mData.forEach((tagMap) -> {
+ for (int i = tagMap.size() - 1; i >= 0; --i) {
+ consumer.accept(tagMap.valueAt(i));
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index c57ac11..b154da4 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -228,11 +228,10 @@
}
}
- public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
- final int displayId = windowState.getDisplayId();
+ public void onAppWindowTransitionLocked(int displayId, int transition) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.onAppWindowTransitionLocked(windowState, transition);
+ displayMagnifier.onAppWindowTransitionLocked(displayId, transition);
}
// Not relevant for the window observer.
}
@@ -446,11 +445,11 @@
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
}
- public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
+ public void onAppWindowTransitionLocked(int displayId, int transition) {
if (DEBUG_WINDOW_TRANSITIONS) {
Slog.i(LOG_TAG, "Window transition: "
+ AppTransition.appTransitionToString(transition)
- + " displayId: " + windowState.getDisplayId());
+ + " displayId: " + displayId);
}
final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
if (magnifying) {
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
deleted file mode 100644
index 84152e8..0000000
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ /dev/null
@@ -1,1396 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.os.Build.VERSION_CODES.N;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.FLAG_PRIVATE;
-import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
-
-import static com.android.server.am.ActivityDisplayProto.DISPLAY;
-import static com.android.server.am.ActivityDisplayProto.FOCUSED_STACK_ID;
-import static com.android.server.am.ActivityDisplayProto.ID;
-import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY;
-import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE;
-import static com.android.server.am.ActivityDisplayProto.STACKS;
-import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
-import static com.android.server.wm.RootActivityContainer.FindTaskResult;
-import static com.android.server.wm.RootActivityContainer.TAG_STATES;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.ActivityOptions;
-import android.app.WindowConfiguration;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.IntArray;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.Display;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.protolog.common.ProtoLog;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Exactly one of these classes per Display in the system. Capable of holding zero or more
- * attached {@link ActivityStack}s.
- */
-class ActivityDisplay extends DisplayContent {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_ATM;
- private static final String TAG_STACK = TAG + POSTFIX_STACK;
-
- static final int POSITION_TOP = Integer.MAX_VALUE;
- static final int POSITION_BOTTOM = Integer.MIN_VALUE;
-
- /**
- * Counter for next free stack ID to use for dynamic activity stacks. Unique across displays.
- */
- private static int sNextFreeStackId = 0;
-
- private RootActivityContainer mRootActivityContainer;
- /** Actual Display this object tracks. */
- int mDisplayId;
- Display mDisplay;
-
- /**
- * All of the stacks on this display. Order matters, topmost stack is in front of all other
- * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls
- * changing the list should also call {@link #onStackOrderChanged()}.
- */
- private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>();
-
- /** Array of all UIDs that are present on the display. */
- private IntArray mDisplayAccessUIDs = new IntArray();
-
- /** All tokens used to put activities on this stack to sleep (including mOffToken) */
- final ArrayList<ActivityTaskManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
- /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
- ActivityTaskManagerInternal.SleepToken mOffToken;
-
- private boolean mSleeping;
-
- /** We started the process of removing the display from the system. */
- private boolean mRemoving;
-
- /**
- * The display is removed from the system and we are just waiting for all activities on it to be
- * finished before removing this object.
- */
- private boolean mRemoved;
-
- /** The display can only contain one task. */
- private boolean mSingleTaskInstance;
-
- /**
- * Non-null if the last size compatibility mode activity is using non-native screen
- * configuration. The activity is not able to put in multi-window mode, so it exists only one
- * per display.
- */
- private ActivityRecord mLastCompatModeActivity;
-
- /**
- * A focusable stack that is purposely to be positioned at the top. Although the stack may not
- * have the topmost index, it is used as a preferred candidate to prevent being unable to resume
- * target stack properly when there are other focusable always-on-top stacks.
- */
- private ActivityStack mPreferredTopFocusableStack;
-
- /**
- * If this is the same as {@link #getFocusedStack} then the activity on the top of the focused
- * stack has been resumed. If stacks are changing position this will hold the old stack until
- * the new stack becomes resumed after which it will be set to current focused stack.
- */
- private ActivityStack mLastFocusedStack;
-
- // Used in updating the display size
- private Point mTmpDisplaySize = new Point();
-
- // Used in updating override configurations
- private final Configuration mTempConfig = new Configuration();
-
- private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();
-
- ActivityDisplay(RootActivityContainer root, Display display) {
- super(display, root.mWindowManager);
- mRootActivityContainer = root;
- mDisplayId = display.getDisplayId();
- mDisplay = display;
-
- if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
-
- mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
-
- if (mWmService.mDisplayManagerInternal != null) {
- mWmService.mDisplayManagerInternal
- .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
- configureDisplayPolicy();
- }
-
- reconfigureDisplayLocked();
- onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
- mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
- }
-
- void onDisplayChanged() {
- // The window policy is responsible for stopping activities on the default display.
- final int displayId = mDisplay.getDisplayId();
- if (displayId != DEFAULT_DISPLAY) {
- final int displayState = mDisplay.getState();
- if (displayState == Display.STATE_OFF && mOffToken == null) {
- mOffToken = mAtmService.acquireSleepToken("Display-off", displayId);
- } else if (displayState == Display.STATE_ON && mOffToken != null) {
- mOffToken.release();
- mOffToken = null;
- }
- }
-
- mDisplay.getRealSize(mTmpDisplaySize);
- setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
- updateDisplayInfo();
- mWmService.requestTraversal();
- }
-
- void addStack(ActivityStack stack, int position) {
- setStackOnDisplay(stack, position);
- positionStackAt(stack, position);
- mAtmService.updateSleepIfNeededLocked();
- }
-
- void onStackRemoved(ActivityStack stack) {
- if (DEBUG_STACK) {
- Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId=" + mDisplayId);
- }
- if (mPreferredTopFocusableStack == stack) {
- mPreferredTopFocusableStack = null;
- }
- releaseSelfIfNeeded();
- mAtmService.updateSleepIfNeededLocked();
- onStackOrderChanged(stack);
- }
-
- void positionStackAtTop(ActivityStack stack, boolean includingParents) {
- positionStackAtTop(stack, includingParents, null /* updateLastFocusedStackReason */);
- }
-
- void positionStackAtTop(ActivityStack stack, boolean includingParents,
- String updateLastFocusedStackReason) {
- positionStackAt(stack, getStackCount(), includingParents, updateLastFocusedStackReason);
- }
-
- void positionStackAtBottom(ActivityStack stack) {
- positionStackAtBottom(stack, null /* updateLastFocusedStackReason */);
- }
-
- void positionStackAtBottom(ActivityStack stack, String updateLastFocusedStackReason) {
- positionStackAt(stack, 0, false /* includingParents */, updateLastFocusedStackReason);
- }
-
- private void positionStackAt(ActivityStack stack, int position) {
- positionStackAt(stack, position, false /* includingParents */,
- null /* updateLastFocusedStackReason */);
- }
-
- private void positionStackAt(ActivityStack stack, int position, boolean includingParents,
- String updateLastFocusedStackReason) {
- // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
- // the position internally, also update the logic here
- final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null
- ? getFocusedStack() : null;
- final boolean wasContained = getIndexOf(stack) >= 0;
- if (mSingleTaskInstance && getStackCount() == 1 && !wasContained) {
- throw new IllegalStateException(
- "positionStackAt: Can only have one task on display=" + this);
- }
-
- // Since positionChildAt() is called during the creation process of pinned stacks,
- // ActivityStack#getStack() can be null.
- positionStackAt(position, stack, includingParents);
-
- // The insert position may be adjusted to non-top when there is always-on-top stack. Since
- // the original position is preferred to be top, the stack should have higher priority when
- // we are looking for top focusable stack. The condition {@code wasContained} restricts the
- // preferred stack is set only when moving an existing stack to top instead of adding a new
- // stack that may be too early (e.g. in the middle of launching or reparenting).
- if (wasContained && position >= getStackCount() - 1 && stack.isFocusableAndVisible()) {
- mPreferredTopFocusableStack = stack;
- } else if (mPreferredTopFocusableStack == stack) {
- mPreferredTopFocusableStack = null;
- }
-
- if (updateLastFocusedStackReason != null) {
- final ActivityStack currentFocusedStack = getFocusedStack();
- if (currentFocusedStack != prevFocusedStack) {
- mLastFocusedStack = prevFocusedStack;
- EventLogTags.writeWmFocusedStack(mRootActivityContainer.mCurrentUser, mDisplayId,
- currentFocusedStack == null ? -1 : currentFocusedStack.getStackId(),
- mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(),
- updateLastFocusedStackReason);
- }
- }
-
- onStackOrderChanged(stack);
- }
-
- ActivityStack getStack(int stackId) {
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack stack = getStackAt(i);
- if (stack.mStackId == stackId) {
- return stack;
- }
- }
- return null;
- }
-
- boolean alwaysCreateStack(int windowingMode, int activityType) {
- // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
- // modes so that we can manage visual ordering and return types correctly.
- return activityType == ACTIVITY_TYPE_STANDARD
- && (windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_FREEFORM
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- }
-
- /**
- * Returns an existing stack compatible with the windowing mode and activity type or creates one
- * if a compatible stack doesn't exist.
- * @see #getStack(int, int)
- * @see #createStack(int, int, boolean)
- */
- ActivityStack getOrCreateStack(int windowingMode, int activityType,
- boolean onTop) {
- if (!alwaysCreateStack(windowingMode, activityType)) {
- ActivityStack stack = getStack(windowingMode, activityType);
- if (stack != null) {
- return stack;
- }
- }
- return createStack(windowingMode, activityType, onTop);
- }
-
- /**
- * Returns an existing stack compatible with the input params or creates one
- * if a compatible stack doesn't exist.
- * @see #getOrCreateStack(int, int, boolean)
- */
- ActivityStack getOrCreateStack(@Nullable ActivityRecord r,
- @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
- boolean onTop) {
- // First preference is the windowing mode in the activity options if set.
- int windowingMode = (options != null)
- ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
- // Validate that our desired windowingMode will work under the current conditions.
- // UNDEFINED windowing mode is a valid result and means that the new stack will inherit
- // it's display's windowing mode.
- windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
- return getOrCreateStack(windowingMode, activityType, onTop);
- }
-
- @VisibleForTesting
- int getNextStackId() {
- return sNextFreeStackId++;
- }
-
- /**
- * Creates a stack matching the input windowing mode and activity type on this display.
- * @param windowingMode The windowing mode the stack should be created in. If
- * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
- * inherit it's parent's windowing mode.
- * @param activityType The activityType the stack should be created in. If
- * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
- * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- * @param onTop If true the stack will be created at the top of the display, else at the bottom.
- * @return The newly created stack.
- */
- ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
-
- if (mSingleTaskInstance && getStackCount() > 0) {
- // Create stack on default display instead since this display can only contain 1 stack.
- // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
- // this goes away once ActivityView is no longer using virtual displays.
- return mRootActivityContainer.getDefaultDisplay().createStack(
- windowingMode, activityType, onTop);
- }
-
- if (activityType == ACTIVITY_TYPE_UNDEFINED) {
- // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
- // anything else should be passing it in anyways...
- activityType = ACTIVITY_TYPE_STANDARD;
- }
-
- if (activityType != ACTIVITY_TYPE_STANDARD) {
- // For now there can be only one stack of a particular non-standard activity type on a
- // display. So, get that ignoring whatever windowing mode it is currently in.
- ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
- if (stack != null) {
- throw new IllegalArgumentException("Stack=" + stack + " of activityType="
- + activityType + " already on display=" + this + ". Can't have multiple.");
- }
- }
-
- if (!isWindowingModeSupported(windowingMode, mAtmService.mSupportsMultiWindow,
- mAtmService.mSupportsSplitScreenMultiWindow,
- mAtmService.mSupportsFreeformWindowManagement,
- mAtmService.mSupportsPictureInPicture, activityType)) {
- throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
- + windowingMode);
- }
-
- final int stackId = getNextStackId();
- return createStackUnchecked(windowingMode, activityType, stackId, onTop);
- }
-
- @VisibleForTesting
- ActivityStack createStackUnchecked(int windowingMode, int activityType,
- int stackId, boolean onTop) {
- if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
- throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
- + "activity type.");
- }
- return new ActivityStack(this, stackId,
- mRootActivityContainer.mStackSupervisor, windowingMode, activityType, onTop);
- }
-
- /**
- * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a
- * focusable and visible stack from the top of stacks in this display.
- */
- ActivityStack getFocusedStack() {
- if (mPreferredTopFocusableStack != null) {
- return mPreferredTopFocusableStack;
- }
-
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack stack = getStackAt(i);
- if (stack.isFocusableAndVisible()) {
- return stack;
- }
- }
-
- return null;
- }
-
- ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) {
- final int currentWindowingMode = currentFocus != null
- ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
- ActivityStack candidate = null;
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack stack = getStackAt(i);
- if (ignoreCurrent && stack == currentFocus) {
- continue;
- }
- if (!stack.isFocusableAndVisible()) {
- continue;
- }
-
- if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) {
- // If the currently focused stack is in split-screen secondary we save off the
- // top primary split-screen stack as a candidate for focus because we might
- // prefer focus to move to an other stack to avoid primary split-screen stack
- // overlapping with a fullscreen stack when a fullscreen stack is higher in z
- // than the next split-screen stack. Assistant stack, I am looking at you...
- // We only move the focus to the primary-split screen stack if there isn't a
- // better alternative.
- candidate = stack;
- continue;
- }
- if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) {
- // Use the candidate stack since we are now at the secondary split-screen.
- return candidate;
- }
- return stack;
- }
- return candidate;
- }
-
- ActivityRecord getResumedActivity() {
- final ActivityStack focusedStack = getFocusedStack();
- if (focusedStack == null) {
- return null;
- }
- // TODO(b/111541062): Move this into ActivityStack#getResumedActivity()
- // Check if the focused stack has the resumed activity
- ActivityRecord resumedActivity = focusedStack.getResumedActivity();
- if (resumedActivity == null || resumedActivity.app == null) {
- // If there is no registered resumed activity in the stack or it is not running -
- // try to use previously resumed one.
- resumedActivity = focusedStack.mPausingActivity;
- if (resumedActivity == null || resumedActivity.app == null) {
- // If previously resumed activity doesn't work either - find the topmost running
- // activity that can be focused.
- resumedActivity = focusedStack.topRunningActivityLocked(true /* focusableOnly */);
- }
- }
- return resumedActivity;
- }
-
- ActivityStack getLastFocusedStack() {
- return mLastFocusedStack;
- }
-
- boolean allResumedActivitiesComplete() {
- for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityRecord r = getStackAt(stackNdx).getResumedActivity();
- if (r != null && !r.isState(RESUMED)) {
- return false;
- }
- }
- final ActivityStack currentFocusedStack = getFocusedStack();
- if (DEBUG_STACK) {
- Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from="
- + mLastFocusedStack + " to=" + currentFocusedStack);
- }
- mLastFocusedStack = currentFocusedStack;
- return true;
- }
-
- /**
- * Pause all activities in either all of the stacks or just the back stacks. This is done before
- * resuming a new activity and to make sure that previously active activities are
- * paused in stacks that are no longer visible or in pinned windowing mode. This does not
- * pause activities in visible stacks, so if an activity is launched within the same stack/task,
- * then we should explicitly pause that stack's top activity.
- * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
- * @param resuming The resuming activity.
- * @return {@code true} if any activity was paused as a result of this call.
- */
- boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
- boolean someActivityPaused = false;
- for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = getStackAt(stackNdx);
- final ActivityRecord resumedActivity = stack.getResumedActivity();
- if (resumedActivity != null
- && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
- || !stack.isFocusable())) {
- if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
- " mResumedActivity=" + resumedActivity);
- someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
- resuming);
- }
- }
- return someActivityPaused;
- }
-
- /**
- * Find task for putting the Activity in.
- */
- void findTaskLocked(final ActivityRecord r, final boolean isPreferredDisplay,
- FindTaskResult result) {
- mTmpFindTaskResult.clear();
- for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = getStackAt(stackNdx);
- if (!r.hasCompatibleActivityType(stack)) {
- if (DEBUG_TASKS) {
- Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack);
- }
- continue;
- }
-
- mTmpFindTaskResult.process(r, stack);
- // It is possible to have tasks in multiple stacks with the same root affinity, so
- // we should keep looking after finding an affinity match to see if there is a
- // better match in another stack. Also, task affinity isn't a good enough reason
- // to target a display which isn't the source of the intent, so skip any affinity
- // matches not on the specified display.
- if (mTmpFindTaskResult.mRecord != null) {
- if (mTmpFindTaskResult.mIdealMatch) {
- result.setTo(mTmpFindTaskResult);
- return;
- } else if (isPreferredDisplay) {
- // Note: since the traversing through the stacks is top down, the floating
- // tasks should always have lower priority than any affinity-matching tasks
- // in the fullscreen stacks
- result.setTo(mTmpFindTaskResult);
- }
- }
- }
- }
-
- /**
- * Removes stacks in the input windowing modes from the system if they are of activity type
- * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
- */
- void removeStacksInWindowingModes(int... windowingModes) {
- if (windowingModes == null || windowingModes.length == 0) {
- return;
- }
-
- // Collect the stacks that are necessary to be removed instead of performing the removal
- // by looping mStacks, so that we don't miss any stacks after the stack size changed or
- // stacks reordered.
- final ArrayList<ActivityStack> stacks = new ArrayList<>();
- for (int j = windowingModes.length - 1 ; j >= 0; --j) {
- final int windowingMode = windowingModes[j];
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack stack = getStackAt(i);
- if (!stack.isActivityTypeStandardOrUndefined()) {
- continue;
- }
- if (stack.getWindowingMode() != windowingMode) {
- continue;
- }
- stacks.add(stack);
- }
- }
-
- for (int i = stacks.size() - 1; i >= 0; --i) {
- mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i));
- }
- }
-
- void removeStacksWithActivityTypes(int... activityTypes) {
- if (activityTypes == null || activityTypes.length == 0) {
- return;
- }
-
- // Collect the stacks that are necessary to be removed instead of performing the removal
- // by looping mStacks, so that we don't miss any stacks after the stack size changed or
- // stacks reordered.
- final ArrayList<ActivityStack> stacks = new ArrayList<>();
- for (int j = activityTypes.length - 1 ; j >= 0; --j) {
- final int activityType = activityTypes[j];
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack stack = getStackAt(i);
- if (stack.getActivityType() == activityType) {
- stacks.add(stack);
- }
- }
- }
-
- for (int i = stacks.size() - 1; i >= 0; --i) {
- mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i));
- }
- }
-
- void onSplitScreenModeDismissed() {
- mAtmService.deferWindowLayout();
- try {
- // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack otherStack = getStackAt(i);
- if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
- continue;
- }
- otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */,
- false /* showRecents */, false /* enteringSplitScreenMode */,
- true /* deferEnsuringVisibility */, false /* creating */);
- }
- } finally {
- final ActivityStack topFullscreenStack =
- getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
- final ActivityStack homeStack = getHomeStack();
- if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
- // Whenever split-screen is dismissed we want the home stack directly behind the
- // current top fullscreen stack so it shows up when the top stack is finished.
- // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
- // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
- // once we have that.
- homeStack.moveToFront("onSplitScreenModeDismissed");
- topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
- }
- mAtmService.continueWindowLayout();
- }
- }
-
- void onSplitScreenModeActivated() {
- mAtmService.deferWindowLayout();
- try {
- // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
- final ActivityStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack otherStack = getStackAt(i);
- if (otherStack == splitScreenPrimaryStack
- || !otherStack.affectedBySplitScreenResize()) {
- continue;
- }
- otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
- false /* animate */, false /* showRecents */,
- true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
- false /* creating */);
- }
- } finally {
- mAtmService.continueWindowLayout();
- }
- }
-
- /**
- * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
- * @param windowingMode The windowing mode we are checking support for.
- * @param supportsMultiWindow If we should consider support for multi-window mode in general.
- * @param supportsSplitScreen If we should consider support for split-screen multi-window.
- * @param supportsFreeform If we should consider support for freeform multi-window.
- * @param supportsPip If we should consider support for picture-in-picture mutli-window.
- * @param activityType The activity type under consideration.
- * @return true if the windowing mode is supported.
- */
- private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
- boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
- int activityType) {
-
- if (windowingMode == WINDOWING_MODE_UNDEFINED
- || windowingMode == WINDOWING_MODE_FULLSCREEN) {
- return true;
- }
- if (!supportsMultiWindow) {
- return false;
- }
-
- final int displayWindowingMode = getWindowingMode();
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- return supportsSplitScreen
- && WindowConfiguration.supportSplitScreenWindowingMode(activityType)
- // Freeform windows and split-screen windows don't mix well, so prevent
- // split windowing modes on freeform displays.
- && displayWindowingMode != WINDOWING_MODE_FREEFORM;
- }
-
- if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
- return false;
- }
-
- if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
- return false;
- }
- return true;
- }
-
- /**
- * Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this
- * display with the provided parameters.
- *
- * @param r The ActivityRecord in question.
- * @param options Options to start with.
- * @param task The task within-which the activity would start.
- * @param activityType The type of activity to start.
- * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
- */
- int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
- @Nullable Task task, int activityType) {
-
- // First preference if the windowing mode in the activity options if set.
- int windowingMode = (options != null)
- ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
- // If windowing mode is unset, then next preference is the candidate task, then the
- // activity record.
- if (windowingMode == WINDOWING_MODE_UNDEFINED) {
- if (task != null) {
- windowingMode = task.getWindowingMode();
- }
- if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
- windowingMode = r.getWindowingMode();
- }
- if (windowingMode == WINDOWING_MODE_UNDEFINED) {
- // Use the display's windowing mode.
- windowingMode = getWindowingMode();
- }
- }
- windowingMode = validateWindowingMode(windowingMode, r, task, activityType);
- return windowingMode != WINDOWING_MODE_UNDEFINED
- ? windowingMode : WINDOWING_MODE_FULLSCREEN;
- }
-
- /**
- * Check that the requested windowing-mode is appropriate for the specified task and/or activity
- * on this display.
- *
- * @param windowingMode The windowing-mode to validate.
- * @param r The {@link ActivityRecord} to check against.
- * @param task The {@link Task} to check against.
- * @param activityType An activity type.
- * @return The provided windowingMode or the closest valid mode which is appropriate.
- */
- int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
- int activityType) {
- // Make sure the windowing mode we are trying to use makes sense for what is supported.
- boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
- boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow;
- boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement;
- boolean supportsPip = mAtmService.mSupportsPictureInPicture;
- if (supportsMultiWindow) {
- if (task != null) {
- supportsMultiWindow = task.isResizeable();
- supportsSplitScreen = task.supportsSplitScreenWindowingMode();
- // TODO: Do we need to check for freeform and Pip support here?
- } else if (r != null) {
- supportsMultiWindow = r.isResizeable();
- supportsSplitScreen = r.supportsSplitScreenWindowingMode();
- supportsFreeform = r.supportsFreeform();
- supportsPip = r.supportsPictureInPicture();
- }
- }
-
- final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
- if (!inSplitScreenMode
- && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
- // Switch to the display's windowing mode if we are not in split-screen mode and we are
- // trying to launch in split-screen secondary.
- windowingMode = WINDOWING_MODE_UNDEFINED;
- } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_UNDEFINED)
- && supportsSplitScreen) {
- windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- }
-
- if (windowingMode != WINDOWING_MODE_UNDEFINED
- && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
- supportsFreeform, supportsPip, activityType)) {
- return windowingMode;
- }
- return WINDOWING_MODE_UNDEFINED;
- }
-
- boolean isTopStack(ActivityStack stack) {
- return stack == getTopStack();
- }
-
- boolean isTopNotPinnedStack(ActivityStack stack) {
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack current = getStackAt(i);
- if (!current.inPinnedWindowingMode()) {
- return current == stack;
- }
- }
- return false;
- }
-
- ActivityStack getTopStackInWindowingMode(int windowingMode) {
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack current = getStackAt(i);
- if (windowingMode == current.getWindowingMode()) {
- return current;
- }
- }
- return null;
- }
-
- ActivityRecord topRunningActivity() {
- return topRunningActivity(false /* considerKeyguardState */);
- }
-
- /**
- * Returns the top running activity in the focused stack. In the case the focused stack has no
- * such activity, the next focusable stack on this display is returned.
- *
- * @param considerKeyguardState Indicates whether the locked state should be considered. if
- * {@code true} and the keyguard is locked, only activities that
- * can be shown on top of the keyguard will be considered.
- * @return The top running activity. {@code null} if none is available.
- */
- ActivityRecord topRunningActivity(boolean considerKeyguardState) {
- ActivityRecord topRunning = null;
- final ActivityStack focusedStack = getFocusedStack();
- if (focusedStack != null) {
- topRunning = focusedStack.topRunningActivityLocked();
- }
-
- // Look in other focusable stacks.
- if (topRunning == null) {
- 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()) {
- continue;
- }
- topRunning = stack.topRunningActivityLocked();
- if (topRunning != null) {
- break;
- }
- }
- }
-
- // This activity can be considered the top running activity if we are not considering
- // the locked state, the keyguard isn't locked, or we can show when locked.
- if (topRunning != null && considerKeyguardState
- && mRootActivityContainer.mStackSupervisor.getKeyguardController().isKeyguardLocked()
- && !topRunning.canShowWhenLocked()) {
- return null;
- }
-
- return topRunning;
- }
-
- boolean updateDisplayOverrideConfigurationLocked() {
- Configuration values = new Configuration();
- computeScreenConfiguration(values);
-
- mAtmService.mH.sendMessage(PooledLambda.obtainMessage(
- ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal,
- mDisplayId));
-
- Settings.System.clearConfiguration(values);
- updateDisplayOverrideConfigurationLocked(values, null /* starting */,
- false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
- return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
- }
-
- /**
- * Updates override configuration specific for the selected display. If no config is provided,
- * new one will be computed in WM based on current display info.
- */
- boolean updateDisplayOverrideConfigurationLocked(Configuration values,
- ActivityRecord starting, boolean deferResume,
- ActivityTaskManagerService.UpdateConfigurationResult result) {
-
- int changes = 0;
- boolean kept = true;
-
- mAtmService.deferWindowLayout();
- try {
- if (values != null) {
- if (mDisplayId == DEFAULT_DISPLAY) {
- // Override configuration of the default display duplicates global config, so
- // we're calling global config update instead for default display. It will also
- // apply the correct override config.
- changes = mAtmService.updateGlobalConfigurationLocked(values,
- false /* initLocale */, false /* persistent */,
- UserHandle.USER_NULL /* userId */, deferResume);
- } else {
- changes = performDisplayOverrideConfigUpdate(values, deferResume);
- }
- }
-
- kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
- } finally {
- mAtmService.continueWindowLayout();
- }
-
- if (result != null) {
- result.changes = changes;
- result.activityRelaunched = !kept;
- }
- return kept;
- }
-
- int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume) {
- mTempConfig.setTo(getRequestedOverrideConfiguration());
- final int changes = mTempConfig.updateFrom(values);
- if (changes != 0) {
- Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
- + mTempConfig + " for displayId=" + mDisplayId);
- onRequestedOverrideConfigurationChanged(mTempConfig);
-
- final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
- if (isDensityChange && mDisplayId == DEFAULT_DISPLAY) {
- mAtmService.mAppWarnings.onDensityChanged();
-
- // Post message to start process to avoid possible deadlock of calling into AMS with
- // the ATMS lock held.
- final Message msg = PooledLambda.obtainMessage(
- ActivityManagerInternal::killAllBackgroundProcessesExcept,
- mAtmService.mAmInternal, N,
- ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
- mAtmService.mH.sendMessage(msg);
- }
- mWmService.mDisplayNotificationController.dispatchDisplayChanged(
- this, getConfiguration());
- }
- return changes;
- }
-
- @Override
- public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
- final int currRotation =
- getRequestedOverrideConfiguration().windowConfiguration.getRotation();
- if (currRotation != ROTATION_UNDEFINED
- && currRotation != overrideConfiguration.windowConfiguration.getRotation()) {
- applyRotationLocked(currRotation,
- overrideConfiguration.windowConfiguration.getRotation());
- }
- super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
- mWmService.setNewDisplayOverrideConfiguration(overrideConfiguration, this);
- mAtmService.addWindowLayoutReasons(
- ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newParentConfig) {
- // update resources before cascade so that docked/pinned stacks use the correct info
- preOnConfigurationChanged();
- super.onConfigurationChanged(newParentConfig);
- }
-
- void onLockTaskPackagesUpdated() {
- for (int i = getStackCount() - 1; i >= 0; --i) {
- getStackAt(i).onLockTaskPackagesUpdated();
- }
- }
-
- /** Checks whether the given activity is in size compatibility mode and notifies the change. */
- void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
- if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
- // The callback is only interested in the foreground changes of fullscreen activity.
- return;
- }
- if (!r.inSizeCompatMode()) {
- if (mLastCompatModeActivity != null) {
- mAtmService.getTaskChangeNotificationController()
- .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
- }
- mLastCompatModeActivity = null;
- return;
- }
- if (mLastCompatModeActivity == r) {
- return;
- }
- mLastCompatModeActivity = r;
- mAtmService.getTaskChangeNotificationController()
- .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
- }
-
- @Override
- public String toString() {
- return "ActivityDisplay={" + mDisplayId + " numStacks=" + getStackCount() + "}";
- }
-
- boolean isPrivate() {
- return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
- }
-
- boolean isUidPresent(int uid) {
- final PooledPredicate p = PooledLambda.obtainPredicate(
- ActivityRecord::isUid, PooledLambda.__(ActivityRecord.class), uid);
- final boolean isUidPresent = mDisplayContent.getActivity(p) != null;
- p.recycle();
- return isUidPresent;
- }
-
- /**
- * @see #mRemoved
- */
- boolean isRemoved() {
- return mRemoved;
- }
-
- /**
- * @see #mRemoving
- */
- boolean isRemoving() {
- return mRemoving;
- }
-
- void remove() {
- mRemoving = true;
- final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove();
- ActivityStack lastReparentedStack = null;
- mPreferredTopFocusableStack = null;
-
- // Stacks could be reparented from the removed display to other display. While
- // reparenting the last stack of the removed display, the remove display is ready to be
- // released (no more ActivityStack). But, we cannot release it at that moment or the
- // related WindowContainer will also be removed. So, we set display as removed after
- // reparenting stack finished.
- final ActivityDisplay toDisplay = mRootActivityContainer.getDefaultDisplay();
- mRootActivityContainer.mStackSupervisor.beginDeferResume();
- try {
- int numStacks = getStackCount();
- // Keep the order from bottom to top.
- for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
- final ActivityStack stack = getStackAt(stackNdx);
- // Always finish non-standard type stacks.
- if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) {
- stack.finishAllActivitiesImmediately();
- } else {
- // If default display is in split-window mode, set windowing mode of the stack
- // to split-screen secondary. Otherwise, set the windowing mode to undefined by
- // default to let stack inherited the windowing mode from the new display.
- final int windowingMode = toDisplay.hasSplitScreenPrimaryStack()
- ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- : WINDOWING_MODE_UNDEFINED;
- stack.reparent(toDisplay, true /* onTop */);
- stack.setWindowingMode(windowingMode);
- lastReparentedStack = stack;
- }
- // Stacks may be removed from this display. Ensure each stack will be processed and
- // the loop will end.
- stackNdx -= numStacks - getStackCount();
- numStacks = getStackCount();
- }
- } finally {
- mRootActivityContainer.mStackSupervisor.endDeferResume();
- }
- mRemoved = true;
-
- // Only update focus/visibility for the last one because there may be many stacks are
- // reparented and the intermediate states are unnecessary.
- if (lastReparentedStack != null) {
- lastReparentedStack.postReparent();
- }
- releaseSelfIfNeeded();
-
- if (!mAllSleepTokens.isEmpty()) {
- mRootActivityContainer.mSleepTokens.removeAll(mAllSleepTokens);
- mAllSleepTokens.clear();
- mAtmService.updateSleepIfNeededLocked();
- }
- }
-
- private void releaseSelfIfNeeded() {
- if (!mRemoved) {
- return;
- }
-
- final ActivityStack stack = getStackCount() == 1 ? getStackAt(0) : null;
- if (stack != null && stack.isActivityTypeHome() && stack.getAllTasks().isEmpty()) {
- // Release this display if an empty home stack is the only thing left.
- // Since it is the last stack, this display will be released along with the stack
- // removal.
- stack.removeIfPossible();
- } else if (getTopStack() == null) {
- removeIfPossible();
- mRootActivityContainer.removeChild(this);
- mRootActivityContainer.mStackSupervisor
- .getKeyguardController().onDisplayRemoved(mDisplayId);
- }
- }
-
- /** Update and get all UIDs that are present on the display and have access to it. */
- IntArray getPresentUIDs() {
- mDisplayAccessUIDs.clear();
- final PooledConsumer c = PooledLambda.obtainConsumer(ActivityDisplay::addActivityUid,
- PooledLambda.__(ActivityRecord.class), mDisplayAccessUIDs);
- mDisplayContent.forAllActivities(c);
- c.recycle();
- return mDisplayAccessUIDs;
- }
-
- private static void addActivityUid(ActivityRecord r, IntArray uids) {
- uids.add(r.getUid());
- }
-
- @VisibleForTesting
- boolean shouldDestroyContentOnRemove() {
- return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
- }
-
- boolean shouldSleep() {
- return (getStackCount() == 0 || !mAllSleepTokens.isEmpty())
- && (mAtmService.mRunningVoice == null);
- }
-
- void setFocusedApp(ActivityRecord r, boolean moveFocusNow) {
- final ActivityRecord newFocus;
- final IBinder token = r.appToken;
- if (token == null) {
- ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Clearing focused app, displayId=%d",
- mDisplayId);
- newFocus = null;
- } else {
- newFocus = mWmService.mRoot.getActivityRecord(token);
- if (newFocus == null) {
- Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token
- + ", displayId=" + mDisplayId);
- }
- ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
- "Set focused app to: %s moveFocusNow=%b displayId=%d", newFocus,
- moveFocusNow, mDisplayId);
- }
-
- final boolean changed = setFocusedApp(newFocus);
- if (moveFocusNow && changed) {
- mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
- true /*updateInputWindows*/);
- }
- }
-
- /**
- * @return the stack currently above the {@param stack}. Can be null if the {@param stack} is
- * already top-most.
- */
- ActivityStack getStackAbove(ActivityStack stack) {
- final int stackIndex = getIndexOf(stack) + 1;
- return (stackIndex < getStackCount()) ? getStackAt(stackIndex) : null;
- }
-
- /**
- * Adjusts the {@param stack} behind the last visible stack in the display if necessary.
- * Generally used in conjunction with {@link #moveStackBehindStack}.
- */
- void moveStackBehindBottomMostVisibleStack(ActivityStack stack) {
- if (stack.shouldBeVisible(null)) {
- // Skip if the stack is already visible
- return;
- }
-
- // Move the stack to the bottom to not affect the following visibility checks
- positionStackAtBottom(stack);
-
- // Find the next position where the stack should be placed
- final int numStacks = getStackCount();
- for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
- final ActivityStack s = getStackAt(stackNdx);
- if (s == stack) {
- continue;
- }
- final int winMode = s.getWindowingMode();
- final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN ||
- winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- if (s.shouldBeVisible(null) && isValidWindowingMode) {
- // Move the provided stack to behind this stack
- positionStackAt(stack, Math.max(0, stackNdx - 1));
- break;
- }
- }
- }
-
- /**
- * Moves the {@param stack} behind the given {@param behindStack} if possible. If
- * {@param behindStack} is not currently in the display, then then the stack is moved to the
- * back. Generally used in conjunction with {@link #moveStackBehindBottomMostVisibleStack}.
- */
- void moveStackBehindStack(ActivityStack stack, ActivityStack behindStack) {
- if (behindStack == null || behindStack == stack) {
- return;
- }
-
- // Note that positionChildAt will first remove the given stack before inserting into the
- // list, so we need to adjust the insertion index to account for the removed index
- // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
- // position internally
- final int stackIndex = getIndexOf(stack);
- final int behindStackIndex = getIndexOf(behindStack);
- final int insertIndex = stackIndex <= behindStackIndex
- ? behindStackIndex - 1 : behindStackIndex;
- positionStackAt(stack, Math.max(0, insertIndex));
- }
-
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
- for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = getStackAt(stackNdx);
- stack.ensureActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
- }
- }
-
- void moveHomeStackToFront(String reason) {
- final ActivityStack homeStack = getHomeStack();
- if (homeStack != null) {
- homeStack.moveToFront(reason);
- }
- }
-
- /**
- * Moves the focusable home activity to top. If there is no such activity, the home stack will
- * still move to top.
- */
- void moveHomeActivityToTop(String reason) {
- final ActivityRecord top = getHomeActivity();
- if (top == null) {
- moveHomeStackToFront(reason);
- return;
- }
- top.moveFocusableActivityToTop(reason);
- }
-
- @Nullable
- ActivityRecord getHomeActivity() {
- return getHomeActivityForUser(mRootActivityContainer.mCurrentUser);
- }
-
- @Nullable
- ActivityRecord getHomeActivityForUser(int userId) {
- final ActivityStack homeStack = getHomeStack();
- if (homeStack == null) {
- return null;
- }
-
- final PooledPredicate p = PooledLambda.obtainPredicate(
- ActivityDisplay::isHomeActivityForUser, PooledLambda.__(ActivityRecord.class),
- userId);
- final ActivityRecord r = homeStack.getActivity(p);
- p.recycle();
- return r;
- }
-
- private static boolean isHomeActivityForUser(ActivityRecord r, int userId) {
- return r.isActivityTypeHome() && (userId == UserHandle.USER_ALL || r.mUserId == userId);
- }
-
- boolean isSleeping() {
- return mSleeping;
- }
-
- void setIsSleeping(boolean asleep) {
- mSleeping = asleep;
- }
-
- /**
- * Adds a listener to be notified whenever the stack order in the display changes. Currently
- * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
- * current animation when the system state changes.
- */
- void registerStackOrderChangedListener(OnStackOrderChangedListener listener) {
- if (!mStackOrderChangedCallbacks.contains(listener)) {
- mStackOrderChangedCallbacks.add(listener);
- }
- }
-
- /**
- * Removes a previously registered stack order change listener.
- */
- void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) {
- mStackOrderChangedCallbacks.remove(listener);
- }
-
- /**
- * Notifies of a stack order change
- * @param stack The stack which triggered the order change
- */
- private void onStackOrderChanged(ActivityStack stack) {
- for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
- mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
- }
- }
-
- void setDisplayToSingleTaskInstance() {
- final int childCount = getStackCount();
- if (childCount > 1) {
- throw new IllegalArgumentException("Display already has multiple stacks. display="
- + this);
- }
- if (childCount > 0) {
- final ActivityStack stack = getStackAt(0);
- if (stack.getChildCount() > 1) {
- throw new IllegalArgumentException("Display stack already has multiple tasks."
- + " display=" + this + " stack=" + stack);
- }
- }
-
- mSingleTaskInstance = true;
- }
-
- /** Returns true if the display can only contain one task */
- boolean isSingleTaskInstance() {
- return mSingleTaskInstance;
- }
-
- @VisibleForTesting
- void removeAllTasks() {
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final ActivityStack stack = getStackAt(i);
- final ArrayList<Task> tasks = stack.getAllTasks();
- for (int j = tasks.size() - 1; j >= 0; --j) {
- stack.removeChild(tasks.get(j), "removeAllTasks");
- }
- }
- }
-
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + getStackCount()
- + (mSingleTaskInstance ? " mSingleTaskInstance" : ""));
- final String myPrefix = prefix + " ";
- ActivityStack stack = getHomeStack();
- if (stack != null) {
- pw.println(myPrefix + "mHomeStack=" + stack);
- }
- stack = getRecentsStack();
- if (stack != null) {
- pw.println(myPrefix + "mRecentsStack=" + stack);
- }
- stack = getPinnedStack();
- if (stack != null) {
- pw.println(myPrefix + "mPinnedStack=" + stack);
- }
- stack = getSplitScreenPrimaryStack();
- if (stack != null) {
- pw.println(myPrefix + "mSplitScreenPrimaryStack=" + stack);
- }
- if (mPreferredTopFocusableStack != null) {
- pw.println(myPrefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack);
- }
- if (mLastFocusedStack != null) {
- pw.println(myPrefix + "mLastFocusedStack=" + mLastFocusedStack);
- }
- }
-
- public void dumpStacks(PrintWriter pw) {
- for (int i = getStackCount() - 1; i >= 0; --i) {
- pw.print(getStackAt(i).mStackId);
- if (i > 0) {
- pw.print(",");
- }
- }
- }
-
- public void dumpDebug(ProtoOutputStream proto, long fieldId,
- @WindowTraceLogLevel int logLevel) {
- final long token = proto.start(fieldId);
- dumpDebugInner(proto, DISPLAY, logLevel);
- proto.write(ID, mDisplayId);
- proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance);
- final ActivityStack focusedStack = getFocusedStack();
- if (focusedStack != null) {
- proto.write(FOCUSED_STACK_ID, focusedStack.mStackId);
- final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity();
- if (focusedActivity != null) {
- focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
- }
- } else {
- proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
- }
- for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = getStackAt(stackNdx);
- stack.dumpDebug(proto, STACKS, logLevel);
- }
- proto.end(token);
- }
-
- /**
- * Callback for when the order of the stacks in the display changes.
- */
- interface OnStackOrderChangedListener {
- void onStackOrderChanged(ActivityStack stack);
- }
-}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 20d1d1c..1c010c7 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -190,6 +190,8 @@
final int mTransitionType;
/** Whether the process was already running when the transition started. */
final boolean mProcessRunning;
+ /** whether the process of the launching activity didn't have any active activity. */
+ final boolean mProcessSwitch;
/** The activities that should be drawn. */
final LinkedList<ActivityRecord> mPendingDrawActivities = new LinkedList<>();
/** The latest activity to have been launched. */
@@ -218,7 +220,8 @@
/** @return Non-null if there will be a window drawn event for the launch. */
@Nullable
static TransitionInfo create(@NonNull ActivityRecord r,
- @NonNull LaunchingState launchingState, boolean processRunning, int startResult) {
+ @NonNull LaunchingState launchingState, boolean processRunning,
+ boolean processSwitch, int startResult) {
int transitionType = INVALID_TRANSITION_TYPE;
if (processRunning) {
if (startResult == START_SUCCESS) {
@@ -235,16 +238,18 @@
// That means the startResult is neither START_SUCCESS nor START_TASK_TO_FRONT.
return null;
}
- return new TransitionInfo(r, launchingState, transitionType, processRunning);
+ return new TransitionInfo(r, launchingState, transitionType, processRunning,
+ processSwitch);
}
/** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */
private TransitionInfo(ActivityRecord r, LaunchingState launchingState, int transitionType,
- boolean processRunning) {
+ boolean processRunning, boolean processSwitch) {
mLaunchingState = launchingState;
mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
mTransitionType = transitionType;
mProcessRunning = processRunning;
+ mProcessSwitch = processSwitch;
mCurrentTransitionDeviceUptime =
(int) TimeUnit.MILLISECONDS.toSeconds(SystemClock.uptimeMillis());
setLatestLaunchedActivity(r);
@@ -282,6 +287,14 @@
return mPendingDrawActivities.isEmpty();
}
+ /**
+ * @return {@code true} if the transition info should be sent to MetricsLogger, StatsLog, or
+ * LaunchObserver.
+ */
+ boolean isInterestingToLoggerAndObserver() {
+ return mProcessSwitch;
+ }
+
int calculateCurrentDelay() {
return calculateDelay(SystemClock.elapsedRealtimeNanos());
}
@@ -530,9 +543,10 @@
return;
}
- if (info != null) {
- // If we are already in an existing transition, only update the activity name, but not
- // the other attributes.
+ if (info != null
+ && info.mLastLaunchedActivity.mDisplayContent == launchedActivity.mDisplayContent) {
+ // If we are already in an existing transition on the same display, only update the
+ // activity name, but not the other attributes.
if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunched update launched activity");
// Coalesce multiple (trampoline) activities from a single sequence together.
@@ -540,13 +554,8 @@
return;
}
- if (!processSwitch) {
- abort(info, "not a process switch");
- return;
- }
-
final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState,
- processRunning, resultCode);
+ processRunning, processSwitch, resultCode);
if (newInfo == null) {
abort(info, "unrecognized launch");
return;
@@ -557,7 +566,12 @@
mTransitionInfoList.add(newInfo);
mLastTransitionInfo.put(launchedActivity, newInfo);
startLaunchTrace(newInfo);
- launchObserverNotifyActivityLaunched(newInfo);
+ if (newInfo.isInterestingToLoggerAndObserver()) {
+ launchObserverNotifyActivityLaunched(newInfo);
+ } else {
+ // As abort for no process switch.
+ launchObserverNotifyIntentFailed();
+ }
}
/**
@@ -733,7 +747,9 @@
launchObserverNotifyActivityLaunchCancelled(info);
} else {
logAppTransitionFinished(info);
- launchObserverNotifyActivityLaunchFinished(info, timestampNs);
+ if (info.isInterestingToLoggerAndObserver()) {
+ launchObserverNotifyActivityLaunchFinished(info, timestampNs);
+ }
}
info.mPendingDrawActivities.clear();
mTransitionInfoList.remove(info);
@@ -768,8 +784,11 @@
// Take a snapshot of the transition info before sending it to the handler for logging.
// This will avoid any races with other operations that modify the ActivityRecord.
final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
- BackgroundThread.getHandler().post(() -> logAppTransition(
- info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs, infoSnapshot));
+ if (info.isInterestingToLoggerAndObserver()) {
+ BackgroundThread.getHandler().post(() -> logAppTransition(
+ info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs,
+ infoSnapshot));
+ }
BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot));
if (info.mPendingFullyDrawn != null) {
info.mPendingFullyDrawn.run();
@@ -905,6 +924,18 @@
return null;
}
+ final long currentTimestampNs = SystemClock.elapsedRealtimeNanos();
+ final long startupTimeMs = info.mPendingFullyDrawn != null
+ ? info.mWindowsDrawnDelayMs
+ : TimeUnit.NANOSECONDS.toMillis(currentTimestampNs - info.mTransitionStartTimeNs);
+ final TransitionInfoSnapshot infoSnapshot =
+ new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
+ BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot));
+
+ if (!info.isInterestingToLoggerAndObserver()) {
+ return infoSnapshot;
+ }
+
// Record the handling of the reportFullyDrawn callback in the trace system. This is not
// actually used to trace this function, but instead the logical task that this function
// fullfils (handling reportFullyDrawn() callbacks).
@@ -914,10 +945,6 @@
final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
builder.setPackageName(r.packageName);
builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
- final long currentTimestampNs = SystemClock.elapsedRealtimeNanos();
- final long startupTimeMs = info.mPendingFullyDrawn != null
- ? info.mWindowsDrawnDelayMs
- : TimeUnit.NANOSECONDS.toMillis(currentTimestampNs - info.mTransitionStartTimeNs);
builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
builder.setType(restoredFromBundle
? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
@@ -940,10 +967,6 @@
// the trace slice to have a noticable duration.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- final TransitionInfoSnapshot infoSnapshot =
- new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
- BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot));
-
// Notify reportFullyDrawn event.
launchObserverNotifyReportFullyDrawn(r, currentTimestampNs);
@@ -1069,7 +1092,8 @@
return;
}
info.mLaunchTraceName = "launching: " + info.mLastLaunchedActivity.packageName;
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, info.mLaunchTraceName, 0);
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, info.mLaunchTraceName,
+ (int) info.mTransitionStartTimeNs /* cookie */);
}
/** Stops trace for the launch is completed or cancelled. */
@@ -1078,7 +1102,8 @@
if (info.mLaunchTraceName == null) {
return;
}
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, info.mLaunchTraceName, 0);
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, info.mLaunchTraceName,
+ (int) info.mTransitionStartTimeNs /* cookie */);
info.mLaunchTraceName = null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 83c854b..834e924 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -547,7 +547,7 @@
private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
new WindowState.UpdateReportedVisibilityResults();
- private boolean mUseTransferredAnimation;
+ boolean mUseTransferredAnimation;
/**
* @see #currentLaunchCanTurnScreenOn()
@@ -1640,6 +1640,17 @@
allowTaskSnapshot, activityCreated, fromRecents, snapshot);
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
+ if (isActivityTypeHome()) {
+ // The snapshot of home is only used once because it won't be updated while screen
+ // is on (see {@link TaskSnapshotController#screenTurningOff}).
+ mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
+ // TODO(b/9684093): Use more general condition to specify the case.
+ if (mDisplayContent.mAppTransition
+ .getAppTransition() != WindowManager.TRANSIT_KEYGUARD_GOING_AWAY) {
+ // Only use snapshot of home as starting window when unlocking.
+ return false;
+ }
+ }
return createSnapshot(snapshot);
}
@@ -1800,11 +1811,6 @@
} else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
- if (mWmService.mLowRamTaskSnapshotsAndRecents) {
- // For low RAM devices, we use the splash screen starting window instead of the
- // task snapshot starting window.
- return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
- }
return snapshot == null ? STARTING_WINDOW_TYPE_NONE
: snapshotOrientationSameAsTask(snapshot) || fromRecents
? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -1996,7 +2002,7 @@
return getActivityStack() != null ? getActivityStack().mStackId : INVALID_STACK_ID;
}
- ActivityDisplay getDisplay() {
+ DisplayContent getDisplay() {
final ActivityStack stack = getActivityStack();
return stack != null ? stack.getDisplay() : null;
}
@@ -2153,7 +2159,7 @@
boolean isKeyguardLocked = mAtmService.isKeyguardLocked();
boolean isCurrentAppLocked =
mAtmService.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
boolean hasPinnedStack = display != null && display.hasPinnedStack();
// Don't return early if !isNotLocked, since we want to throw an exception if the activity
// is in an incorrect state
@@ -2190,7 +2196,7 @@
}
/**
- * Sets if this AWT is in the process of closing or entering PIP.
+ * Sets if this {@link ActivityRecord} is in the process of closing or entering PIP.
* {@link #mWillCloseOrEnterPip}}
*/
void setWillCloseOrEnterPip(boolean willCloseOrEnterPip) {
@@ -2198,7 +2204,7 @@
}
/**
- * Returns whether this AWT is considered closing. Conditions are either
+ * Returns whether this {@link ActivityRecord} is considered closing. Conditions are either
* 1. Is this app animating and was requested to be hidden
* 2. App is delayed closing since it might enter PIP.
*/
@@ -2217,11 +2223,15 @@
return (info.flags & FLAG_ALWAYS_FOCUSABLE) != 0;
}
+ boolean windowsAreFocusable() {
+ return windowsAreFocusable(false /* fromUserTouch */);
+ }
+
// TODO: Does this really need to be different from isAlwaysFocusable()? For the activity side
// focusable means resumeable. I guess with that in mind maybe we should rename the other
// method to isResumeable() or something like that.
- boolean windowsAreFocusable() {
- if (mTargetSdk < Build.VERSION_CODES.Q) {
+ boolean windowsAreFocusable(boolean fromUserTouch) {
+ if (!fromUserTouch && mTargetSdk < Build.VERSION_CODES.Q) {
final int pid = getPid();
final ActivityRecord topFocusedAppOfMyProcess =
mWmService.mRoot.mTopFocusedAppByProcess.get(pid);
@@ -2235,7 +2245,12 @@
&& getDisplay() != null;
}
- /** Move activity with its stack to front and make the stack focused. */
+ /**
+ * Move activity with its stack to front and make the stack focused.
+ * @param reason the reason to move to top
+ * @return {@code true} if the stack is focusable and has been moved to top or the activity
+ * is not yet resumed while the stack is already on top, {@code false} otherwise.
+ */
boolean moveFocusableActivityToTop(String reason) {
if (!isFocusable()) {
if (DEBUG_FOCUS) {
@@ -2256,7 +2271,7 @@
if (DEBUG_FOCUS) {
Slog.d(TAG_FOCUS, "moveActivityStackToFront: already on top, activity=" + this);
}
- return false;
+ return !isState(RESUMED);
}
if (DEBUG_FOCUS) {
@@ -2406,14 +2421,14 @@
// 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.topRunningActivityLocked() == null || !stack.isFocusable())) {
+ && (stack.topRunningActivity() == null || !stack.isFocusable())) {
if (shouldAdjustGlobalFocus) {
// Move the entire hierarchy to top with updating global top resumed activity
// and focused application if needed.
stack.adjustFocusToNextFocusableStack("finish-top");
} else {
// Only move the next stack to top in its display.
- final ActivityDisplay display = stack.getDisplay();
+ final DisplayContent display = stack.getDisplay();
next = display.topRunningActivity();
if (next != null) {
display.positionStackAtTop(next.getActivityStack(),
@@ -2587,9 +2602,9 @@
mStackSupervisor.mGoingToSleepActivities.remove(this);
final ActivityStack stack = getActivityStack();
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
// TODO(b/137329632): Exclude current activity when looking for the next one with
- // ActivityDisplay#topRunningActivity().
+ // DisplayContent#topRunningActivity().
final ActivityRecord next = display.topRunningActivity();
final boolean isLastStackOverEmptyHome =
next == null && stack.isFocusedStackOnDisplay() && display.getHomeStack() != null;
@@ -3000,7 +3015,7 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Removing app token: %s", this);
- boolean delayed = commitVisibility(null, false, TRANSIT_UNSET, true, mVoiceInteraction);
+ commitVisibility(false /* visible */, true /* performLayout */);
getDisplayContent().mOpeningApps.remove(this);
getDisplayContent().mChangingApps.remove(this);
@@ -3008,6 +3023,8 @@
mWmService.mTaskSnapshotController.onAppRemoved(this);
mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
waitingToShow = false;
+
+ boolean delayed = isAnimating(TRANSITION | CHILDREN);
if (getDisplayContent().mClosingApps.contains(this)) {
delayed = true;
} else if (getDisplayContent().mAppTransition.isTransitionSet()) {
@@ -3152,16 +3169,6 @@
updateLetterboxSurface(child);
}
- private boolean waitingForReplacement() {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowState candidate = mChildren.get(i);
- if (candidate.waitingForReplacement()) {
- return true;
- }
- }
- return false;
- }
-
void onWindowReplacementTimeout() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
(mChildren.get(i)).onWindowReplacementTimeout();
@@ -3417,7 +3424,7 @@
// before the non-exiting app tokens. So, we skip the exiting app tokens here.
// TODO: Investigate if we need to continue to do this or if we can just process them
// in-order.
- if (mIsExiting && !waitingForReplacement()) {
+ if (mIsExiting && !forAllWindowsUnchecked(WindowState::waitingForReplacement, true)) {
return false;
}
return forAllWindowsUnchecked(callback, traverseTopToBottom);
@@ -3440,7 +3447,8 @@
}
@Override
- ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+ ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
+ WindowContainer boundary) {
return callback.test(this) ? this : null;
}
@@ -3861,6 +3869,17 @@
}
}
+ /**
+ * Set visibility on this {@link ActivityRecord}
+ *
+ * <p class="note"><strong>Note: </strong>This function might not update the visibility of
+ * this {@link ActivityRecord} immediately. In case we are preparing an app transition, we
+ * delay changing the visibility of this {@link ActivityRecord} until we execute that
+ * transition.</p>
+ *
+ * @param visible {@code true} if the {@link ActivityRecord} should become visible, otherwise
+ * this should become invisible.
+ */
void setVisibility(boolean visible) {
if (getParent() == null) {
Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: "
@@ -3997,153 +4016,166 @@
return;
}
- commitVisibility(null, visible, TRANSIT_UNSET, true, mVoiceInteraction);
+ commitVisibility(visible, true /* performLayout */);
updateReportedVisibilityLocked();
}
- boolean commitVisibility(WindowManager.LayoutParams lp,
- boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
+ @Override
+ boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+ boolean isVoiceInteraction) {
+ if (mUseTransferredAnimation) {
+ return false;
+ }
+ return super.applyAnimation(lp, transit, enter, isVoiceInteraction);
+ }
- boolean delayed = false;
- // Reset the state of mVisibleSetFromTransferredStartingWindow since visibility is actually
+ /**
+ * Update visibility to this {@link ActivityRecord}.
+ *
+ * <p class="note"><strong>Note: </strong> Unlike {@link #setVisibility}, this immediately
+ * updates the visibility without starting an app transition. Since this function may start
+ * animation on {@link WindowState} depending on app transition animation status, an app
+ * transition animation must be started before calling this function if necessary.</p>
+ *
+ * @param visible {@code true} if this {@link ActivityRecord} should become visible, otherwise
+ * this should become invisible.
+ * @param performLayout if {@code true}, perform surface placement after committing visibility.
+ */
+ void commitVisibility(boolean visible, boolean performLayout) {
+ // Reset the state of mHiddenSetFromTransferredStartingWindow since visibility is actually
// been set by the app now.
mVisibleSetFromTransferredStartingWindow = false;
-
- // Allow for state changes and animation to be applied if:
- // * token is transitioning visibility state
- // * or the token was marked as hidden and is exiting before we had a chance to play the
- // transition animation
- // * or this is an opening app and windows are being replaced
- // * or the token is the opening app and visible while opening task behind existing one.
- final DisplayContent displayContent = getDisplayContent();
- boolean visibilityChanged = false;
- if (isVisible() != visible || (!isVisible() && mIsExiting)
- || (visible && waitingForReplacement())
- || (visible && displayContent.mOpeningApps.contains(this)
- && displayContent.mAppTransition.getAppTransition() == TRANSIT_TASK_OPEN_BEHIND)) {
- final AccessibilityController accessibilityController =
- mWmService.mAccessibilityController;
- boolean changed = false;
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Changing app %s visible=%b performLayout=%b", this, isVisible(),
- performLayout);
-
- boolean runningAppAnimation = false;
-
- if (transit != WindowManager.TRANSIT_UNSET) {
- if (mUseTransferredAnimation) {
- runningAppAnimation = isAnimating();
- } else if (applyAnimation(lp, transit, visible, isVoiceInteraction)) {
- runningAppAnimation = true;
- }
- delayed = runningAppAnimation;
- final WindowState window = findMainWindow();
- if (window != null && accessibilityController != null) {
- accessibilityController.onAppWindowTransitionLocked(window, transit);
- }
- changed = true;
- }
-
- final int windowsCount = mChildren.size();
- for (int i = 0; i < windowsCount; i++) {
- final WindowState win = mChildren.get(i);
- changed |= win.onAppVisibilityChanged(visible, runningAppAnimation);
- }
-
- setVisible(visible);
- mVisibleRequested = visible;
- visibilityChanged = true;
- if (!visible) {
- stopFreezingScreen(true, true);
- } else {
- // If we are being set visible, and the starting window is not yet displayed,
- // then make sure it doesn't get displayed.
- if (startingWindow != null && !startingWindow.isDrawnLw()) {
- startingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
- startingWindow.mLegacyPolicyVisibilityAfterAnim = false;
- }
-
- // We are becoming visible, so better freeze the screen with the windows that are
- // getting visible so we also wait for them.
- forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true);
- }
-
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "commitVisibility: %s: visible=%b visibleRequested=%b", this,
- isVisible(), mVisibleRequested);
-
- if (changed) {
- displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
- if (performLayout) {
- mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
- false /*updateInputWindows*/);
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
- displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
- }
+ if (visible == isVisible()) {
+ return;
}
+
+ final int windowsCount = mChildren.size();
+ for (int i = 0; i < windowsCount; i++) {
+ mChildren.get(i).onAppVisibilityChanged(visible, isAnimating(PARENTS));
+ }
+ setVisible(visible);
+ mVisibleRequested = visible;
+ if (!visible) {
+ stopFreezingScreen(true, true);
+ } else {
+ // If we are being set visible, and the starting window is not yet displayed,
+ // then make sure it doesn't get displayed.
+ if (startingWindow != null && !startingWindow.isDrawnLw()) {
+ startingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
+ startingWindow.mLegacyPolicyVisibilityAfterAnim = false;
+ }
+ // We are becoming visible, so better freeze the screen with the windows that are
+ // getting visible so we also wait for them.
+ forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true);
+ }
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
+ isVisible(), mVisibleRequested);
+ final DisplayContent displayContent = getDisplayContent();
+ displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
+ if (performLayout) {
+ mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ false /*updateInputWindows*/);
+ mWmService.mWindowPlacerLocked.performSurfacePlacement();
+ }
+ displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
mUseTransferredAnimation = false;
- delayed = isAnimating(CHILDREN);
+ postApplyAnimation(visible);
+ }
+
+ /**
+ * Post process after applying an app transition animation.
+ *
+ * <p class="note"><strong>Note: </strong> This function must be called after the animations
+ * have been applied and {@link #commitVisibility}.</p>
+ *
+ * @param visible {@code true} if this {@link ActivityRecord} has become visible, otherwise
+ * this has become invisible.
+ */
+ private void postApplyAnimation(boolean visible) {
+ final boolean delayed = isAnimating(PARENTS | CHILDREN);
if (!delayed) {
- // We aren't animating anything, but exiting windows rely on the animation finished
- // callback being called in case the ActivityRecord was pretending to be animating,
+ // We aren't delayed anything, but exiting windows rely on the animation finished
+ // callback being called in case the ActivityRecord was pretending to be delayed,
// which we might have done because we were in closing/opening apps list.
onAnimationFinished();
- }
-
- if (visibilityChanged) {
- if (visible && !delayed) {
+ if (visible) {
// The token was made immediately visible, there will be no entrance animation.
// We need to inform the client the enter animation was finished.
mEnteringAnimation = true;
mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
token);
}
+ }
- // If we're becoming visible, immediately change client visibility as well. there seem
- // to be some edge cases where we change our visibility but client visibility never gets
- // updated.
- // If we're becoming invisible, update the client visibility if we are not running an
- // animation. Otherwise, we'll update client visibility in onAnimationFinished.
- if (visible || !isAnimating()) {
- setClientVisible(visible);
- }
+ // If we're becoming visible, immediately change client visibility as well. there seem
+ // to be some edge cases where we change our visibility but client visibility never gets
+ // updated.
+ // If we're becoming invisible, update the client visibility if we are not running an
+ // animation. Otherwise, we'll update client visibility in onAnimationFinished.
+ if (visible || !isAnimating(PARENTS)) {
+ setClientVisible(visible);
+ }
- if (!displayContent.mClosingApps.contains(this)
- && !displayContent.mOpeningApps.contains(this)) {
- // The token is not closing nor opening, so even if there is an animation set, that
- // doesn't mean that it goes through the normal app transition cycle so we have
- // to inform the docked controller about visibility change.
- // TODO(multi-display): notify docked divider on all displays where visibility was
- // affected.
- displayContent.getDockedDividerController().notifyAppVisibilityChanged();
+ final DisplayContent displayContent = getDisplayContent();
+ if (!displayContent.mClosingApps.contains(this)
+ && !displayContent.mOpeningApps.contains(this)) {
+ // The token is not closing nor opening, so even if there is an animation set, that
+ // doesn't mean that it goes through the normal app transition cycle so we have
+ // to inform the docked controller about visibility change.
+ // TODO(multi-display): notify docked divider on all displays where visibility was
+ // affected.
+ displayContent.getDockedDividerController().notifyAppVisibilityChanged();
- // Take the screenshot before possibly hiding the WSA, otherwise the screenshot
- // will not be taken.
- mWmService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible);
- }
+ // Take the screenshot before possibly hiding the WSA, otherwise the screenshot
+ // will not be taken.
+ mWmService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible);
+ }
- // If we are hidden but there is no delay needed we immediately
- // apply the Surface transaction so that the ActivityManager
- // can have some guarantee on the Surface state following
- // setting the visibility. This captures cases like dismissing
- // the docked or pinned stack where there is no app transition.
- //
- // In the case of a "Null" animation, there will be
- // no animation but there will still be a transition set.
- // We still need to delay hiding the surface such that it
- // can be synchronized with showing the next surface in the transition.
- if (!isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) {
- SurfaceControl.openTransaction();
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- mChildren.get(i).mWinAnimator.hide("immediately hidden");
- }
+ // If we are hidden but there is no delay needed we immediately
+ // apply the Surface transaction so that the ActivityManager
+ // can have some guarantee on the Surface state following
+ // setting the visibility. This captures cases like dismissing
+ // the docked or pinned stack where there is no app transition.
+ //
+ // In the case of a "Null" animation, there will be
+ // no animation but there will still be a transition set.
+ // We still need to delay hiding the surface such that it
+ // can be synchronized with showing the next surface in the transition.
+ if (!isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) {
+ SurfaceControl.openTransaction();
+ try {
+ forAllWindows(win -> {
+ win.mWinAnimator.hide("immediately hidden"); }, true);
+ } finally {
SurfaceControl.closeTransaction();
}
}
+ }
- return delayed;
+ /**
+ * Check if visibility of this {@link ActivityRecord} should be updated as part of an app
+ * transition.
+ *
+ * <p class="note><strong>Note:</strong> If the visibility of this {@link ActivityRecord} is
+ * already set to {@link #mVisible}, we don't need to update the visibility. So {@code false} is
+ * returned.</p>
+ *
+ * @param visible {@code true} if this {@link ActivityRecord} should become visible,
+ * {@code false} if this should become invisible.
+ * @return {@code true} if visibility of this {@link ActivityRecord} should be updated, and
+ * an app transition animation should be run.
+ */
+ boolean shouldApplyAnimation(boolean visible) {
+ // Allow for state update and animation to be applied if:
+ // * activity is transitioning visibility state
+ // * or the activity was marked as hidden and is exiting before we had a chance to play the
+ // transition animation
+ // * or this is an opening app and windows are being replaced (e.g. freeform window to
+ // normal window).
+ return isVisible() != visible || (!isVisible() && mIsExiting)
+ || (visible && forAllWindows(WindowState::waitingForReplacement, true));
}
/**
@@ -4636,7 +4668,7 @@
}
r.setSavedState(null /* savedState */);
- final ActivityDisplay display = r.getDisplay();
+ final DisplayContent display = r.getDisplay();
if (display != null) {
display.handleActivitySizeCompatModeIfNeeded(r);
}
@@ -5541,12 +5573,12 @@
/**
* @return The to top most child window for which {@link LayoutParams#isFullscreen()} returns
- * true.
+ * true and isn't fully transparent.
*/
- WindowState getTopFullscreenWindow() {
+ WindowState getTopFullscreenOpaqueWindow() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState win = mChildren.get(i);
- if (win != null && win.mAttrs.isFullscreen()) {
+ if (win != null && win.mAttrs.isFullscreen() && !win.isFullyTransparent()) {
return win;
}
}
@@ -5634,19 +5666,6 @@
return task != null ? task.isChangingAppTransition() : super.isChangingAppTransition();
}
- @Override
- boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
- boolean isVoiceInteraction) {
- if (mWmService.mDisableTransitionAnimation || !shouldAnimate(transit)) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: transition animation is disabled or skipped. "
- + "container=%s", this);
- cancelAnimation();
- return false;
- }
- return super.applyAnimation(lp, transit, enter, isVoiceInteraction);
- }
-
/**
* Creates a layer to apply crop to an animation.
*/
@@ -5913,14 +5932,14 @@
protected void onAnimationFinished() {
super.onAnimationFinished();
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AWT#onAnimationFinished");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished");
mTransit = TRANSIT_UNSET;
mTransitFlags = 0;
mNeedsZBoost = false;
mNeedsAnimationBoundsLayer = false;
setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,
- "AppWindowToken");
+ "ActivityRecord");
clearThumbnail();
setClientVisible(isVisible() || mVisibleRequested);
@@ -6041,6 +6060,13 @@
void registerRemoteAnimations(RemoteAnimationDefinition definition) {
mRemoteAnimationDefinition = definition;
+ if (definition != null) {
+ definition.linkToDeath(this::unregisterRemoteAnimations);
+ }
+ }
+
+ void unregisterRemoteAnimations() {
+ mRemoteAnimationDefinition = null;
}
RemoteAnimationDefinition getRemoteAnimationDefinition() {
@@ -6138,10 +6164,9 @@
if (mCompatDisplayInsets == null || !shouldUseSizeCompatMode()) {
return false;
}
- final Configuration resolvedConfig = getResolvedOverrideConfiguration();
- final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
- if (resolvedAppBounds == null) {
- // The override configuration has not been resolved yet.
+ final Rect appBounds = getConfiguration().windowConfiguration.getAppBounds();
+ if (appBounds == null) {
+ // The app bounds hasn't been computed yet.
return false;
}
@@ -6149,13 +6174,13 @@
// Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
// fields should be changed with density and bounds, so here only compares the most
// significant field.
- if (parentConfig.densityDpi != resolvedConfig.densityDpi) {
+ if (parentConfig.densityDpi != getConfiguration().densityDpi) {
return true;
}
final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
- final int appWidth = resolvedAppBounds.width();
- final int appHeight = resolvedAppBounds.height();
+ final int appWidth = appBounds.width();
+ final int appHeight = appBounds.height();
final int parentAppWidth = parentAppBounds.width();
final int parentAppHeight = parentAppBounds.height();
if (parentAppWidth == appWidth && parentAppHeight == appHeight) {
@@ -6174,7 +6199,8 @@
// The rest of the condition is that only one side is smaller than the parent, but it still
// needs to exclude the cases where the size is limited by the fixed aspect ratio.
if (info.maxAspectRatio > 0) {
- final float aspectRatio = Math.max(appWidth, appHeight) / Math.min(appWidth, appHeight);
+ final float aspectRatio =
+ (float) Math.max(appWidth, appHeight) / Math.min(appWidth, appHeight);
if (aspectRatio >= info.maxAspectRatio) {
// The current size has reached the max aspect ratio.
return false;
@@ -6246,7 +6272,7 @@
}
// The role of CompatDisplayInsets is like the override bounds.
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
if (display != null) {
mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent,
getWindowConfiguration().getBounds(),
@@ -6282,6 +6308,11 @@
resolveSizeCompatModeConfiguration(newParentConfiguration);
} else {
super.resolveOverrideConfiguration(newParentConfiguration);
+ // We ignore activities' requested orientation in multi-window modes. Task level may
+ // take them into consideration when calculating bounds.
+ if (getParent() != null && getParent().inMultiWindowMode()) {
+ resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED;
+ }
applyAspectRatio(resolvedConfig.windowConfiguration.getBounds(),
newParentConfiguration.windowConfiguration.getAppBounds(),
newParentConfiguration.windowConfiguration.getBounds());
@@ -6516,7 +6547,7 @@
onMergedOverrideConfigurationChanged();
}
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
if (display == null) {
return;
}
@@ -6526,7 +6557,7 @@
} else if (mCompatDisplayInsets != null) {
// The override changes can only be obtained from display, because we don't have the
// difference of full configuration in each hierarchy.
- final int displayChanges = display.getLastOverrideConfigurationChanges();
+ final int displayChanges = display.getCurrentOverrideConfigurationChanges();
final int orientationChanges = CONFIG_WINDOW_CONFIGURATION
| CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION;
final boolean hasNonOrienSizeChanged = hasResizeChange(displayChanges)
@@ -7265,7 +7296,7 @@
* otherwise.
*/
boolean isResumedActivityOnDisplay() {
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
return display != null && this == display.getResumedActivity();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index cc45671..62dd7bb 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
@@ -130,8 +131,6 @@
import android.app.IActivityController;
import android.app.RemoteAction;
import android.app.ResultInfo;
-import android.app.WindowConfiguration.ActivityType;
-import android.app.WindowConfiguration.WindowingMode;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.NewIntentItem;
@@ -143,7 +142,6 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.Region;
import android.os.Binder;
import android.os.Debug;
import android.os.Handler;
@@ -153,9 +151,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
-import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
-import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
@@ -191,7 +187,7 @@
/**
* State and management of a single stack of activities.
*/
-class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarget {
+class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAnimationTarget {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_ATM;
static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -583,7 +579,7 @@
return true;
}
- final ActivityRecord topActivity = topRunningActivityLocked();
+ final ActivityRecord topActivity = topRunningActivity();
final PooledFunction f = PooledLambda.obtainFunction(
CheckBehindFullscreenActivityHelper::processActivity, this,
PooledLambda.__(ActivityRecord.class), topActivity);
@@ -731,7 +727,7 @@
}
}
- ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+ ActivityStack(DisplayContent display, int stackId, ActivityStackSupervisor supervisor,
int windowingMode, int activityType, boolean onTop) {
super(supervisor.mService.mWindowManager);
mStackId = stackId;
@@ -815,7 +811,7 @@
}
}
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
if (display == null ) {
return;
}
@@ -825,10 +821,7 @@
// Update bounds if applicable
boolean hasNewOverrideBounds = false;
// Use override windowing mode to prevent extra bounds changes if inheriting the mode.
- if (overrideWindowingMode == WINDOWING_MODE_PINNED) {
- // Pinned calculation already includes rotation
- hasNewOverrideBounds = calculatePinnedBoundsForConfigChange(newBounds);
- } else if (!matchParentBounds()) {
+ if ((overrideWindowingMode != WINDOWING_MODE_PINNED) && !matchParentBounds()) {
// If the parent (display) has rotated, rotate our bounds to best-fit where their
// bounds were on the pre-rotated display.
final int newRotation = getWindowConfiguration().getRotation();
@@ -870,7 +863,7 @@
final boolean isMinimizedDock =
display.mDisplayContent.getDockedDividerController().isMinimizedDock();
if (isMinimizedDock) {
- Task topTask = display.getSplitScreenPrimaryStack().topTask();
+ Task topTask = display.getSplitScreenPrimaryStack().getTopMostTask();
if (topTask != null) {
dockedBounds = topTask.getBounds();
}
@@ -886,9 +879,6 @@
null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */,
PRESERVE_WINDOWS, true /* deferResume */);
- } else {
- resize(new Rect(newBounds), null /* tempTaskBounds */,
- null /* tempTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */);
}
}
if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
@@ -945,8 +935,8 @@
boolean creating) {
final int currentMode = getWindowingMode();
final int currentOverrideMode = getRequestedOverrideWindowingMode();
- final ActivityDisplay display = getDisplay();
- final Task topTask = topTask();
+ final DisplayContent display = getDisplay();
+ final Task topTask = getTopMostTask();
final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
int windowingMode = preferredWindowingMode;
if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED
@@ -1115,8 +1105,8 @@
!PRESERVE_WINDOWS);
}
- ActivityDisplay getDisplay() {
- return mRootActivityContainer.getActivityDisplay(mDisplayId);
+ DisplayContent getDisplay() {
+ return getDisplayContent();
}
/**
@@ -1174,11 +1164,11 @@
return false;
}
- ActivityRecord topRunningActivityLocked() {
- return topRunningActivityLocked(false /* focusableOnly */);
+ ActivityRecord topRunningActivity() {
+ return topRunningActivity(false /* focusableOnly */);
}
- ActivityRecord topRunningActivityLocked(boolean focusableOnly) {
+ ActivityRecord topRunningActivity(boolean focusableOnly) {
// Split into 2 to avoid object creation due to variable capture.
if (focusableOnly) {
return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
@@ -1212,7 +1202,7 @@
*
* @return Returns the HistoryRecord of the next activity on the stack.
*/
- final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) {
+ ActivityRecord topRunningActivity(IBinder token, int taskId) {
final PooledPredicate p = PooledLambda.obtainPredicate(ActivityStack::isTopRunning,
PooledLambda.__(ActivityRecord.class), taskId, token);
final ActivityRecord r = getActivity(p);
@@ -1221,31 +1211,13 @@
}
private static boolean isTopRunning(ActivityRecord r, int taskId, IBinder notTop) {
- return r.getTask().mTaskId == taskId && r.appToken != notTop && r.canBeTopRunning();
+ return r.getTask().mTaskId != taskId && r.appToken != notTop && r.canBeTopRunning();
}
ActivityRecord getTopNonFinishingActivity() {
return getTopActivity(false /*includeFinishing*/, true /*includeOverlays*/);
}
- final Task topTask() {
- final int size = getChildCount();
- if (size > 0) {
- return getChildAt(size - 1);
- }
- return null;
- }
-
- Task taskForIdLocked(int id) {
- for (int taskNdx = getChildCount() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = getChildAt(taskNdx);
- if (task.mTaskId == id) {
- return task;
- }
- }
- return null;
- }
-
ActivityRecord isInStackLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
return isInStackLocked(r);
@@ -1267,7 +1239,7 @@
/** @return true if the stack can only contain one task */
boolean isSingleTaskInstance() {
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
return display != null && display.isSingleTaskInstance();
}
@@ -1292,9 +1264,7 @@
}
private boolean returnsToHomeStack() {
- return !inMultiWindowMode()
- && hasChild()
- && getChildAt(0).returnsToHomeStack();
+ return !inMultiWindowMode() && hasChild() && getBottomMostTask().returnsToHomeStack();
}
void moveToFront(String reason) {
@@ -1310,7 +1280,7 @@
return;
}
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
if (inSplitScreenSecondaryWindowingMode()) {
// If the stack is in split-screen seconardy mode, we need to make sure we move the
@@ -1367,7 +1337,7 @@
}
boolean isFocusable() {
- final ActivityRecord r = topRunningActivityLocked();
+ final ActivityRecord r = topRunningActivity();
return mRootActivityContainer.isFocusable(this, r != null && r.isFocusable());
}
@@ -1377,7 +1347,7 @@
@Override
public boolean isAttached() {
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
return display != null && !display.isRemoved();
}
@@ -1390,15 +1360,12 @@
mCurrentUser = userId;
super.switchUser(userId);
- int top = mChildren.size();
- for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
- Task task = mChildren.get(taskNdx);
- if (mWmService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) {
- mChildren.remove(taskNdx);
- mChildren.add(task);
- --top;
+ forAllTasks((t) -> {
+ if (t.mWmService.isCurrentProfileLocked(t.mUserId) || t.showForAllUsers()) {
+ mChildren.remove(t);
+ mChildren.add(t);
}
- }
+ });
}
void minimalResumeActivityLocked(ActivityRecord r) {
@@ -1719,7 +1686,7 @@
mRootActivityContainer.resumeFocusedStacksTopActivities(topStack, prev, null);
} else {
checkReadyForSleep();
- ActivityRecord top = topStack.topRunningActivityLocked();
+ ActivityRecord top = topStack.topRunningActivity();
if (top == null || (prev != null && top != prev)) {
// If there are no more activities available to run, do resume anyway to start
// something. Also if the top activity on the stack is not the just paused
@@ -1799,7 +1766,7 @@
}
boolean isTopStackOnDisplay() {
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
return display != null && display.isTopStack(this);
}
@@ -1808,7 +1775,7 @@
* otherwise.
*/
boolean isFocusedStackOnDisplay() {
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
return display != null && this == display.getFocusedStack();
}
@@ -1837,7 +1804,7 @@
return STACK_VISIBILITY_INVISIBLE;
}
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
boolean gotSplitScreenStack = false;
boolean gotOpaqueSplitScreenPrimary = false;
boolean gotOpaqueSplitScreenSecondary = false;
@@ -1849,7 +1816,7 @@
final boolean isAssistantType = isActivityTypeAssistant();
for (int i = display.getStackCount() - 1; i >= 0; --i) {
final ActivityStack other = display.getStackAt(i);
- final boolean hasRunningActivities = other.topRunningActivityLocked() != null;
+ final boolean hasRunningActivities = other.topRunningActivity() != null;
if (other == this) {
// Should be visible if there is no other stack occluding it, unless it doesn't
// have any running activities, not starting one and not home stack.
@@ -2001,7 +1968,7 @@
@Override
public boolean supportsSplitScreenWindowingMode() {
- final Task topTask = topTask();
+ final Task topTask = getTopMostTask();
return super.supportsSplitScreenWindowingMode()
&& (topTask == null || topTask.supportsSplitScreenWindowingMode());
}
@@ -2071,13 +2038,13 @@
* {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
*/
boolean canShowWithInsecureKeyguard() {
- final ActivityDisplay activityDisplay = getDisplay();
- if (activityDisplay == null) {
+ final DisplayContent displayContent = getDisplay();
+ if (displayContent == null) {
throw new IllegalStateException("Stack is not attached to any display, stackId="
+ mStackId);
}
- final int flags = activityDisplay.mDisplay.getFlags();
+ final int flags = displayContent.mDisplay.getFlags();
return (flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0;
}
@@ -2185,7 +2152,7 @@
// to ensure any necessary pause logic occurs. In the case where the Activity will be
// shown regardless of the lock screen, the call to
// {@link ActivityStackSupervisor#checkReadyForSleepLocked} is skipped.
- final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+ final ActivityRecord next = topRunningActivity(true /* focusableOnly */);
if (next == null || !next.canTurnScreenOn()) {
checkReadyForSleep();
}
@@ -2224,7 +2191,7 @@
// Find the next top-most activity to resume in this stack that is not finishing and is
// focusable. If it is not focusable, we will fall into the case below to resume the
// top activity in the next focusable task.
- ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+ ActivityRecord next = topRunningActivity(true /* focusableOnly */);
final boolean hasRunningActivity = next != null;
@@ -2246,7 +2213,7 @@
}
next.delayedResume = false;
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
// If the top activity is the resumed one, nothing to do.
if (mResumedActivity == next && next.isState(RESUMED)
@@ -2535,7 +2502,7 @@
// We should be all done, but let's just make sure our activity
// is still at the top and schedule another run if something
// weird happened.
- ActivityRecord nextNext = topRunningActivityLocked();
+ ActivityRecord nextNext = topRunningActivity();
if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
"Activity config changed during resume: " + next
+ ", new next: " + nextNext);
@@ -2672,41 +2639,28 @@
void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
Task rTask = r.getTask();
- final int taskId = rTask.mTaskId;
final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
+ final boolean hasTask = hasChild(rTask);
// mLaunchTaskBehind tasks get placed at the back of the task stack.
- if (!r.mLaunchTaskBehind && allowMoveToFront
- && (taskForIdLocked(taskId) == null || newTask)) {
+ if (!r.mLaunchTaskBehind && allowMoveToFront && (!hasTask || newTask)) {
// Last activity in task had been removed or ActivityManagerService is reusing task.
// Insert or replace.
// Might not even be in.
positionChildAtTop(rTask);
}
Task task = null;
- if (!newTask) {
- // If starting in an existing task, find where that is...
- boolean isOccluded = false;
- for (int taskNdx = getChildCount() - 1; taskNdx >= 0; --taskNdx) {
- task = getChildAt(taskNdx);
- if (task.getTopNonFinishingActivity() == null) {
- // All activities in task are finishing.
- continue;
- }
- if (task == rTask) {
- // Here it is! Now, if this is not yet visible (occluded by another task) to
- // the user, then just add it without starting; it will get started when the
- // user navigates back to it.
- if (isOccluded) {
- if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task "
- + task, new RuntimeException("here").fillInStackTrace());
- rTask.positionChildAtTop(r);
- ActivityOptions.abort(options);
- return;
- }
- break;
- } else if (!isOccluded) {
- isOccluded = task.getActivity(ActivityRecord::occludesParent) != null;
- }
+ if (!newTask && hasTask) {
+ final ActivityRecord occludingActivity = getActivity(
+ (ar) -> !ar.finishing && ar.occludesParent(), true, rTask);
+ if (occludingActivity != null) {
+ // Here it is! Now, if this is not yet visible (occluded by another task) to the
+ // user, then just add it without starting; it will get started when the user
+ // navigates back to it.
+ if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task " + task,
+ new RuntimeException("here").fillInStackTrace());
+ rTask.positionChildAtTop(r);
+ ActivityOptions.abort(options);
+ return;
}
}
@@ -2892,7 +2846,7 @@
return null;
}
- final ActivityRecord top = stack.topRunningActivityLocked();
+ final ActivityRecord top = stack.topRunningActivity();
if (stack.isActivityTypeHome() && (top == null || !top.mVisibleRequested)) {
// If we will be focusing on the home stack next and its current top activity isn't
@@ -2922,7 +2876,7 @@
* not belong to the crashed app.
*/
final Task finishTopCrashedActivityLocked(WindowProcessController app, String reason) {
- ActivityRecord r = topRunningActivityLocked();
+ final ActivityRecord r = topRunningActivity();
if (r == null || r.app != app) {
return null;
}
@@ -3001,7 +2955,7 @@
/** @return true if the stack behind this one is a standard activity type. */
private boolean inFrontOfStandardStack() {
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
if (display == null) {
return false;
}
@@ -3033,12 +2987,11 @@
return true;
}
// We now need to get the task below it to determine what to do.
- int taskIdx = mChildren.indexOf(task);
- if (taskIdx <= 0) {
+ final Task prevTask = getTaskBelow(task);
+ if (prevTask == null) {
Slog.w(TAG, "shouldUpRecreateTask: task not in history for " + srec);
return false;
}
- final Task prevTask = getChildAt(taskIdx);
if (!task.affinity.equals(prevTask.affinity)) {
// These are different apps, so need to recreate.
return true;
@@ -3073,7 +3026,7 @@
// We should consolidate.
IActivityController controller = mService.mController;
if (controller != null) {
- ActivityRecord next = topRunningActivityLocked(srec.appToken, 0);
+ ActivityRecord next = topRunningActivity(srec.appToken, INVALID_TASK_ID);
if (next != null) {
// ask watcher if this is allowed
boolean resumeOK = true;
@@ -3287,7 +3240,7 @@
private void updateTransitLocked(int transit, ActivityOptions options) {
if (options != null) {
- ActivityRecord r = topRunningActivityLocked();
+ ActivityRecord r = topRunningActivity();
if (r != null && !r.isState(RESUMED)) {
r.updateOptionsLocked(options);
} else {
@@ -3344,7 +3297,7 @@
}
// Set focus to the top running activity of this stack.
- final ActivityRecord r = topRunningActivityLocked();
+ final ActivityRecord r = topRunningActivity();
if (r != null) {
r.moveFocusableActivityToTop(reason);
}
@@ -3386,15 +3339,10 @@
* If a watcher is installed, the action is preflighted and the watcher has an opportunity
* to premeptively cancel the move.
*
- * @param taskId The taskId to collect and move to the bottom.
+ * @param tr The task to collect and move to the bottom.
* @return Returns true if the move completed, false if not.
*/
- final boolean moveTaskToBackLocked(int taskId) {
- final Task tr = taskForIdLocked(taskId);
- if (tr == null) {
- Slog.i(TAG, "moveTaskToBack: bad taskId=" + taskId);
- return false;
- }
+ boolean moveTaskToBack(Task tr) {
Slog.i(TAG, "moveTaskToBack: " + tr);
// In LockTask mode, moving a locked task to the back of the stack may expose unlocked
@@ -3407,9 +3355,9 @@
// for *other* available tasks, but if none are available, then try again allowing the
// current task to be selected.
if (isTopStackOnDisplay() && mService.mController != null) {
- ActivityRecord next = topRunningActivityLocked(null, taskId);
+ ActivityRecord next = topRunningActivity(null, tr.mTaskId);
if (next == null) {
- next = topRunningActivityLocked(null, 0);
+ next = topRunningActivity(null, INVALID_TASK_ID);
}
if (next != null) {
// ask watcher if this is allowed
@@ -3426,7 +3374,8 @@
}
}
- if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId);
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task="
+ + tr.mTaskId);
getDisplay().mDisplayContent.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
moveToBack("moveTaskToBackLocked", tr);
@@ -3471,24 +3420,16 @@
try {
// Update override configurations of all tasks in the stack.
final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
- for (int i = getChildCount() - 1; i >= 0; i--) {
- final Task task = getChildAt(i);
- if (task.isResizeable()) {
- if (tempTaskInsetBounds != null && !tempTaskInsetBounds.isEmpty()) {
- task.setOverrideDisplayedBounds(taskBounds);
- task.setBounds(tempTaskInsetBounds);
- } else {
- task.setOverrideDisplayedBounds(null);
- task.setBounds(taskBounds);
- }
- }
- }
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ ActivityStack::processTaskResizeBounds, PooledLambda.__(Task.class),
+ taskBounds, tempTaskInsetBounds);
+ forAllTasks(c);
+ c.recycle();
setBounds(bounds);
if (!deferResume) {
- ensureVisibleActivitiesConfiguration(
- topRunningActivityLocked(), preserveWindows);
+ ensureVisibleActivitiesConfiguration(topRunningActivity(), preserveWindows);
}
} finally {
mService.continueWindowLayout();
@@ -3496,39 +3437,51 @@
}
}
+ private static void processTaskResizeBounds(Task task, Rect bounds, Rect insetBounds) {
+ if (!task.isResizeable()) return;
+
+ if (insetBounds != null && !insetBounds.isEmpty()) {
+ task.setOverrideDisplayedBounds(bounds);
+ task.setBounds(insetBounds);
+ } else {
+ task.setOverrideDisplayedBounds(null);
+ task.setBounds(bounds);
+ }
+ }
+
/**
* Until we can break this "set task bounds to same as stack bounds" behavior, this
* basically resizes both stack and task bounds to the same bounds.
*/
- void setTaskBounds(Rect bounds) {
+ private void setTaskBounds(Rect bounds) {
if (!updateBoundsAllowed(bounds)) {
return;
}
- for (int i = getChildCount() - 1; i >= 0; i--) {
- final Task task = getChildAt(i);
- if (task.isResizeable()) {
- task.setBounds(bounds);
- } else {
- task.setBounds(null);
- }
- }
+ final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskBounds,
+ PooledLambda.__(Task.class), bounds);
+ forAllTasks(c);
+ c.recycle();
+ }
+
+ private static void setTaskBounds(Task task, Rect bounds) {
+ task.setBounds(task.isResizeable() ? bounds : null);
}
/** Helper to setDisplayedBounds on all child tasks */
- void setTaskDisplayedBounds(Rect bounds) {
+ private void setTaskDisplayedBounds(Rect bounds) {
if (!updateDisplayedBoundsAllowed(bounds)) {
return;
}
- for (int i = getChildCount() - 1; i >= 0; i--) {
- final Task task = getChildAt(i);
- if (bounds == null || bounds.isEmpty()) {
- task.setOverrideDisplayedBounds(null);
- } else if (task.isResizeable()) {
- task.setOverrideDisplayedBounds(bounds);
- }
- }
+ final PooledConsumer c = PooledLambda.obtainConsumer(ActivityStack::setTaskDisplayedBounds,
+ PooledLambda.__(Task.class), bounds);
+ forAllTasks(c);
+ c.recycle();
+ }
+
+ private static void setTaskDisplayedBounds(Task task, Rect bounds) {
+ task.setOverrideDisplayedBounds(bounds == null || bounds.isEmpty() ? null : bounds);
}
boolean willActivityBeVisible(IBinder token) {
@@ -3548,56 +3501,6 @@
return !r.finishing;
}
- /**
- * @return The set of running tasks through {@param tasksOut} that are available to the caller.
- * If {@param ignoreActivityType} or {@param ignoreWindowingMode} are not undefined,
- * then skip running tasks that match those types.
- */
- void getRunningTasks(List<Task> tasksOut, @ActivityType int ignoreActivityType,
- @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed,
- boolean crossUser, ArraySet<Integer> profileIds) {
- boolean focusedStack = mRootActivityContainer.getTopDisplayFocusedStack() == this;
- boolean topTask = true;
- int userId = UserHandle.getUserId(callingUid);
- for (int taskNdx = getChildCount() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = getChildAt(taskNdx);
- if (task.getTopNonFinishingActivity() == null) {
- // Skip if there are no activities in the task
- continue;
- }
- if (task.effectiveUid != callingUid) {
- if (task.mUserId != userId && !crossUser && !profileIds.contains(task.mUserId)) {
- // Skip if the caller does not have cross user permission or cannot access
- // the task's profile
- continue;
- }
- if (!allowed && !task.isActivityTypeHome()) {
- // Skip if the caller isn't allowed to fetch this task, except for the home
- // task which we always return.
- continue;
- }
- }
- if (ignoreActivityType != ACTIVITY_TYPE_UNDEFINED
- && task.getActivityType() == ignoreActivityType) {
- // Skip ignored activity type
- continue;
- }
- if (ignoreWindowingMode != WINDOWING_MODE_UNDEFINED
- && task.getWindowingMode() == ignoreWindowingMode) {
- // Skip ignored windowing mode
- continue;
- }
- if (focusedStack && topTask) {
- // For the focused stack top task, update the last stack active time so that it can
- // be used to determine the order of the tasks (it may not be set for newly created
- // tasks)
- task.touchActiveTime();
- topTask = false;
- }
- tasksOut.add(task);
- }
- }
-
void unhandledBackLocked() {
final ActivityRecord topActivity = getTopMostActivity();
if (DEBUG_SWITCH) Slog.d(TAG_SWITCH,
@@ -3685,7 +3588,10 @@
pw.println(prefix + "* " + task);
task.dump(pw, prefix + " ");
final ArrayList<ActivityRecord> activities = new ArrayList<>();
- forAllActivities((Consumer<ActivityRecord>) activities::add);
+ // Add activities by traversing the hierarchy from bottom to top, since activities
+ // are dumped in reverse order in {@link ActivityStackSupervisor#dumpHistoryList()}.
+ forAllActivities((Consumer<ActivityRecord>) activities::add,
+ false /* traverseTopToBottom */);
dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient,
dumpPackage, false, null, task);
});
@@ -3717,7 +3623,7 @@
}
ActivityRecord restartPackage(String packageName) {
- ActivityRecord starting = topRunningActivityLocked();
+ ActivityRecord starting = topRunningActivity();
// All activities that came from the package must be
// restarted as if there was a config change.
@@ -3745,19 +3651,20 @@
* @param child to remove.
* @param reason for removal.
*/
- void removeChild(Task child, String reason) {
+ void removeChild(WindowContainer child, String reason) {
if (!mChildren.contains(child)) {
// Not really in this stack anymore...
return;
}
- final ActivityDisplay display = getDisplay();
- final boolean topFocused = mRootActivityContainer.isTopDisplayFocusedStack(this);
- if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeChild: task=" + child);
+ final DisplayContent display = getDisplay();
+ if (DEBUG_TASK_MOVEMENT) {
+ Slog.d(TAG_WM, "removeChild: task=" + child + " reason=" + reason);
+ }
super.removeChild(child);
- EventLogTags.writeWmRemoveTask(child.mTaskId, mStackId);
+ EventLogTags.writeWmRemoveTask(((Task) child).mTaskId, mStackId);
if (display.isSingleTaskInstance()) {
mService.notifySingleTaskDisplayEmpty(display.mDisplayId);
@@ -3767,27 +3674,13 @@
if (!hasChild()) {
// Stack is now empty...
- removeIfPossible();
+ removeIfPossible();
}
-
- moveHomeStackToFrontIfNeeded(topFocused, display, reason);
}
@Override
- void removeChild(Task task) {
- removeChild(task, "removeChild");
- }
-
- void moveHomeStackToFrontIfNeeded(
- boolean wasTopFocusedStack, ActivityDisplay display, String reason) {
- if (!hasChild() && wasTopFocusedStack) {
- // We only need to adjust focused stack if this stack is in focus and we are not in the
- // process of moving the task to the top of the stack that will be focused.
- String myReason = reason + " leftTaskHistoryEmpty";
- if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) {
- display.moveHomeStackToFront(myReason);
- }
- }
+ void removeChild(WindowContainer child) {
+ removeChild(child, "removeChild");
}
Task createTask(int taskId, ActivityInfo info, Intent intent,
@@ -3816,10 +3709,6 @@
return task;
}
- ArrayList<Task> getAllTasks() {
- return new ArrayList<>(mChildren);
- }
-
void addChild(final Task task, final boolean toTop, boolean showForAllUsers) {
if (isSingleTaskInstance() && hasChild()) {
throw new IllegalStateException("Can only have one child on stack=" + this);
@@ -3880,11 +3769,11 @@
return;
}
super.setAlwaysOnTop(alwaysOnTop);
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
// positionChildAtTop() must be called even when always on top gets turned off because we
// need to make sure that the stack is moved from among always on top windows to below other
// always on top windows. Since the position the stack should be inserted into is calculated
- // properly in {@link ActivityDisplay#getTopInsertPosition()} in both cases, we can just
+ // properly in {@link DisplayContent#getTopInsertPosition()} in both cases, we can just
// request that the stack is put at top here.
display.positionStackAtTop(this, false /* includingParents */);
}
@@ -4015,7 +3904,7 @@
}
mWindowManager.inSurfaceTransaction(() -> {
- final Task task = mChildren.get(0);
+ final Task task = getBottomMostTask();
setWindowingMode(WINDOWING_MODE_UNDEFINED);
getDisplay().positionStackAtTop(this, false /* includingParents */);
@@ -4026,7 +3915,7 @@
});
}
- void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
+ private void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds,
boolean forceUpdate) {
// It is guaranteed that the activities requiring the update will be in the pinned stack at
// this point (either reparented before the animation into PiP, or before reparenting after
@@ -4034,29 +3923,19 @@
if (!isAttached()) {
return;
}
- ArrayList<Task> tasks = getAllTasks();
- for (int i = 0; i < tasks.size(); i++) {
- mStackSupervisor.updatePictureInPictureMode(tasks.get(i), targetStackBounds,
- forceUpdate);
- }
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ ActivityStackSupervisor::updatePictureInPictureMode, mStackSupervisor,
+ PooledLambda.__(Task.class), targetStackBounds, forceUpdate);
+ forAllTasks(c);
+ c.recycle();
}
public int getStackId() {
return mStackId;
}
- Task findHomeTask() {
- if (!isActivityTypeHome() || mChildren.isEmpty()) {
- return null;
- }
- return mChildren.get(mChildren.size() - 1);
- }
-
void prepareFreezingTaskBounds() {
- for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = mChildren.get(taskNdx);
- task.prepareFreezingBounds();
- }
+ forAllTasks(Task::prepareFreezingBounds);
}
/**
@@ -4082,26 +3961,20 @@
insetBounds = mFullyAdjustedImeBounds;
}
}
- alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds);
+
+ if (!matchParentBounds()) {
+ final boolean alignBottom = mAdjustedForIme && getDockSide() == DOCKED_TOP;
+ final PooledConsumer c = PooledLambda.obtainConsumer(Task::alignToAdjustedBounds,
+ PooledLambda.__(Task.class), adjusted ? mAdjustedBounds : getRawBounds(),
+ insetBounds, alignBottom);
+ forAllTasks(c);
+ c.recycle();
+ }
+
mDisplayContent.setLayoutNeeded();
-
updateSurfaceBounds();
}
- private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
- if (matchParentBounds()) {
- return;
- }
-
- final boolean alignBottom = mAdjustedForIme && getDockSide() == DOCKED_TOP;
-
- // Update bounds of containing tasks.
- for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = mChildren.get(taskNdx);
- task.alignToAdjustedBounds(adjustedBounds, tempInsetBounds, alignBottom);
- }
- }
-
@Override
public int setBounds(Rect bounds) {
return setBounds(getRequestedOverrideBounds(), bounds);
@@ -4210,45 +4083,27 @@
}
/**
- * Updates the passed-in {@code inOutBounds} based on the current state of the
- * pinned controller. This gets run *after* the override configuration is updated, so it's
- * safe to rely on the controller's state in here (though eventually this dependence should
- * be removed).
+ * Reset the current animation running on {@link #mBoundsAnimationTarget}.
*
- * This does NOT modify this TaskStack's configuration. However, it does, for the time-being,
- * update pinned controller state.
- *
- * @param inOutBounds the bounds to update (both input and output).
- * @return true if bounds were updated to some non-empty value.
+ * @param destinationBounds the final destination bounds
*/
- boolean calculatePinnedBoundsForConfigChange(Rect inOutBounds) {
- boolean animating = false;
- if ((mBoundsAnimatingRequested || mBoundsAnimating) && !mBoundsAnimationTarget.isEmpty()) {
- animating = true;
- getFinalAnimationBounds(mTmpRect2);
- } else {
- mTmpRect2.set(inOutBounds);
- }
- boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
- mTmpRect2, mTmpRect3);
- if (updated) {
- inOutBounds.set(mTmpRect3);
+ void resetCurrentBoundsAnimation(Rect destinationBounds) {
+ boolean animating = (mBoundsAnimatingRequested || mBoundsAnimating)
+ && !mBoundsAnimationTarget.isEmpty();
- // The final boundary is updated while there is an existing boundary animation. Let's
- // cancel this animation to prevent the obsolete animation overwritten updated bounds.
- if (animating && !inOutBounds.equals(mBoundsAnimationTarget)) {
- final DisplayContent displayContent = getDisplayContent();
- displayContent.mBoundsAnimationController.getHandler().post(() ->
- displayContent.mBoundsAnimationController.cancel(this));
- }
- // Once we've set the bounds based on the rotation of the old bounds in the new
- // orientation, clear the animation target bounds since they are obsolete, and
- // cancel any currently running animations
- mBoundsAnimationTarget.setEmpty();
- mBoundsAnimationSourceHintBounds.setEmpty();
- mCancelCurrentBoundsAnimation = true;
+ // The final boundary is updated while there is an existing boundary animation. Let's
+ // cancel this animation to prevent the obsolete animation overwritten updated bounds.
+ if (animating && !destinationBounds.equals(mBoundsAnimationTarget)) {
+ final BoundsAnimationController controller =
+ getDisplayContent().mBoundsAnimationController;
+ controller.getHandler().post(() -> controller.cancel(this));
}
- return updated;
+ // Once we've set the bounds based on the rotation of the old bounds in the new
+ // orientation, clear the animation target bounds since they are obsolete, and
+ // cancel any currently running animations
+ mBoundsAnimationTarget.setEmpty();
+ mBoundsAnimationSourceHintBounds.setEmpty();
+ mCancelCurrentBoundsAnimation = true;
}
/**
@@ -4366,17 +4221,17 @@
* @param position Target position to add the task to.
* @param showForAllUsers Whether to show the task regardless of the current user.
*/
- void addChild(Task task, int position, boolean showForAllUsers, boolean moveParents) {
+ private void addChild(Task task, int position, boolean showForAllUsers, boolean moveParents) {
// Add child task.
addChild(task, null);
// Move child to a proper position, as some restriction for position might apply.
- position = positionChildAt(
- position, task, moveParents /* includingParents */, showForAllUsers);
+ positionChildAt(position, task, moveParents /* includingParents */, showForAllUsers);
}
@Override
- void addChild(Task task, int position) {
+ void addChild(WindowContainer child, int position) {
+ final Task task = (Task) child;
addChild(task, position, task.showForAllUsers(), false /* includingParents */);
}
@@ -4415,14 +4270,15 @@
}
@Override
- void positionChildAt(int position, Task child, boolean includingParents) {
- positionChildAt(position, child, includingParents, child.showForAllUsers());
+ void positionChildAt(int position, WindowContainer child, boolean includingParents) {
+ final Task task = (Task) child;
+ positionChildAt(position, task, includingParents, task.showForAllUsers());
}
/**
- * Overridden version of {@link ActivityStack#positionChildAt(int, Task, boolean)}. Used in
- * {@link ActivityStack#addChild(Task, int, boolean showForAllUsers, boolean)}, as it can
- * receive showForAllUsers param from {@link ActivityRecord} instead of
+ * Overridden version of {@link ActivityStack#positionChildAt(int, WindowContainer, boolean)}.
+ * Used in {@link ActivityStack#addChild(Task, int, boolean showForAllUsers, boolean)}, as it
+ * can receive showForAllUsers param from {@link ActivityRecord} instead of
* {@link Task#showForAllUsers()}.
*/
private int positionChildAt(int position, Task child, boolean includingParents,
@@ -4458,11 +4314,10 @@
@Override
protected void onParentChanged(
ConfigurationContainer newParent, ConfigurationContainer oldParent) {
- // TODO(display-merge): Remove cast
- final ActivityDisplay display = newParent != null
- ? (ActivityDisplay) ((WindowContainer) newParent).getDisplayContent() : null;
- final ActivityDisplay oldDisplay = oldParent != null
- ? (ActivityDisplay) ((WindowContainer) oldParent).getDisplayContent() : null;
+ final DisplayContent display = newParent != null
+ ? ((WindowContainer) newParent).getDisplayContent() : null;
+ final DisplayContent oldDisplay = oldParent != null
+ ? ((WindowContainer) oldParent).getDisplayContent() : null;
mDisplayId = (display != null) ? display.mDisplayId : INVALID_DISPLAY;
mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY;
@@ -4538,9 +4393,10 @@
* We will start adjusting up from here.
* @param size The size of the current task list.
*/
+ // TODO(task-hierarchy): Move user to their own window container.
private int computeMinPosition(int minPosition, int size) {
while (minPosition < size) {
- final Task tmpTask = mChildren.get(minPosition);
+ final Task tmpTask = (Task) mChildren.get(minPosition);
final boolean canShowTmpTask =
tmpTask.showForAllUsers()
|| mWmService.isCurrentProfileLocked(tmpTask.mUserId);
@@ -4557,9 +4413,10 @@
* @param maxPosition The maximum position the caller is suggesting.
* We will start adjusting down from here.
*/
+ // TODO(task-hierarchy): Move user to their own window container.
private int computeMaxPosition(int maxPosition) {
while (maxPosition > 0) {
- final Task tmpTask = mChildren.get(maxPosition);
+ final Task tmpTask = (Task) mChildren.get(maxPosition);
final boolean canShowTmpTask =
tmpTask.showForAllUsers()
|| mWmService.isCurrentProfileLocked(tmpTask.mUserId);
@@ -4664,7 +4521,7 @@
// When the home stack is resizable, should always have the same stack and task bounds
if (isActivityTypeHome()) {
- final Task homeTask = findHomeTask();
+ final Task homeTask = getTopMostTask();
if (homeTask == null || homeTask.isResizeable()) {
// Calculate the home stack bounds when in docked mode and the home stack is
// resizeable.
@@ -4894,25 +4751,20 @@
* to the list of to be drawn windows the service is waiting for.
*/
void beginImeAdjustAnimation() {
- for (int j = mChildren.size() - 1; j >= 0; j--) {
- final Task task = mChildren.get(j);
- if (task.hasContentToDisplay()) {
- task.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
- task.setWaitingForDrawnIfResizingChanged();
+ forAllTasks((t) -> {
+ if (t.hasContentToDisplay()) {
+ t.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+ t.setWaitingForDrawnIfResizingChanged();
}
- }
+ });
}
- /**
- * Resets the resizing state of all windows.
- */
+ /** Resets the resizing state of all windows. */
void endImeAdjustAnimation() {
- for (int j = mChildren.size() - 1; j >= 0; j--) {
- mChildren.get(j).setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
- }
+ forAllTasks((t) -> { t.setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER); });
}
- int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) {
+ private int getMinTopStackBottom(final Rect displayContentRect, int originalStackBottom) {
return displayContentRect.top + (int)
((originalStackBottom - displayContentRect.top) * ADJUSTED_STACK_FRACTION_MIN);
}
@@ -5024,7 +4876,7 @@
return true;
}
- private boolean isMinimizedDockAndHomeStackResizable() {
+ boolean isMinimizedDockAndHomeStackResizable() {
return mDisplayContent.mDividerControllerLocked.isMinimizedDock()
&& mDisplayContent.mDividerControllerLocked.isHomeStackResizable();
}
@@ -5092,13 +4944,7 @@
* recents animation); {@code false} otherwise.
*/
boolean isTaskAnimating() {
- for (int j = mChildren.size() - 1; j >= 0; j--) {
- final Task task = mChildren.get(j);
- if (task.isTaskAnimating()) {
- return true;
- }
- }
- return false;
+ return getTask(Task::isTaskAnimating) != null;
}
@Override
@@ -5173,102 +5019,11 @@
}
boolean hasTaskForUser(int userId) {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final Task task = mChildren.get(i);
- if (task.mUserId == userId) {
- return true;
- }
- }
- return false;
- }
-
- void findTaskForResizePoint(int x, int y, int delta,
- DisplayContent.TaskForResizePointSearchResult results) {
- if (!getWindowConfiguration().canResizeTask()) {
- results.searchDone = true;
- return;
- }
-
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final Task task = mChildren.get(i);
- if (task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- results.searchDone = true;
- return;
- }
-
- // We need to use the task's dim bounds (which is derived from the visible bounds of
- // its apps windows) for any touch-related tests. Can't use the task's original
- // bounds because it might be adjusted to fit the content frame. One example is when
- // the task is put to top-left quadrant, the actual visible area would not start at
- // (0,0) after it's adjusted for the status bar.
- task.getDimBounds(mTmpRect);
- mTmpRect.inset(-delta, -delta);
- if (mTmpRect.contains(x, y)) {
- mTmpRect.inset(delta, delta);
-
- results.searchDone = true;
-
- if (!mTmpRect.contains(x, y)) {
- results.taskForResize = task;
- return;
- }
- // User touched inside the task. No need to look further,
- // focus transfer will be handled in ACTION_UP.
- return;
- }
- }
- }
-
- void setTouchExcludeRegion(Task focusedTask, int delta, Region touchExcludeRegion,
- Rect contentRect, Rect postExclude) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final Task task = mChildren.get(i);
- ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
- if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
- continue;
- }
-
- /**
- * Exclusion region is the region that TapDetector doesn't care about.
- * Here we want to remove all non-focused tasks from the exclusion region.
- * We also remove the outside touch area for resizing for all freeform
- * tasks (including the focused).
- *
- * We save the focused task region once we find it, and add it back at the end.
- *
- * If the task is home stack and it is resizable in the minimized state, we want to
- * exclude the docked stack from touch so we need the entire screen area and not just a
- * small portion which the home stack currently is resized to.
- */
-
- if (task.isActivityTypeHome() && isMinimizedDockAndHomeStackResizable()) {
- mDisplayContent.getBounds(mTmpRect);
- } else {
- task.getDimBounds(mTmpRect);
- }
-
- if (task == focusedTask) {
- // Add the focused task rect back into the exclude region once we are done
- // processing stacks.
- postExclude.set(mTmpRect);
- }
-
- final boolean isFreeformed = task.inFreeformWindowingMode();
- if (task != focusedTask || isFreeformed) {
- if (isFreeformed) {
- // If the task is freeformed, enlarge the area to account for outside
- // touch area for resize.
- mTmpRect.inset(-delta, -delta);
- // Intersect with display content rect. If we have system decor (status bar/
- // navigation bar), we want to exclude that from the tap detection.
- // Otherwise, if the app is partially placed under some system button (eg.
- // Recents, Home), pressing that button would cause a full series of
- // unwanted transfer focus/resume/pause, before we could go home.
- mTmpRect.intersect(contentRect);
- }
- touchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
- }
- }
+ final PooledPredicate p = PooledLambda.obtainPredicate(
+ Task::isTaskForUser, PooledLambda.__(Task.class), userId);
+ final Task task = getTask(p);
+ p.recycle();
+ return task != null;
}
public boolean setPinnedStackSize(Rect stackBounds, Rect tempTaskBounds) {
@@ -5429,10 +5184,7 @@
/** Called immediately prior to resizing the tasks at the end of the pinned stack animation. */
void onPipAnimationEndResize() {
mBoundsAnimating = false;
- for (int i = 0; i < mChildren.size(); i++) {
- final Task t = mChildren.get(i);
- t.clearPreserveNonFloatingState();
- }
+ forAllTasks(Task::clearPreserveNonFloatingState, false);
mWmService.requestTraversal();
}
@@ -5453,7 +5205,7 @@
if (homeStack == null) {
return true;
}
- final Task homeTask = homeStack.getTopChild();
+ final Task homeTask = homeStack.getTopMostTask();
if (homeTask == null) {
return true;
}
@@ -5585,7 +5337,7 @@
@Override
void getAnimationFrames(Rect outFrame, Rect outInsets, Rect outStableInsets,
Rect outSurfaceInsets) {
- final Task task = getTopChild();
+ final Task task = getTopMostTask();
if (task != null) {
task.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
} else {
@@ -5596,7 +5348,7 @@
@Override
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
- final Task task = getTopChild();
+ final Task task = getTopMostTask();
return task != null ? task.createRemoteAnimationTarget(record) : null;
}
@@ -5611,19 +5363,13 @@
+ getChildCount() + " tasks}";
}
- void onLockTaskPackagesUpdated() {
- for (int taskNdx = getChildCount() - 1; taskNdx >= 0; --taskNdx) {
- getChildAt(taskNdx).setLockTaskAuth();
- }
- }
-
void executeAppTransition(ActivityOptions options) {
getDisplay().mDisplayContent.executeAppTransition();
ActivityOptions.abort(options);
}
boolean shouldSleepActivities() {
- final ActivityDisplay display = getDisplay();
+ final DisplayContent display = getDisplay();
// Do not sleep activities in this stack if we're marked as focused and the keyguard
// is in the process of going away.
@@ -5644,10 +5390,10 @@
final long token = proto.start(fieldId);
dumpDebugInnerStackOnly(proto, STACK, logLevel);
proto.write(com.android.server.am.ActivityStackProto.ID, mStackId);
- for (int taskNdx = getChildCount() - 1; taskNdx >= 0; --taskNdx) {
- final Task task = getChildAt(taskNdx);
- task.dumpDebug(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel);
- }
+
+ forAllTasks((t) -> {
+ t.dumpDebug(proto, com.android.server.am.ActivityStackProto.TASKS, logLevel);
+ });
if (mResumedActivity != null) {
mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
}
@@ -5672,9 +5418,7 @@
final long token = proto.start(fieldId);
super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
proto.write(StackProto.ID, mStackId);
- for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
- mChildren.get(taskNdx).dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel);
- }
+ forAllTasks((t) -> { t.dumpDebugInnerTaskOnly(proto, StackProto.TASKS, logLevel); });
proto.write(FILLS_PARENT, matchParentBounds());
getRawBounds().dumpDebug(proto, StackProto.BOUNDS);
proto.write(DEFER_REMOVAL, mDeferRemoval);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 7356368..26d9dbc 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -165,28 +165,28 @@
static final String TAG_TASKS = TAG + POSTFIX_TASKS;
/** How long we wait until giving up on the last activity telling us it is idle. */
- static final int IDLE_TIMEOUT = 10 * 1000;
+ private static final int IDLE_TIMEOUT = 10 * 1000;
/** How long we can hold the sleep wake lock before giving up. */
- static final int SLEEP_TIMEOUT = 5 * 1000;
+ private static final int SLEEP_TIMEOUT = 5 * 1000;
// How long we can hold the launch wake lock before giving up.
- static final int LAUNCH_TIMEOUT = 10 * 1000;
+ private static final int LAUNCH_TIMEOUT = 10 * 1000;
/** How long we wait until giving up on the activity telling us it released the top state. */
- static final int TOP_RESUMED_STATE_LOSS_TIMEOUT = 500;
+ private static final int TOP_RESUMED_STATE_LOSS_TIMEOUT = 500;
- static final int IDLE_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG;
- static final int IDLE_NOW_MSG = FIRST_SUPERVISOR_STACK_MSG + 1;
- static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2;
- static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3;
- static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4;
- static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
- static final int RESTART_ACTIVITY_PROCESS_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 13;
- static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14;
- static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15;
- static final int REPORT_HOME_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 16;
- static final int TOP_RESUMED_STATE_LOSS_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 17;
+ private static final int IDLE_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG;
+ private static final int IDLE_NOW_MSG = FIRST_SUPERVISOR_STACK_MSG + 1;
+ 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 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;
+ private static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15;
+ private static final int REPORT_HOME_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 16;
+ private static final int TOP_RESUMED_STATE_LOSS_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 17;
// Used to indicate that windows of activities should be preserved during the resize.
static final boolean PRESERVE_WINDOWS = true;
@@ -237,7 +237,7 @@
// For debugging to make sure the caller when acquiring/releasing our
// wake lock is the system process.
- static final boolean VALIDATE_WAKE_LOCK_CALLER = false;
+ private static final boolean VALIDATE_WAKE_LOCK_CALLER = false;
/** The number of distinct task ids that can be assigned to the tasks of a single user */
private static final int MAX_TASK_IDS_PER_USER = UserHandle.PER_USER_RANGE;
@@ -250,11 +250,11 @@
/** Helper class to abstract out logic for fetching the set of currently running tasks */
private RunningTasks mRunningTasks;
- final ActivityStackSupervisorHandler mHandler;
+ private final ActivityStackSupervisorHandler mHandler;
final Looper mLooper;
/** Short cut */
- WindowManagerService mWindowManager;
+ private WindowManagerService mWindowManager;
/** Common synchronization logic used to save things to disks. */
PersisterQueue mPersisterQueue;
@@ -286,11 +286,11 @@
/** List of activities whose multi-window mode changed that we need to report to the
* application */
- final ArrayList<ActivityRecord> mMultiWindowModeChangedActivities = new ArrayList<>();
+ private final ArrayList<ActivityRecord> mMultiWindowModeChangedActivities = new ArrayList<>();
/** List of activities whose picture-in-picture mode changed that we need to report to the
* application */
- final ArrayList<ActivityRecord> mPipModeChangedActivities = new ArrayList<>();
+ private final ArrayList<ActivityRecord> mPipModeChangedActivities = new ArrayList<>();
/**
* Animations that for the current transition have requested not to
@@ -312,7 +312,7 @@
/** The target stack bounds for the picture-in-picture mode changed that we need to report to
* the application */
- Rect mPipModeChangedTargetStackBounds;
+ private Rect mPipModeChangedTargetStackBounds;
/** Used on user changes */
final ArrayList<UserState> mStartingUsers = new ArrayList<>();
@@ -398,6 +398,52 @@
private boolean mInitialized;
+ private final MoveTaskToFullscreenHelper mMoveTaskToFullscreenHelper =
+ new MoveTaskToFullscreenHelper();
+ private class MoveTaskToFullscreenHelper {
+ private DisplayContent mToDisplay;
+ private boolean mOnTop;
+ private Task mTopTask;
+ private boolean mSchedulePictureInPictureModeChange;
+
+ void process(ActivityStack fromStack, DisplayContent toDisplay, boolean onTop,
+ boolean schedulePictureInPictureModeChange) {
+ mSchedulePictureInPictureModeChange = schedulePictureInPictureModeChange;
+ mToDisplay = toDisplay;
+ mOnTop = onTop;
+ mTopTask = fromStack.getTopMostTask();
+
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ MoveTaskToFullscreenHelper::processTask, this, PooledLambda.__(Task.class));
+ fromStack.forAllTasks(c, false);
+ c.recycle();
+ mToDisplay = null;
+ mTopTask = null;
+ }
+
+ private void processTask(Task task) {
+ final ActivityStack toStack = mToDisplay.getOrCreateStack(
+ null, mTmpOptions, task, task.getActivityType(), mOnTop);
+
+ if (mOnTop) {
+ final boolean isTopTask = task == mTopTask;
+ // Defer resume until all the tasks have been moved to the fullscreen stack
+ task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, isTopTask /*animate*/,
+ DEFER_RESUME, mSchedulePictureInPictureModeChange,
+ "moveTasksToFullscreenStack - onTop");
+ MetricsLoggerWrapper.logPictureInPictureFullScreen(mService.mContext,
+ task.effectiveUid, task.realActivity.flattenToString());
+ } else {
+ // Position the tasks in the fullscreen stack in order at the bottom of the
+ // stack. Also defer resume until all the tasks have been moved to the
+ // fullscreen stack.
+ task.reparent(toStack, ON_TOP, REPARENT_LEAVE_STACK_IN_PLACE,
+ !ANIMATE, DEFER_RESUME, mSchedulePictureInPictureModeChange,
+ "moveTasksToFullscreenStack - NOT_onTop");
+ }
+ }
+ }
+
/**
* Description of a request to start a new activity, which has been held
* due to app switches being disabled.
@@ -1076,9 +1122,9 @@
return true;
}
- final ActivityDisplay activityDisplay =
- mRootActivityContainer.getActivityDisplayOrCreate(launchDisplayId);
- if (activityDisplay == null || activityDisplay.isRemoved()) {
+ final DisplayContent displayContent =
+ mRootActivityContainer.getDisplayContentOrCreate(launchDisplayId);
+ if (displayContent == null || displayContent.isRemoved()) {
Slog.w(TAG, "Launch on display check: display not found");
return false;
}
@@ -1094,10 +1140,10 @@
}
// Check if caller is already present on display
- final boolean uidPresentOnDisplay = activityDisplay.isUidPresent(callingUid);
+ final boolean uidPresentOnDisplay = displayContent.isUidPresent(callingUid);
- final int displayOwnerUid = activityDisplay.mDisplay.getOwnerUid();
- if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID
+ final int displayOwnerUid = displayContent.mDisplay.getOwnerUid();
+ if (displayContent.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID
&& displayOwnerUid != aInfo.applicationInfo.uid) {
// Limit launching on virtual displays, because their contents can be read from Surface
// by apps that created them.
@@ -1115,7 +1161,7 @@
}
}
- if (!activityDisplay.isPrivate()) {
+ if (!displayContent.isPrivate()) {
// Anyone can launch on a public display.
if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ " allow launch on public display");
@@ -1436,7 +1482,7 @@
currentStack, forceNonResizeable);
}
- private void moveHomeStackToFrontIfNeeded(int flags, ActivityDisplay display, String reason) {
+ private void moveHomeStackToFrontIfNeeded(int flags, DisplayContent display, String reason) {
final ActivityStack focusedStack = display.getFocusedStack();
if ((display.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
@@ -1496,8 +1542,8 @@
mService.deferWindowLayout();
try {
final int windowingMode = fromStack.getWindowingMode();
- final ActivityDisplay toDisplay =
- mRootActivityContainer.getActivityDisplay(toDisplayId);
+ final DisplayContent toDisplay =
+ mRootActivityContainer.getDisplayContent(toDisplayId);
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
// We are moving all tasks from the docked stack to the fullscreen stack,
@@ -1522,35 +1568,11 @@
// the picture-in-picture mode.
final boolean schedulePictureInPictureModeChange =
windowingMode == WINDOWING_MODE_PINNED;
- final ArrayList<Task> tasks = fromStack.getAllTasks();
- if (!tasks.isEmpty()) {
+ if (fromStack.hasChild()) {
mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
- final int size = tasks.size();
- for (int i = 0; i < size; ++i) {
- final Task task = tasks.get(i);
- final ActivityStack toStack = toDisplay.getOrCreateStack(
- null, mTmpOptions, task, task.getActivityType(), onTop);
-
- if (onTop) {
- final boolean isTopTask = i == (size - 1);
- // Defer resume until all the tasks have been moved to the fullscreen stack
- task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
- isTopTask /* animate */, DEFER_RESUME,
- schedulePictureInPictureModeChange,
- "moveTasksToFullscreenStack - onTop");
- MetricsLoggerWrapper.logPictureInPictureFullScreen(mService.mContext,
- task.effectiveUid, task.realActivity.flattenToString());
- } else {
- // Position the tasks in the fullscreen stack in order at the bottom of the
- // stack. Also defer resume until all the tasks have been moved to the
- // fullscreen stack.
- task.reparent(toStack, ON_TOP,
- REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
- schedulePictureInPictureModeChange,
- "moveTasksToFullscreenStack - NOT_onTop");
- }
- }
+ mMoveTaskToFullscreenHelper.process(
+ fromStack, toDisplay, onTop, schedulePictureInPictureModeChange);
}
mRootActivityContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
@@ -1562,12 +1584,8 @@
}
void moveTasksToFullscreenStackLocked(ActivityStack fromStack, boolean onTop) {
- moveTasksToFullscreenStackLocked(fromStack, DEFAULT_DISPLAY, onTop);
- }
-
- void moveTasksToFullscreenStackLocked(ActivityStack fromStack, int toDisplayId, boolean onTop) {
mWindowManager.inSurfaceTransaction(() ->
- moveTasksToFullscreenStackInSurfaceTransaction(fromStack, toDisplayId, onTop));
+ moveTasksToFullscreenStackInSurfaceTransaction(fromStack, DEFAULT_DISPLAY, onTop));
}
void setSplitScreenResizing(boolean resizing) {
@@ -1630,7 +1648,7 @@
try {
// Don't allow re-entry while resizing. E.g. due to docked stack detaching.
mAllowDockedStackResize = false;
- ActivityRecord r = stack.topRunningActivityLocked();
+ ActivityRecord r = stack.topRunningActivity();
stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds,
!PRESERVE_WINDOWS, DEFER_RESUME);
@@ -1650,7 +1668,7 @@
// static stacks need to be adjusted so they don't overlap with the docked stack.
// We get the bounds to use from window manager which has been adjusted for any
// screen controls and is also the same for all stacks.
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final Rect otherTaskRect = new Rect();
for (int i = display.getStackCount() - 1; i >= 0; --i) {
final ActivityStack current = display.getStackAt(i);
@@ -1731,7 +1749,6 @@
}
private void removeStackInSurfaceTransaction(ActivityStack stack) {
- final ArrayList<Task> tasks = stack.getAllTasks();
if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) {
/**
* Workaround: Force-stop all the activities in the pinned stack before we reparent them
@@ -1746,18 +1763,22 @@
stack.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
stack.mForceHidden = false;
activityIdleInternalLocked(null, false /* fromTimeout */,
- true /* processPausingActivites */, null /* configuration */);
+ true /* processPausingActivities */, null /* configuration */);
// Move all the tasks to the bottom of the fullscreen stack
moveTasksToFullscreenStackLocked(stack, !ON_TOP);
} else {
- for (int i = tasks.size() - 1; i >= 0; i--) {
- removeTaskByIdLocked(tasks.get(i).mTaskId, true /* killProcess */,
- REMOVE_FROM_RECENTS, "remove-stack");
- }
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class));
+ stack.forAllTasks(c);
+ c.recycle();
}
}
+ private void processRemoveTask(Task task) {
+ removeTask(task, true /* killProcess */, REMOVE_FROM_RECENTS, "remove-stack");
+ }
+
/**
* Removes the stack associated with the given {@param stack}. If the {@param stack} is the
* pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
@@ -1775,24 +1796,28 @@
* @param removeFromRecents Whether to also remove the task from recents.
* @return Returns true if the given task was found and removed.
*/
- boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
+ boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
String reason) {
final Task task =
mRootActivityContainer.anyTaskForId(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (task != null) {
- task.removeTaskActivitiesLocked(reason);
- cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
- mService.getLockTaskController().clearLockedTask(task);
- mService.getTaskChangeNotificationController().notifyTaskStackChanged();
- if (task.isPersistable) {
- mService.notifyTaskPersisterLocked(null, true);
- }
+ removeTask(task, killProcess, removeFromRecents, reason);
return true;
}
Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
return false;
}
+ void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
+ task.removeTaskActivitiesLocked(reason);
+ cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
+ mService.getLockTaskController().clearLockedTask(task);
+ mService.getTaskChangeNotificationController().notifyTaskStackChanged();
+ if (task.isPersistable) {
+ mService.notifyTaskPersisterLocked(null, true);
+ }
+ }
+
void cleanUpRemovedTaskLocked(Task task, boolean killProcess, boolean removeFromRecents) {
if (removeFromRecents) {
mRecentTasks.remove(task);
@@ -1899,7 +1924,7 @@
if (wasTrimmed) {
// Task was trimmed from the recent tasks list -- remove the active task record as well
// since the user won't really be able to go back to it
- removeTaskByIdLocked(task.mTaskId, killProcess, false /* removeFromRecents */,
+ removeTaskById(task.mTaskId, killProcess, false /* removeFromRecents */,
"recent-task-trimmed");
}
task.removedFromRecents();
@@ -1909,7 +1934,7 @@
* Returns the reparent target stack, creating the stack if necessary. This call also enforces
* the various checks on tasks that are going to be reparented from one stack to another.
*/
- // TODO: Look into changing users to this method to ActivityDisplay.resolveWindowingMode()
+ // TODO: Look into changing users to this method to DisplayContent.resolveWindowingMode()
ActivityStack getReparentTargetStack(Task task, ActivityStack stack, boolean toTop) {
final ActivityStack prevStack = task.getStack();
final int stackId = stack.mStackId;
@@ -2401,8 +2426,8 @@
throw new IllegalStateException("Task resolved to incompatible display");
}
- final ActivityDisplay preferredDisplay =
- mRootActivityContainer.getActivityDisplay(preferredDisplayId);
+ final DisplayContent preferredDisplay =
+ mRootActivityContainer.getDisplayContent(preferredDisplayId);
final boolean singleTaskInstance = preferredDisplay != null
&& preferredDisplay.isSingleTaskInstance();
@@ -2502,8 +2527,8 @@
void scheduleUpdatePictureInPictureModeIfNeeded(Task task, ActivityStack prevStack) {
final ActivityStack stack = task.getStack();
- if (prevStack == null || prevStack == stack
- || (!prevStack.inPinnedWindowingMode() && !stack.inPinnedWindowingMode())) {
+ if ((prevStack == null || (prevStack != stack
+ && !prevStack.inPinnedWindowingMode() && !stack.inPinnedWindowingMode()))) {
return;
}
@@ -2793,7 +2818,7 @@
// call this at the end to make sure that tasks exists on the window manager side.
setResizingDuringAnimation(task);
- final ActivityDisplay display = task.getStack().getDisplay();
+ final DisplayContent display = task.getStack().getDisplay();
final ActivityStack topSecondaryStack =
display.getTopStackInWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
if (topSecondaryStack.isActivityTypeHome()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 695f58c..4c165df 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -43,6 +43,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.RemoteAnimationAdapter;
@@ -180,8 +181,8 @@
}
options.setLaunchDisplayId(displayId);
- final ActivityDisplay display =
- mService.mRootActivityContainer.getActivityDisplay(displayId);
+ final DisplayContent display =
+ mService.mRootActivityContainer.getDisplayContent(displayId);
// The home activity will be started later, defer resuming to avoid unneccerary operations
// (e.g. start home recursively) when creating home stack.
mSupervisor.beginDeferResume();
@@ -385,6 +386,7 @@
} else {
callingPid = callingUid = -1;
}
+ final SparseArray<String> startingUidPkgs = new SparseArray<>();
final long origId = Binder.clearCallingIdentity();
try {
intents = ArrayUtils.filterNotNull(intents, Intent[]::new);
@@ -411,9 +413,14 @@
callingUid, realCallingUid, UserHandle.USER_NULL));
aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
- if (aInfo != null && (aInfo.applicationInfo.privateFlags
- & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
- throw new IllegalArgumentException("FLAG_CANT_SAVE_STATE not supported here");
+ if (aInfo != null) {
+ if ((aInfo.applicationInfo.privateFlags
+ & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
+ throw new IllegalArgumentException(
+ "FLAG_CANT_SAVE_STATE not supported here");
+ }
+ startingUidPkgs.put(aInfo.applicationInfo.uid,
+ aInfo.applicationInfo.packageName);
}
final boolean top = i == intents.length - 1;
@@ -439,6 +446,16 @@
.setOriginatingPendingIntent(originatingPendingIntent)
.setAllowBackgroundActivityStart(allowBackgroundActivityStart);
}
+ // Log if the activities to be started have different uids.
+ if (startingUidPkgs.size() > 1) {
+ final StringBuilder sb = new StringBuilder("startActivities: different apps [");
+ final int size = startingUidPkgs.size();
+ for (int i = 0; i < size; i++) {
+ sb.append(startingUidPkgs.valueAt(i)).append(i == size - 1 ? "]" : ", ");
+ }
+ sb.append(" from ").append(callingPackage);
+ Slog.wtf(TAG, sb.toString());
+ }
final ActivityRecord[] outActivity = new ActivityRecord[1];
// Lock the loop to ensure the activities launched in a sequence.
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index b2fb93d..1355424 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -252,7 +252,8 @@
final SuspendDialogInfo dialogInfo = pmi.getSuspendedDialogInfo(suspendedPackage,
suspendingPackage, mUserId);
mIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(suspendedPackage,
- suspendingPackage, dialogInfo, mUserId);
+ suspendingPackage, dialogInfo, deferCrossProfileAppsAnimationIfNecessary(),
+ mUserId);
mCallingPid = mRealCallingPid;
mCallingUid = mRealCallingUid;
mResolvedType = null;
@@ -301,7 +302,8 @@
FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE);
final KeyguardManager km = (KeyguardManager) mServiceContext
.getSystemService(KEYGUARD_SERVICE);
- final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId);
+ final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId,
+ true /* disallowBiometricsIfPolicyExists */);
if (newIntent == null) {
return null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index baa2955..6ad439e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -51,7 +51,6 @@
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
@@ -98,6 +97,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
@@ -1309,9 +1309,11 @@
String resolvedType, int userId) {
if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) {
// request phase two resolution
- mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo(
+ PackageManagerInternal packageManager = mService.getPackageManagerInternalLocked();
+ boolean isRequesterInstantApp = packageManager.isInstantApp(callingPackage, userId);
+ packageManager.requestInstantAppResolutionPhaseTwo(
auxiliaryResponse, originalIntent, resolvedType, callingPackage,
- verificationBundle, userId);
+ isRequesterInstantApp, verificationBundle, userId);
}
return InstantAppResolver.buildEphemeralInstallerIntent(
originalIntent,
@@ -1434,7 +1436,7 @@
// If there is no state change (e.g. a resumed activity is reparented to top of
// another display) to trigger a visibility/configuration checking, we have to
// update the configuration for changing to different display.
- final ActivityRecord currentTop = startedActivityStack.topRunningActivityLocked();
+ final ActivityRecord currentTop = startedActivityStack.topRunningActivity();
if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
mRootActivityContainer.ensureVisibilityAndConfig(
currentTop, currentTop.getDisplayId(),
@@ -1685,19 +1687,9 @@
return START_SUCCESS;
}
- // True if we are clearing top and resetting of a standard (default) launch mode
- // ({@code LAUNCH_MULTIPLE}) activity. The existing activity will be finished.
- final boolean clearTopAndResetStandardLaunchMode =
- (mLaunchFlags & (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED))
- == (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
- && mLaunchMode == LAUNCH_MULTIPLE;
-
boolean clearTaskForReuse = false;
if (reusedTask != null) {
- // If mStartActivity does not have a task associated with it, associate it with the
- // reused activity's task. Do not do so if we're clearing top and resetting for a
- // standard launchMode activity.
- if (mStartActivity.getTask() == null && !clearTopAndResetStandardLaunchMode) {
+ if (mStartActivity.getTask() == null) {
mStartActivity.setTaskForReuse(reusedTask);
clearTaskForReuse = true;
}
@@ -2307,7 +2299,7 @@
? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop);
final Task topTask = curTop != null ? curTop.getTask() : null;
differentTopTask = topTask != intentActivity.getTask()
- || (focusStack != null && topTask != focusStack.topTask());
+ || (focusStack != null && topTask != focusStack.getTopMostTask());
} else {
// The existing task should always be different from those in other displays.
differentTopTask = true;
@@ -2371,7 +2363,7 @@
mMovedToFront = true;
}
- if (launchStack != null && launchStack.topTask() == null) {
+ if (launchStack != null && launchStack.getTopMostTask() == null) {
// The task does not need to be reparented to the launch stack. Remove the
// launch stack if there is no activity in it.
Slog.w(TAG, "Removing an empty stack: " + launchStack);
@@ -2588,7 +2580,7 @@
// If task's parent stack is not focused - use it during adjacent launch.
return parentStack;
} else {
- if (focusedStack != null && task == focusedStack.topTask()) {
+ if (focusedStack != null && task == focusedStack.getTopMostTask()) {
// If task is already on top of focused stack - use it. We don't want to move the
// existing focused task to adjacent stack, just deliver new intent in this case.
return focusedStack;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index efd21ec8..60f051c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -698,7 +698,7 @@
private final Runnable mUpdateOomAdjRunnable = new Runnable() {
@Override
- public void run() {
+ public void run() {
mAmInternal.updateOomAdj();
}
};
@@ -1591,7 +1591,8 @@
// We should consolidate.
if (mController != null) {
// Find the first activity that is not finishing.
- final ActivityRecord next = r.getActivityStack().topRunningActivityLocked(token, 0);
+ final ActivityRecord next =
+ r.getActivityStack().topRunningActivity(token, INVALID_TASK_ID);
if (next != null) {
// ask watcher if this is allowed
boolean resumeOK = true;
@@ -1628,11 +1629,9 @@
// because we don't support returning them across task boundaries. Also, to
// keep backwards compatibility we remove the task from recents when finishing
// task with root activity.
- res = mStackSupervisor.removeTaskByIdLocked(tr.mTaskId, false /* killProcess */,
+ mStackSupervisor.removeTask(tr, false /*killProcess*/,
finishWithRootActivity, "finish-activity");
- if (!res) {
- Slog.i(TAG, "Removing task failed to finish activity");
- }
+ res = true;
// Explicitly dismissing the activity so reset its relaunch flag.
r.mRelaunchReason = RELAUNCH_REASON_NONE;
} else {
@@ -1901,7 +1900,7 @@
public boolean isTopActivityImmersive() {
enforceNotIsolatedCaller("isTopActivityImmersive");
synchronized (mGlobalLock) {
- final ActivityRecord r = getTopDisplayFocusedStack().topRunningActivityLocked();
+ final ActivityRecord r = getTopDisplayFocusedStack().topRunningActivity();
return (r != null) ? r.immersive : false;
}
}
@@ -1931,7 +1930,7 @@
public int getFrontActivityScreenCompatMode() {
enforceNotIsolatedCaller("getFrontActivityScreenCompatMode");
synchronized (mGlobalLock) {
- final ActivityRecord r = getTopDisplayFocusedStack().topRunningActivityLocked();
+ final ActivityRecord r = getTopDisplayFocusedStack().topRunningActivity();
if (r == null) {
return ActivityManager.COMPAT_MODE_UNKNOWN;
}
@@ -1945,7 +1944,7 @@
"setFrontActivityScreenCompatMode");
ApplicationInfo ai;
synchronized (mGlobalLock) {
- final ActivityRecord r = getTopDisplayFocusedStack().topRunningActivityLocked();
+ final ActivityRecord r = getTopDisplayFocusedStack().topRunningActivity();
if (r == null) {
Slog.w(TAG, "setFrontActivityScreenCompatMode failed: no top activity");
return;
@@ -2039,7 +2038,7 @@
}
@Override
- public int getActivityDisplayId(IBinder activityToken) throws RemoteException {
+ public int getDisplayId(IBinder activityToken) throws RemoteException {
synchronized (mGlobalLock) {
final ActivityStack stack = ActivityRecord.getStackLocked(activityToken);
if (stack != null && stack.mDisplayId != INVALID_DISPLAY) {
@@ -2078,7 +2077,7 @@
Slog.w(TAG, "setFocusedStack: No stack with id=" + stackId);
return;
}
- final ActivityRecord r = stack.topRunningActivityLocked();
+ final ActivityRecord r = stack.topRunningActivity();
if (r != null && r.moveFocusableActivityToTop("setFocusedStack")) {
mRootActivityContainer.resumeFocusedStacksTopActivities();
}
@@ -2133,7 +2132,7 @@
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
try {
- return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS,
+ return mStackSupervisor.removeTaskById(taskId, true, REMOVE_FROM_RECENTS,
"remove-task");
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2207,7 +2206,7 @@
int taskId = ActivityRecord.getTaskForActivityLocked(token, !nonRoot);
final Task task = mRootActivityContainer.anyTaskForId(taskId);
if (task != null) {
- return ActivityRecord.getStackLocked(token).moveTaskToBackLocked(taskId);
+ return ActivityRecord.getStackLocked(token).moveTaskToBack(task);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -2918,7 +2917,7 @@
}
final ActivityStack stack = mRootActivityContainer.getTopDisplayFocusedStack();
- if (stack == null || task != stack.topTask()) {
+ if (stack == null || task != stack.getTopMostTask()) {
throw new IllegalArgumentException("Invalid task, not in foreground");
}
@@ -3303,6 +3302,23 @@
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);
+ }
+
+ private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
+ int windowMask, Configuration config) {
+ if ((container instanceof ActivityStack)
+ && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0)
+ && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) {
+ final ActivityStack stack = (ActivityStack) container;
+ if (stack.inPinnedWindowingMode()) {
+ stack.resize(config.windowConfiguration.getBounds(),
+ null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+ PRESERVE_WINDOWS, true /* deferResume */);
+ }
+ }
}
@Override
@@ -4177,6 +4193,10 @@
final Runnable enterPipRunnable = () -> {
synchronized (mGlobalLock) {
+ if (r.getParent() == null) {
+ Slog.e(TAG, "Skip enterPictureInPictureMode, destroyed " + r);
+ return;
+ }
// Only update the saved args from the args that are set
r.pictureInPictureArgs.copyOnlySet(params);
final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
@@ -4573,6 +4593,17 @@
}
}
+ @Override
+ public void invalidateHomeTaskSnapshot(IBinder token) {
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null || !r.isActivityTypeHome()) {
+ return;
+ }
+ mWindowManager.mTaskSnapshotController.removeSnapshotCache(r.getTask().mTaskId);
+ }
+ }
+
/** Return the user id of the last resumed activity. */
@Override
public @UserIdInt
@@ -4669,6 +4700,24 @@
}
@Override
+ public void unregisterRemoteAnimations(IBinder token) {
+ mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+ "unregisterRemoteAnimations");
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null) {
+ return;
+ }
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ r.unregisterRemoteAnimations();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ @Override
public void registerRemoteAnimationForNextActivityStart(String packageName,
RemoteAnimationAdapter adapter) {
mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
@@ -4692,7 +4741,7 @@
"registerRemoteAnimations");
definition.setCallingPidUid(Binder.getCallingPid(), Binder.getCallingUid());
synchronized (mGlobalLock) {
- final ActivityDisplay display = mRootActivityContainer.getActivityDisplay(displayId);
+ final DisplayContent display = mRootActivityContainer.getDisplayContent(displayId);
if (display == null) {
Slog.e(TAG, "Couldn't find display with id: " + displayId);
return;
@@ -4900,8 +4949,8 @@
"setDisplayToSingleTaskInstance");
final long origId = Binder.clearCallingIdentity();
try {
- final ActivityDisplay display =
- mRootActivityContainer.getActivityDisplayOrCreate(displayId);
+ final DisplayContent display =
+ mRootActivityContainer.getDisplayContentOrCreate(displayId);
if (display != null) {
display.setDisplayToSingleTaskInstance();
}
@@ -5199,8 +5248,8 @@
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId, boolean deferResume) {
- final ActivityDisplay defaultDisplay =
- mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY);
+ final DisplayContent defaultDisplay =
+ mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY);
mTempConfig.setTo(getGlobalConfiguration());
final int changes = mTempConfig.updateFrom(values);
@@ -5787,7 +5836,7 @@
// If the configuration changed, and the caller is not already
// in the process of starting an activity, then find the top
// activity to check if its configuration needs to change.
- starting = mainStack.topRunningActivityLocked();
+ starting = mainStack.topRunningActivity();
}
if (starting != null) {
@@ -6206,12 +6255,12 @@
// We might change the visibilities here, so prepare an empty app transition which
// might be overridden later if we actually change visibilities.
- final ActivityDisplay activityDisplay =
- mRootActivityContainer.getActivityDisplay(displayId);
- if (activityDisplay == null) {
+ final DisplayContent displayContent =
+ mRootActivityContainer.getDisplayContent(displayId);
+ if (displayContent == null) {
return;
}
- final DisplayContent dc = activityDisplay.mDisplayContent;
+ final DisplayContent dc = displayContent.mDisplayContent;
final boolean wasTransitionSet =
dc.mAppTransition.getAppTransition() != TRANSIT_NONE;
if (!wasTransitionSet) {
@@ -6558,9 +6607,9 @@
return;
}
synchronized (mGlobalLock) {
- final ActivityDisplay activityDisplay =
- mRootActivityContainer.getActivityDisplay(displayId);
- if (activityDisplay == null) {
+ final DisplayContent displayContent =
+ mRootActivityContainer.getDisplayContent(displayId);
+ if (displayContent == null) {
// Call might come when display is not yet added or has been removed.
if (DEBUG_CONFIGURATION) {
Slog.w(TAG, "Trying to update display configuration for non-existing "
@@ -6577,7 +6626,7 @@
return;
}
process.mIsImeProcess = true;
- process.registerDisplayConfigurationListenerLocked(activityDisplay);
+ process.registerDisplayConfigurationListenerLocked(displayContent);
}
}
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 93a22ca..6d9584c 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -62,7 +62,7 @@
long origId = Binder.clearCallingIdentity();
try {
// We remove the task from recents to preserve backwards
- if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
+ if (!mService.mStackSupervisor.removeTaskById(mTaskId, false,
REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index e0c5fd05..3a33a3d 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -130,6 +130,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils.Dump;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.AttributeCache;
import com.android.server.protolog.common.ProtoLog;
import com.android.server.wm.animation.ClipRectLRAnimation;
@@ -1897,7 +1898,10 @@
for (int i = 0; i < specs.length; i++) {
AppTransitionAnimationSpec spec = specs[i];
if (spec != null) {
- final WindowContainer container = findTask(spec.taskId);
+ final PooledPredicate p = PooledLambda.obtainPredicate(
+ Task::isTaskId, PooledLambda.__(Task.class), spec.taskId);
+ final WindowContainer container = mDisplayContent.getTask(p);
+ p.recycle();
if (container == null) {
continue;
}
@@ -1918,21 +1922,6 @@
}
}
- private Task findTask(int taskId) {
- if (taskId < 0) {
- return null;
- }
- final ArrayList<Task> tasks = new ArrayList<>();
- mDisplayContent.forAllTasks(task -> {
- if (task.mTaskId == taskId) {
- tasks.add(task);
- return true;
- }
- return false;
- });
- return tasks.size() == 1 ? tasks.get(0) : null;
- }
-
void overridePendingAppTransitionMultiThumbFuture(
IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
boolean scaleUp) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index d7f4b34..6e09b94 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -48,6 +48,9 @@
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
import static com.android.server.wm.AppTransition.isKeyguardGoingAwayTransit;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -61,11 +64,14 @@
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager.TransitionType;
import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.protolog.common.ProtoLog;
+import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.function.Predicate;
@@ -177,9 +183,11 @@
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
try {
- handleClosingApps(transit, animLp, voiceInteraction);
- handleOpeningApps(transit, animLp, voiceInteraction);
- handleChangingApps(transit, animLp, voiceInteraction);
+ applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
+ animLp, voiceInteraction);
+ handleClosingApps();
+ handleOpeningApps();
+ handleChangingApps(transit);
appTransition.setLastAppTransition(transit, topOpeningApp,
topClosingApp, topChangingApp);
@@ -227,8 +235,8 @@
return mainWindow != null ? mainWindow.mAttrs : null;
}
- RemoteAnimationAdapter getRemoteAnimationOverride(ActivityRecord animLpActivity, int transit,
- ArraySet<Integer> activityTypes) {
+ RemoteAnimationAdapter getRemoteAnimationOverride(ActivityRecord animLpActivity,
+ @TransitionType int transit, ArraySet<Integer> activityTypes) {
final RemoteAnimationDefinition definition = animLpActivity.getRemoteAnimationDefinition();
if (definition != null) {
final RemoteAnimationAdapter adapter = definition.getAdapter(transit, activityTypes);
@@ -246,8 +254,8 @@
* Overrides the pending transition with the remote animation defined for the transition in the
* set of defined remote animations in the app window token.
*/
- private void overrideWithRemoteAnimationIfSet(ActivityRecord animLpActivity, int transit,
- ArraySet<Integer> activityTypes) {
+ private void overrideWithRemoteAnimationIfSet(ActivityRecord animLpActivity,
+ @TransitionType int transit, ArraySet<Integer> activityTypes) {
if (transit == TRANSIT_CRASHING_ACTIVITY_CLOSE) {
// The crash transition has higher priority than any involved remote animations.
return;
@@ -266,7 +274,7 @@
/**
* @return The window token that determines the animation theme.
*/
- private ActivityRecord findAnimLayoutParamsToken(@WindowManager.TransitionType int transit,
+ private ActivityRecord findAnimLayoutParamsToken(@TransitionType int transit,
ArraySet<Integer> activityTypes) {
ActivityRecord result;
final ArraySet<ActivityRecord> closingApps = mDisplayContent.mClosingApps;
@@ -340,26 +348,186 @@
return false;
}
- private void handleOpeningApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
+ /**
+ * Apply animation to the set of window containers.
+ *
+ * @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.
+ * @param transit The current transition type.
+ * @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes
+ * invisible.
+ * @param animLp Layout parameters in which an app transition animation runs.
+ * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
+ * interaction session driving task.
+ */
+ private void applyAnimations(ArraySet<WindowContainer> wcs, @TransitionType int transit,
+ boolean visible, LayoutParams animLp, boolean voiceInteraction) {
+ final int appsCount = wcs.size();
+ for (int i = 0; i < appsCount; i++) {
+ final WindowContainer wc = wcs.valueAt(i);
+ wc.applyAnimation(animLp, transit, visible, voiceInteraction);
+ }
+ }
+
+ /**
+ * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
+ * animation targets to higher level in the window hierarchy if possible.
+ *
+ * @param visible {@code true} to get animation targets for opening apps, {@code false} to get
+ * animation targets for closing apps.
+ * @return {@link WindowContainer}s to be animated.
+ */
+ @VisibleForTesting
+ static ArraySet<WindowContainer> getAnimationTargets(
+ ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
+ boolean visible) {
+
+ // The candidates of animation targets, which might be able to promote to higher level.
+ final LinkedList<WindowContainer> candidates = new LinkedList<>();
+ final ArraySet<ActivityRecord> apps = visible ? openingApps : closingApps;
+ for (int i = 0; i < apps.size(); ++i) {
+ final ActivityRecord app = apps.valueAt(i);
+ if (app.shouldApplyAnimation(visible)) {
+ candidates.add(app);
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Changing app %s visible=%b performLayout=%b",
+ app, app.isVisible(), false);
+ }
+ }
+
+ if (!WindowManagerService.sHierarchicalAnimations) {
+ return new ArraySet<>(candidates);
+ }
+
+ final ArraySet<ActivityRecord> otherApps = visible ? closingApps : openingApps;
+ // Ancestors of closing apps while finding animation targets for opening apps, or ancestors
+ // of opening apps while finding animation targets for closing apps.
+ final ArraySet<WindowContainer> otherAncestors = new ArraySet<>();
+ for (int i = 0; i < otherApps.size(); ++i) {
+ for (WindowContainer wc = otherApps.valueAt(i); wc != null; wc = wc.getParent()) {
+ otherAncestors.add(wc);
+ }
+ }
+
+ // The final animation targets which cannot promote to higher level anymore.
+ final ArraySet<WindowContainer> targets = new ArraySet<>();
+ final ArrayList<WindowContainer> siblings = new ArrayList<>();
+ while (!candidates.isEmpty()) {
+ final WindowContainer current = candidates.removeFirst();
+ final WindowContainer parent = current.getParent();
+ boolean canPromote = true;
+
+ if (parent == null) {
+ canPromote = false;
+ } else {
+ // In case a descendant of the parent belongs to the other group, we cannot promote
+ // the animation target from "current" to the parent.
+ //
+ // Example: Imagine we're checking if we can animate a Task instead of a set of
+ // ActivityRecords. In case an activity starts a new activity within a same Task,
+ // an ActivityRecord of an existing activity belongs to the opening apps, at the
+ // same time, the other ActivityRecord of a new activity belongs to the closing
+ // apps. In this case, we cannot promote the animation target to Task level, but
+ // need to animate each individual activity.
+ //
+ // [Task] +- [ActivityRecord1] (in opening apps)
+ // +- [ActivityRecord2] (in closing apps)
+ if (otherAncestors.contains(parent)) {
+ canPromote = false;
+ }
+
+ // Find all siblings of the current WindowContainer in "candidates", move them into
+ // a separate list "siblings", and checks if an animation target can be promoted
+ // to its parent.
+ //
+ // We can promote an animation target to its parent if and only if all visible
+ // siblings will be animating.
+ //
+ // Example: Imagine that a Task contains two visible activity record, but only one
+ // of them is included in the opening apps and the other belongs to neither opening
+ // or closing apps. This happens when an activity launches another translucent
+ // activity in the same Task. In this case, we cannot animate Task, but have to
+ // animate each activity, otherwise an activity behind the translucent activity also
+ // animates.
+ //
+ // [Task] +- [ActivityRecord1] (visible, in opening apps)
+ // +- [ActivityRecord2] (visible, not in opening apps)
+ siblings.clear();
+ for (int j = 0; j < parent.getChildCount(); ++j) {
+ final WindowContainer sibling = parent.getChildAt(j);
+ if (sibling == current || candidates.remove(sibling)) {
+ siblings.add(sibling);
+ } else if (sibling.isVisible()) {
+ canPromote = false;
+ }
+ }
+ }
+
+ if (canPromote) {
+ candidates.add(parent);
+ } else {
+ targets.addAll(siblings);
+ }
+ }
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "getAnimationTarget in=%s, out=%s",
+ apps, targets);
+ return targets;
+ }
+
+ /**
+ * Apply an app transition animation based on a set of {@link ActivityRecord}
+ *
+ * @param openingApps The list of opening apps to which an app transition animation applies.
+ * @param closingApps The list of closing apps to which an app transition animation applies.
+ * @param transit The current transition type.
+ * @param animLp Layout parameters in which an app transition animation runs.
+ * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
+ * interaction session driving task.
+ */
+ private void applyAnimations(ArraySet<ActivityRecord> openingApps,
+ ArraySet<ActivityRecord> closingApps, @TransitionType int transit,
+ LayoutParams animLp, boolean voiceInteraction) {
+ if (transit == WindowManager.TRANSIT_UNSET
+ || (openingApps.isEmpty() && closingApps.isEmpty())) {
+ return;
+ }
+
+ final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
+ openingApps, closingApps, true /* visible */);
+ final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
+ openingApps, closingApps, false /* visible */);
+ applyAnimations(openingWcs, transit, true /* visible */, animLp, voiceInteraction);
+ applyAnimations(closingWcs, transit, false /* visible */, animLp, voiceInteraction);
+
+ final AccessibilityController accessibilityController =
+ mDisplayContent.mWmService.mAccessibilityController;
+ if (accessibilityController != null) {
+ accessibilityController.onAppWindowTransitionLocked(
+ mDisplayContent.getDisplayId(), transit);
+ }
+ }
+
+ private void handleOpeningApps() {
final ArraySet<ActivityRecord> openingApps = mDisplayContent.mOpeningApps;
final int appsCount = openingApps.size();
- for (int i = 0; i < appsCount; i++) {
- ActivityRecord wtoken = openingApps.valueAt(i);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now opening app %s", wtoken);
- if (!wtoken.commitVisibility(animLp, true, transit, false, voiceInteraction)) {
+ for (int i = 0; i < appsCount; i++) {
+ final ActivityRecord app = openingApps.valueAt(i);
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now opening app %s", app);
+
+ app.commitVisibility(true /* visible */, false /* performLayout */);
+ if (!app.isAnimating(PARENTS | CHILDREN)) {
// This token isn't going to be animating. Add it to the list of tokens to
// be notified of app transition complete since the notification will not be
// sent be the app window animator.
- mDisplayContent.mNoAnimationNotifyOnTransitionFinished.add(wtoken.token);
+ mDisplayContent.mNoAnimationNotifyOnTransitionFinished.add(app.token);
}
- wtoken.updateReportedVisibilityLocked();
- wtoken.waitingToShow = false;
+ app.updateReportedVisibilityLocked();
+ app.waitingToShow = false;
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION handleAppTransitionReady()");
mService.openSurfaceTransaction();
try {
- wtoken.showAllWindowsLocked();
+ app.showAllWindowsLocked();
} finally {
mService.closeSurfaceTransaction("handleAppTransitionReady");
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
@@ -367,41 +535,40 @@
}
if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) {
- wtoken.attachThumbnailAnimation();
+ app.attachThumbnailAnimation();
} else if (mDisplayContent.mAppTransition.isNextAppTransitionOpenCrossProfileApps()) {
- wtoken.attachCrossProfileAppsThumbnailAnimation();
+ app.attachCrossProfileAppsThumbnailAnimation();
}
}
}
- private void handleClosingApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
+ private void handleClosingApps() {
final ArraySet<ActivityRecord> closingApps = mDisplayContent.mClosingApps;
final int appsCount = closingApps.size();
- for (int i = 0; i < appsCount; i++) {
- ActivityRecord wtoken = closingApps.valueAt(i);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now closing app %s", wtoken);
- // TODO: Do we need to add to mNoAnimationNotifyOnTransitionFinished like above if not
- // animating?
- wtoken.commitVisibility(animLp, false, transit, false, voiceInteraction);
- wtoken.updateReportedVisibilityLocked();
+ for (int i = 0; i < appsCount; i++) {
+ final ActivityRecord app = closingApps.valueAt(i);
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now closing app %s", app);
+
+ app.commitVisibility(false /* visible */, false /* performLayout */);
+ app.updateReportedVisibilityLocked();
// Force the allDrawn flag, because we want to start
// this guy's animations regardless of whether it's
// gotten drawn.
- wtoken.allDrawn = true;
+ app.allDrawn = true;
// Ensure that apps that are mid-starting are also scheduled to have their
// starting windows removed after the animation is complete
- if (wtoken.startingWindow != null && !wtoken.startingWindow.mAnimatingExit) {
- wtoken.removeStartingWindow();
+ if (app.startingWindow != null && !app.startingWindow.mAnimatingExit) {
+ app.removeStartingWindow();
}
if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailDown()) {
- wtoken.attachThumbnailAnimation();
+ app.attachThumbnailAnimation();
}
}
}
- private void handleChangingApps(int transit, LayoutParams animLp, boolean voiceInteraction) {
+ private void handleChangingApps(@TransitionType int transit) {
final ArraySet<ActivityRecord> apps = mDisplayContent.mChangingApps;
final int appsCount = apps.size();
for (int i = 0; i < appsCount; i++) {
@@ -419,7 +586,7 @@
}
}
- private void handleNonAppWindowsInTransition(int transit, int flags) {
+ private void handleNonAppWindowsInTransition(@TransitionType int transit, int flags) {
if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
&& (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
@@ -510,8 +677,8 @@
return true;
}
- private int maybeUpdateTransitToWallpaper(int transit, boolean openingAppHasWallpaper,
- boolean closingAppHasWallpaper) {
+ private int maybeUpdateTransitToWallpaper(@TransitionType int transit,
+ boolean openingAppHasWallpaper, boolean closingAppHasWallpaper) {
// Given no app transition pass it through instead of a wallpaper transition.
// Never convert the crashing transition.
// Never update the transition for the wallpaper if we are just docking from recents
@@ -604,7 +771,7 @@
* situation.
*/
@VisibleForTesting
- int maybeUpdateTransitToTranslucentAnim(int transit) {
+ int maybeUpdateTransitToTranslucentAnim(@TransitionType int transit) {
if (AppTransition.isChangeTransit(transit)) {
// There's no special animation to handle change animations with translucent apps
return transit;
@@ -644,7 +811,7 @@
* to determine whether animations should be clipped to the task bounds instead of stack bounds.
*/
@VisibleForTesting
- boolean isTransitWithinTask(int transit, Task task) {
+ boolean isTransitWithinTask(@TransitionType int transit, Task task) {
if (task == null
|| !mDisplayContent.mChangingApps.isEmpty()) {
// if there is no task, then we can't constrain to the task.
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 5dc88b3..9b464c2 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -185,6 +185,10 @@
resume();
};
+ // If this animator is explicitly cancelled when it's in paused state, we should not
+ // attempt to resume the animation. Use this flag to avoid such behavior.
+ private boolean mIsCancelled;
+
BoundsAnimator(BoundsAnimationTarget target, @AnimationType int animationType, Rect from,
Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState,
@SchedulePipModeChangedState int prevShedulePipModeChangedState,
@@ -221,6 +225,7 @@
if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
+ " mPrevSchedulePipModeChangedState=" + mPrevSchedulePipModeChangedState
+ " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState);
+ mIsCancelled = false;
mFinishAnimationAfterTransition = false;
mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth,
mFrom.top + mFrozenTaskHeight);
@@ -293,7 +298,7 @@
public void resume() {
if (DEBUG) Slog.d(TAG, "resume:");
mHandler.removeCallbacks(mResumeRunnable);
- super.resume();
+ if (!mIsCancelled) super.resume();
}
@Override
@@ -376,6 +381,7 @@
@Override
public void onAnimationCancel(Animator animation) {
+ mIsCancelled = true;
// Always skip the final resize when the animation is canceled
mSkipFinalResize = true;
mMoveToFullscreen = false;
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index bfa72e0..dd3365c 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -81,9 +81,6 @@
*/
private Configuration mFullConfiguration = new Configuration();
- /** The bit mask of the last override fields of full configuration. */
- private int mLastOverrideConfigurationChanges;
-
/**
* Contains merged override configuration settings from the top of the hierarchy down to this
* particular instance. It is different from {@link #mFullConfiguration} because it starts from
@@ -121,11 +118,6 @@
return mFullConfiguration;
}
- /** Returns the last changes from applying override configuration. */
- int getLastOverrideConfigurationChanges() {
- return mLastOverrideConfigurationChanges;
- }
-
/**
* Notify that parent config changed and we need to update full configuration.
* @see #mFullConfiguration
@@ -141,8 +133,7 @@
mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
resolveOverrideConfiguration(newParentConfig);
mFullConfiguration.setTo(newParentConfig);
- mLastOverrideConfigurationChanges =
- mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
+ mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
onMergedOverrideConfigurationChanged();
// This depends on the assumption that change-listeners don't do
@@ -393,7 +384,6 @@
* When you call this function, make sure that the following functions are called as well to
* keep proper z-order.
* - {@Link DisplayContent#positionStackAt(POSITION_TOP, TaskStack)};
- * - {@Link ActivityDisplay#positionChildAtTop(ActivityStack)};
* */
public void setAlwaysOnTop(boolean alwaysOnTop) {
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8e126b5..5bf8e05 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,24 +16,33 @@
package com.android.server.wm;
+import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.Build.VERSION_CODES.N;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_PRIVATE;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
@@ -55,9 +64,8 @@
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
-import static android.view.WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE;
-import static android.view.WindowManager.LayoutParams.NEEDS_MENU_UNSET;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
@@ -76,10 +84,20 @@
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+import static com.android.server.am.ActivityDisplayProto.DISPLAY;
+import static com.android.server.am.ActivityDisplayProto.FOCUSED_STACK_ID;
+import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY;
+import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
import static com.android.server.wm.DisplayContentProto.ABOVE_APP_WINDOWS;
import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
import static com.android.server.wm.DisplayContentProto.BELOW_APP_WINDOWS;
@@ -93,6 +111,7 @@
import static com.android.server.wm.DisplayContentProto.ID;
import static com.android.server.wm.DisplayContentProto.IME_WINDOWS;
import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
+import static com.android.server.wm.DisplayContentProto.OVERLAY_WINDOWS;
import static com.android.server.wm.DisplayContentProto.PINNED_STACK_CONTROLLER;
import static com.android.server.wm.DisplayContentProto.ROTATION;
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
@@ -105,6 +124,7 @@
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.server.wm.RootActivityContainer.TAG_STATES;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
@@ -124,6 +144,7 @@
import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
@@ -141,6 +162,12 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -158,15 +185,19 @@
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.DisplayMetrics;
+import android.util.IntArray;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -174,6 +205,7 @@
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.ISystemGestureExclusionListener;
+import android.view.IWindow;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputWindowHandle;
@@ -194,7 +226,9 @@
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.TriConsumer;
import com.android.internal.util.function.pooled.PooledConsumer;
+import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.AnimationThread;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.protolog.common.ProtoLog;
@@ -222,6 +256,7 @@
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>
implements WindowManagerPolicy.DisplayContentInfo {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;
+ private static final String TAG_STACK = TAG + POSTFIX_STACK;
/** The default scaling mode that scales content automatically. */
static final int FORCE_SCALING_MODE_AUTO = 0;
@@ -238,9 +273,23 @@
ActivityTaskManagerService mAtmService;
/** Unique identifier of this display. */
- private final int mDisplayId;
+ final int mDisplayId;
- /** The containers below are the only child containers the display can have. */
+ /**
+ * Most surfaces will be a child of this window. There are some special layers and windows
+ * which are always on top of others and omitted from Screen-Magnification, for example the
+ * strict mode flash or the magnification overlay itself. Those layers will be children of
+ * {@link #mOverlayContainers} where mWindowContainers contains everything else.
+ */
+ private final WindowContainers mWindowContainers =
+ new WindowContainers("mWindowContainers", mWmService);
+
+ // Contains some special windows which are always on top of others and omitted from
+ // Screen-Magnification, for example the WindowMagnification windows.
+ private final NonAppWindowContainers mOverlayContainers =
+ new NonAppWindowContainers("mOverlayContainers", mWmService);
+
+ /** The containers below are the only child containers {@link #mWindowContainers} can have. */
// Contains all window containers that are related to apps (Activities)
private final TaskStackContainers mTaskStackContainers = new TaskStackContainers(mWmService);
// Contains all non-app window containers that should be displayed above the app containers
@@ -260,7 +309,6 @@
private WindowState mTmpWindow;
private WindowState mTmpWindow2;
- private boolean mTmpRecoveringMemory;
private boolean mUpdateImeTarget;
private boolean mTmpInitial;
private int mMaxUiWidth;
@@ -314,8 +362,8 @@
* @see WindowManagerService#setForcedDisplayScalingMode(int, int)
*/
boolean mDisplayScalingDisabled;
+ final Display mDisplay;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
- private final Display mDisplay;
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
private final DisplayPolicy mDisplayPolicy;
private final DisplayRotation mDisplayRotation;
@@ -346,6 +394,9 @@
/** The desired scaling factor for compatible apps. */
float mCompatibleScreenScale;
+ /** @see #getCurrentOverrideConfigurationChanges */
+ private int mCurrentOverrideConfigurationChanges;
+
/**
* Orientation forced by some window. If there is no visible window that specifies orientation
* it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
@@ -490,20 +541,6 @@
private ScreenRotationAnimation mScreenRotationAnimation;
/**
- * We organize all top-level Surfaces in to the following layers.
- * mOverlayLayer contains a few Surfaces which are always on top of others
- * and omitted from Screen-Magnification, for example the strict mode flash or
- * the magnification overlay itself.
- * {@link #mWindowingLayer} contains everything else.
- */
- private SurfaceControl mOverlayLayer;
-
- /**
- * See {@link #mOverlayLayer}
- */
- private SurfaceControl mWindowingLayer;
-
- /**
* Sequence number for the current layout pass.
*/
int mLayoutSeq = 0;
@@ -560,6 +597,74 @@
/** Corner radius that windows should have in order to match the display. */
private final float mWindowCornerRadius;
+ private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+
+ /**
+ * Counter for next free stack ID to use for dynamic activity stacks. Unique across displays.
+ */
+ private static int sNextFreeStackId = 0;
+
+ private RootActivityContainer mRootActivityContainer;
+
+ /**
+ * All of the stacks on this display. Order matters, topmost stack is in front of all other
+ * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls
+ * changing the list should also call {@link #onStackOrderChanged()}.
+ */
+ private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>();
+
+ /** Array of all UIDs that are present on the display. */
+ private IntArray mDisplayAccessUIDs = new IntArray();
+
+ /** All tokens used to put activities on this stack to sleep (including mOffToken) */
+ final ArrayList<ActivityTaskManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
+ /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
+ ActivityTaskManagerInternal.SleepToken mOffToken;
+
+ private boolean mSleeping;
+
+ /** We started the process of removing the display from the system. */
+ private boolean mRemoving;
+
+ /**
+ * The display is removed from the system and we are just waiting for all activities on it to be
+ * finished before removing this object.
+ */
+ private boolean mRemoved;
+
+ /** The display can only contain one task. */
+ private boolean mSingleTaskInstance;
+
+ /**
+ * Non-null if the last size compatibility mode activity is using non-native screen
+ * configuration. The activity is not able to put in multi-window mode, so it exists only one
+ * per display.
+ */
+ private ActivityRecord mLastCompatModeActivity;
+
+ /**
+ * A focusable stack that is purposely to be positioned at the top. Although the stack may not
+ * have the topmost index, it is used as a preferred candidate to prevent being unable to resume
+ * target stack properly when there are other focusable always-on-top stacks.
+ */
+ private ActivityStack mPreferredTopFocusableStack;
+
+ /**
+ * If this is the same as {@link #getFocusedStack} then the activity on the top of the focused
+ * stack has been resumed. If stacks are changing position this will hold the old stack until
+ * the new stack becomes resumed after which it will be set to current focused stack.
+ */
+ private ActivityStack mLastFocusedStack;
+
+ // Used in updating the display size
+ private Point mTmpDisplaySize = new Point();
+
+ // Used in updating override configurations
+ private final Configuration mTempConfig = new Configuration();
+
+ private final RootActivityContainer.FindTaskResult
+ mTmpFindTaskResult = new RootActivityContainer.FindTaskResult();
+
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
final ActivityRecord activity = w.mActivityRecord;
@@ -839,17 +944,18 @@
* Create new {@link DisplayContent} instance, add itself to the root window container and
* initialize direct children.
* @param display May not be null.
- * @param service You know.
- * @param activityDisplay The ActivityDisplay for the display container.
+ * @param root {@link RootActivityContainer}
*/
- DisplayContent(Display display, WindowManagerService service) {
- super(service);
- if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
+ DisplayContent(Display display, RootActivityContainer root) {
+ super(root.mWindowManager);
+ if (mWmService.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
- + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
+ + " already exists="
+ + mWmService.mRoot.getDisplayContent(display.getDisplayId())
+ " new=" + display);
}
+ mRootActivityContainer = root;
mAtmService = mWmService.mAtmService;
mDisplay = display;
mDisplayId = display.getDisplayId();
@@ -863,13 +969,13 @@
calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
initializeDisplayBaseInfo();
- mAppTransition = new AppTransition(service.mContext, service, this);
- mAppTransition.registerListenerLocked(service.mActivityManagerAppTransitionNotifier);
- mAppTransitionController = new AppTransitionController(service, this);
- mUnknownAppVisibilityController = new UnknownAppVisibilityController(service, this);
+ mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
+ mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
+ mAppTransitionController = new AppTransitionController(mWmService, this);
+ mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
AnimationHandler animationHandler = new AnimationHandler();
- mBoundsAnimationController = new BoundsAnimationController(service.mContext,
+ mBoundsAnimationController = new BoundsAnimationController(mWmService.mContext,
mAppTransition, AnimationThread.getHandler(), animationHandler);
final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
@@ -887,9 +993,9 @@
mWmService.mAtmService.getRecentTasks().getInputListener());
}
- mDisplayPolicy = new DisplayPolicy(service, this);
- mDisplayRotation = new DisplayRotation(service, this);
- mCloseToSquareMaxAspectRatio = service.mContext.getResources().getFloat(
+ mDisplayPolicy = new DisplayPolicy(mWmService, this);
+ mDisplayRotation = new DisplayRotation(mWmService, this);
+ mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat(
com.android.internal.R.dimen.config_closeToSquareDisplayMaxAspectRatio);
if (isDefaultDisplay) {
// The policy may be invoked right after here, so it requires the necessary default
@@ -903,31 +1009,26 @@
mDisplayPolicy.systemReady();
}
mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius();
- mDividerControllerLocked = new DockedStackDividerController(service, this);
- mPinnedStackControllerLocked = new PinnedStackController(service, this);
+ mDividerControllerLocked = new DockedStackDividerController(mWmService, this);
+ mPinnedStackControllerLocked = new PinnedStackController(mWmService, this);
final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
.setOpaque(true)
.setContainerLayer();
mSurfaceControl = b.setName("Root").setContainerLayer().build();
- mWindowingLayer = b.setName("Display Windows").setParent(mSurfaceControl).build();
- mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
getPendingTransaction()
.setLayer(mSurfaceControl, 0)
.setLayerStack(mSurfaceControl, mDisplayId)
- .show(mSurfaceControl)
- .setLayer(mWindowingLayer, 0)
- .show(mWindowingLayer)
- .setLayer(mOverlayLayer, 1)
- .show(mOverlayLayer);
+ .show(mSurfaceControl);
getPendingTransaction().apply();
// These are the only direct children we should ever have and they are permanent.
- super.addChild(mBelowAppWindowsContainers, null);
- super.addChild(mTaskStackContainers, null);
- super.addChild(mAboveAppWindowsContainers, null);
- super.addChild(mImeWindowsContainers, null);
+ super.addChild(mWindowContainers, null);
+ super.addChild(mOverlayContainers, null);
+
+ mWindowContainers.addChildren();
+
// Sets the display content for the children.
onDisplayChanged(this);
@@ -939,9 +1040,23 @@
mDisplayReady = true;
mWmService.mAnimator.addDisplayLocked(mDisplayId);
- mInputMonitor = new InputMonitor(service, mDisplayId);
+ mInputMonitor = new InputMonitor(mWmService, mDisplayId);
mInsetsStateController = new InsetsStateController(this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
+
+ if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding 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() {
@@ -1001,6 +1116,9 @@
case TYPE_INPUT_METHOD_DIALOG:
mImeWindowsContainers.addChild(token);
break;
+ case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
+ mOverlayContainers.addChild(token);
+ break;
default:
mAboveAppWindowsContainers.addChild(token);
break;
@@ -1016,6 +1134,37 @@
return token;
}
+ SurfaceControl addShellRoot(@NonNull IWindow client, int windowType) {
+ ShellRoot root = mShellRoots.get(windowType);
+ if (root != null) {
+ if (root.getClient() == client) {
+ return root.getSurfaceControl();
+ }
+ root.clear();
+ mShellRoots.remove(windowType);
+ }
+ root = new ShellRoot(client, this, windowType);
+ SurfaceControl rootLeash = root.getSurfaceControl();
+ if (rootLeash == null) {
+ // Root didn't finish initializing, so don't add it.
+ root.clear();
+ return null;
+ }
+ mShellRoots.put(windowType, root);
+ SurfaceControl out = new SurfaceControl();
+ out.copyFrom(rootLeash);
+ return out;
+ }
+
+ void removeShellRoot(int windowType) {
+ ShellRoot root = mShellRoots.get(windowType);
+ if (root == null) {
+ return;
+ }
+ root.clear();
+ mShellRoots.remove(windowType);
+ }
+
/** Changes the display the input window token is housed on to this one. */
void reParentWindowToken(WindowToken token) {
final DisplayContent prevDc = token.getDisplayContent();
@@ -1153,9 +1302,7 @@
if (mDisplayRotation.isWaitingForRemoteRotation()) {
return;
}
- // TODO(display-merge): Remove cast
- final boolean configUpdated =
- ((ActivityDisplay) this).updateDisplayOverrideConfigurationLocked();
+ final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
if (configUpdated) {
return;
}
@@ -1186,9 +1333,8 @@
if (handled && requestingContainer instanceof ActivityRecord) {
final ActivityRecord activityRecord = (ActivityRecord) requestingContainer;
- // TODO(display-merge): Remove cast
- final boolean kept = ((ActivityDisplay) this).updateDisplayOverrideConfigurationLocked(
- config, activityRecord, false /* deferResume */, null /* result */);
+ final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord,
+ false /* deferResume */, null /* result */);
activityRecord.frozenBeforeDestroy = true;
if (!kept) {
mWmService.mAtmService.mRootActivityContainer.resumeFocusedStacksTopActivities();
@@ -1196,9 +1342,8 @@
} else {
// We have a new configuration to push so we need to update ATMS for now.
// TODO: Clean up display configuration push between ATMS and WMS after unification.
- // TODO(display-merge): Remove cast
- ((ActivityDisplay) this.mDisplayContent).updateDisplayOverrideConfigurationLocked(
- config, null /* starting */, false /* deferResume */, null);
+ updateDisplayOverrideConfigurationLocked(config, null /* starting */,
+ false /* deferResume */, null);
}
return handled;
}
@@ -1867,8 +2012,21 @@
mTaskStackContainers.onStackWindowingModeChanged(stack);
}
+ /**
+ * The value is only valid in the scope {@link #onRequestedOverrideConfigurationChanged} of the
+ * changing hierarchy and the {@link #onConfigurationChanged} of its children.
+ *
+ * @return The current changes ({@link android.content.pm.ActivityInfo.Config}) of requested
+ * override configuration.
+ */
+ int getCurrentOverrideConfigurationChanges() {
+ return mCurrentOverrideConfigurationChanges;
+ }
+
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
+ // update resources before cascade so that docked/pinned stacks use the correct info
+ preOnConfigurationChanged();
final int lastOrientation = getConfiguration().orientation;
super.onConfigurationChanged(newParentConfig);
if (mDisplayPolicy != null) {
@@ -1878,8 +2036,8 @@
if (lastOrientation != getConfiguration().orientation) {
getMetricsLogger().write(
new LogMaker(MetricsEvent.ACTION_PHONE_ORIENTATION_CHANGED)
- .setSubtype(getConfiguration().orientation)
- .addTaggedData(MetricsEvent.FIELD_DISPLAY_ID, getDisplayId()));
+ .setSubtype(getConfiguration().orientation)
+ .addTaggedData(MetricsEvent.FIELD_DISPLAY_ID, getDisplayId()));
}
// If there was no pinned stack, we still need to notify the controller of the display info
@@ -1892,8 +2050,7 @@
/**
* Updates the resources used by docked/pinned controllers. This needs to be called at the
* beginning of a configuration update cascade since the metrics from these resources are used
- * for bounds calculations. Since ActivityDisplay initiates the configuration update, this
- * should be called from there instead of DisplayContent's onConfigurationChanged.
+ * for bounds calculations.
*/
void preOnConfigurationChanged() {
final DockedStackDividerController dividerController = getDockedDividerController();
@@ -1936,49 +2093,6 @@
setWindowingMode(windowingMode);
}
- /**
- * In split-screen mode we process the IME containers above the docked divider
- * rather than directly above their target.
- */
- private boolean skipTraverseChild(WindowContainer child) {
- if (child == mImeWindowsContainers && mInputMethodTarget != null
- && !hasSplitScreenPrimaryStack()) {
- return true;
- }
- return false;
- }
-
- @Override
- boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
- // Special handling so we can process IME windows with #forAllImeWindows above their IME
- // target, or here in order if there isn't an IME target.
- if (traverseTopToBottom) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final DisplayChildWindowContainer child = mChildren.get(i);
- if (skipTraverseChild(child)) {
- continue;
- }
-
- if (child.forAllWindows(callback, traverseTopToBottom)) {
- return true;
- }
- }
- } else {
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- final DisplayChildWindowContainer child = mChildren.get(i);
- if (skipTraverseChild(child)) {
- continue;
- }
-
- if (child.forAllWindows(callback, traverseTopToBottom)) {
- return true;
- }
- }
- }
- return false;
- }
-
boolean forAllImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom);
}
@@ -2000,7 +2114,7 @@
if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is frozen, return %d", mDisplayId,
- mLastWindowForcedOrientation);
+ mLastWindowForcedOrientation);
// If the display is frozen, some activities may be in the middle of restarting, and
// thus have removed their old window. If the window has the flag to hide the lock
// screen, then the lock screen can re-appear and inflict its own orientation on us.
@@ -2014,7 +2128,7 @@
// momentarily unavailable due to activity relaunch.
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is frozen while keyguard locked, return %d",
- mDisplayId, getLastOrientation());
+ mDisplayId, getLastOrientation());
return getLastOrientation();
}
} else {
@@ -2268,7 +2382,7 @@
forAllWindows(fn, true /* traverseTopToBottom */);
fn.recycle();
return FIRST_APPLICATION_WINDOW <= targetWindowType[0]
- && targetWindowType[0] <= LAST_APPLICATION_WINDOW;
+ && targetWindowType[0] <= LAST_APPLICATION_WINDOW;
}
/**
@@ -2277,19 +2391,7 @@
*/
Task findTaskForResizePoint(int x, int y) {
final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
- mTmpTaskForResizePointSearchResult.reset();
- for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = mTaskStackContainers.getChildAt(stackNdx);
- if (!stack.getWindowConfiguration().canResizeTask()) {
- return null;
- }
-
- stack.findTaskForResizePoint(x, y, delta, mTmpTaskForResizePointSearchResult);
- if (mTmpTaskForResizePointSearchResult.searchDone) {
- return mTmpTaskForResizePointSearchResult.taskForResize;
- }
- }
- return null;
+ return mTmpTaskForResizePointSearchResult.process(mTaskStackContainers, x, y, delta);
}
void updateTouchExcludeRegion() {
@@ -2299,13 +2401,15 @@
} else {
mTouchExcludeRegion.set(mBaseDisplayRect);
final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
+ mTmpRect.setEmpty();
mTmpRect2.setEmpty();
- for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0;
- --stackNdx) {
- final ActivityStack stack = mTaskStackContainers.getChildAt(stackNdx);
- stack.setTouchExcludeRegion(focusedTask, delta, mTouchExcludeRegion,
- mDisplayFrames.mContent, mTmpRect2);
- }
+
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ DisplayContent::processTaskForTouchExcludeRegion, this,
+ PooledLambda.__(Task.class), focusedTask, delta);
+ mTaskStackContainers.forAllTasks(c);
+ c.recycle();
+
// If we removed the focused task above, add it back and only leave its
// outside touch area in the exclusion. TapDetector is not interested in
// any touch inside the focused task itself.
@@ -2335,12 +2439,56 @@
mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion);
}
+ private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) {
+ final ActivityRecord topVisibleActivity = task.getTopVisibleActivity();
+
+ if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) {
+ return;
+ }
+
+ // Exclusion region is the region that TapDetector doesn't care about.
+ // Here we want to remove all non-focused tasks from the exclusion region.
+ // We also remove the outside touch area for resizing for all freeform
+ // tasks (including the focused).
+ // We save the focused task region once we find it, and add it back at the end.
+ // If the task is home stack and it is resizable in the minimized state, we want to
+ // exclude the docked stack from touch so we need the entire screen area and not just a
+ // small portion which the home stack currently is resized to.
+ if (task.isActivityTypeHome() && task.getStack().isMinimizedDockAndHomeStackResizable()) {
+ mDisplayContent.getBounds(mTmpRect);
+ } else {
+ task.getDimBounds(mTmpRect);
+ }
+
+ if (task == focusedTask) {
+ // Add the focused task rect back into the exclude region once we are done
+ // processing stacks.
+ mTmpRect2.set(mTmpRect);
+ }
+
+ final boolean isFreeformed = task.inFreeformWindowingMode();
+ if (task != focusedTask || isFreeformed) {
+ if (isFreeformed) {
+ // If the task is freeformed, enlarge the area to account for outside
+ // touch area for resize.
+ mTmpRect.inset(-delta, -delta);
+ // Intersect with display content rect. If we have system decor (status bar/
+ // navigation bar), we want to exclude that from the tap detection.
+ // Otherwise, if the app is partially placed under some system button (eg.
+ // Recents, Home), pressing that button would cause a full series of
+ // unwanted transfer focus/resume/pause, before we could go home.
+ mTmpRect.intersect(mDisplayFrames.mContent);
+ }
+ mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
+ }
+ }
+
/**
* Union the region with all the tap exclude region provided by windows on this display.
*
* @param inOutRegion The region to be amended.
*/
- void amendWindowTapExcludeRegion(Region inOutRegion) {
+ private void amendWindowTapExcludeRegion(Region inOutRegion) {
for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) {
final WindowState win = mTapExcludeProvidingWindows.valueAt(i);
win.amendTapExcludeRegion(inOutRegion);
@@ -2383,12 +2531,8 @@
mPointerEventDispatcher.dispose();
setRotationAnimation(null);
mWmService.mAnimator.removeDisplayLocked(mDisplayId);
- mWindowingLayer.release();
- mOverlayLayer.release();
mInputMonitor.onDisplayRemoved();
- // TODO(display-merge): Remove cast
- mWmService.mDisplayNotificationController
- .dispatchDisplayRemoved((ActivityDisplay) this);
+ mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
} finally {
mDisplayReady = false;
mRemovingDisplay = false;
@@ -2499,7 +2643,7 @@
// the minimized docked stack bounds.
final boolean dockMinimized = mDividerControllerLocked.isMinimizedDock()
|| (topDockedTask != null && imeOnBottom && !dockedStack.isAdjustedForIme()
- && dockedStack.getBounds().height() < topDockedTask.getBounds().height());
+ && dockedStack.getBounds().height() < topDockedTask.getBounds().height());
// The divider could be adjusted for IME position, or be thinner than usual,
// or both. There are three possible cases:
@@ -2601,6 +2745,29 @@
}
}
+ public void dumpDebug(ProtoOutputStream proto, long fieldId,
+ @WindowTraceLogLevel int logLevel) {
+ final long token = proto.start(fieldId);
+ dumpDebugInner(proto, DISPLAY, logLevel);
+ proto.write(com.android.server.am.ActivityDisplayProto.ID, mDisplayId);
+ proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance);
+ final ActivityStack focusedStack = getFocusedStack();
+ if (focusedStack != null) {
+ proto.write(FOCUSED_STACK_ID, focusedStack.mStackId);
+ final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity();
+ if (focusedActivity != null) {
+ focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+ }
+ } else {
+ proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
+ }
+ for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = getStackAt(stackNdx);
+ stack.dumpDebug(proto, com.android.server.am.ActivityDisplayProto.STACKS, logLevel);
+ }
+ proto.end(token);
+ }
+
// TODO(proto-merge): Remove once protos for ActivityDisplay and DisplayContent are merged.
public void dumpDebugInner(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
@@ -2630,6 +2797,10 @@
final WindowToken windowToken = mImeWindowsContainers.getChildAt(i);
windowToken.dumpDebug(proto, IME_WINDOWS, logLevel);
}
+ for (int i = mOverlayContainers.getChildCount() - 1; i >= 0; --i) {
+ final WindowToken windowToken = mOverlayContainers.getChildAt(i);
+ windowToken.dumpDebug(proto, OVERLAY_WINDOWS, logLevel);
+ }
proto.write(DPI, mBaseDisplayDensity);
mDisplayInfo.dumpDebug(proto, DISPLAY_INFO);
proto.write(ROTATION, getRotation());
@@ -2657,34 +2828,36 @@
@Override
public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
super.dump(pw, prefix, dumpAll);
- pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
+ pw.print(prefix);
+ pw.println("Display: mDisplayId=" + mDisplayId + " stacks=" + getStackCount() + (
+ mSingleTaskInstance ? " mSingleTaskInstance" : ""));
final String subPrefix = " " + prefix;
pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
- pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
- pw.print("dpi");
- if (mInitialDisplayWidth != mBaseDisplayWidth
- || mInitialDisplayHeight != mBaseDisplayHeight
- || mInitialDisplayDensity != mBaseDisplayDensity) {
- pw.print(" base=");
- pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight);
- pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi");
- }
- if (mDisplayScalingDisabled) {
- pw.println(" noscale");
- }
- pw.print(" cur=");
- pw.print(mDisplayInfo.logicalWidth);
- pw.print("x"); pw.print(mDisplayInfo.logicalHeight);
- pw.print(" app=");
- pw.print(mDisplayInfo.appWidth);
- pw.print("x"); pw.print(mDisplayInfo.appHeight);
- pw.print(" rng="); pw.print(mDisplayInfo.smallestNominalAppWidth);
- pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight);
- pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
- pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
- pw.print(subPrefix + "deferred=" + mDeferredRemoval
- + " mLayoutNeeded=" + mLayoutNeeded);
- pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
+ pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
+ pw.print("dpi");
+ if (mInitialDisplayWidth != mBaseDisplayWidth
+ || mInitialDisplayHeight != mBaseDisplayHeight
+ || mInitialDisplayDensity != mBaseDisplayDensity) {
+ pw.print(" base=");
+ pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight);
+ pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi");
+ }
+ if (mDisplayScalingDisabled) {
+ pw.println(" noscale");
+ }
+ pw.print(" cur=");
+ pw.print(mDisplayInfo.logicalWidth);
+ pw.print("x"); pw.print(mDisplayInfo.logicalHeight);
+ pw.print(" app=");
+ pw.print(mDisplayInfo.appWidth);
+ pw.print("x"); pw.print(mDisplayInfo.appHeight);
+ pw.print(" rng="); pw.print(mDisplayInfo.smallestNominalAppWidth);
+ pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight);
+ pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
+ pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
+ pw.print(subPrefix + "deferred=" + mDeferredRemoval
+ + " mLayoutNeeded=" + mLayoutNeeded);
+ pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
pw.println();
pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
@@ -2693,6 +2866,12 @@
if (mLastFocus != mCurrentFocus) {
pw.print(" mLastFocus="); pw.println(mLastFocus);
}
+ if (mPreferredTopFocusableStack != null) {
+ pw.println(prefix + "mPreferredTopFocusableStack=" + mPreferredTopFocusableStack);
+ }
+ if (mLastFocusedStack != null) {
+ pw.println(prefix + "mLastFocusedStack=" + mLastFocusedStack);
+ }
if (mLosingFocus.size() > 0) {
pw.println();
pw.println(" Windows losing focus:");
@@ -2770,6 +2949,10 @@
if (splitScreenPrimaryStack != null) {
pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
}
+ final ActivityStack recentsStack = getRecentsStack();
+ if (recentsStack != null) {
+ pw.println(prefix + "recentsStack=" + recentsStack.getName());
+ }
pw.println();
mDividerControllerLocked.dump(prefix, pw);
@@ -2841,7 +3024,7 @@
}
final WindowState win = getWindow(w ->
w.mAttrs.type == TYPE_TOAST && w.mOwnerUid == uid && !w.mPermanentlyHidden
- && !w.mWindowRemovalAllowed);
+ && !w.mWindowRemovalAllowed);
return win == null;
}
@@ -2921,7 +3104,7 @@
}
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",
- mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
+ mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
final WindowState oldFocus = mCurrentFocus;
mCurrentFocus = newFocus;
mLosingFocus.remove(newFocus);
@@ -3223,7 +3406,7 @@
// target app together.
final boolean shouldAttachToDisplay = (mMagnificationSpec != null);
final SurfaceControl newParent =
- shouldAttachToDisplay ? mWindowingLayer : computeImeParent();
+ shouldAttachToDisplay ? mWindowContainers.getSurfaceControl() : computeImeParent();
if (newParent != null) {
getPendingTransaction().reparent(mImeWindowsContainers.mSurfaceControl, newParent);
scheduleAnimation();
@@ -3248,39 +3431,7 @@
}
// Otherwise, we just attach it to the display.
- return mWindowingLayer;
- }
-
- boolean getNeedsMenu(WindowState top, WindowManagerPolicy.WindowState bottom) {
- if (top.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
- return top.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
- }
-
- // Used to indicate we have reached the first window in the range we are interested in.
- mTmpWindow = null;
-
- // TODO: Figure-out a more efficient way to do this.
- final WindowState candidate = getWindow(w -> {
- if (w == top) {
- // Reached the first window in the range we are interested in.
- mTmpWindow = w;
- }
- if (mTmpWindow == null) {
- return false;
- }
-
- if (w.mAttrs.needsMenuKey != NEEDS_MENU_UNSET) {
- return true;
- }
- // If we reached the bottom of the range of windows we are considering,
- // assume no menu is needed.
- if (w == bottom) {
- return true;
- }
- return false;
- });
-
- return candidate != null && candidate.mAttrs.needsMenuKey == NEEDS_MENU_SET_TRUE;
+ return mWindowContainers.getSurfaceControl();
}
void setLayoutNeeded() {
@@ -3395,7 +3546,7 @@
boolean wallpaperEnabled = mWmService.mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableWallpaperService)
&& mWmService.mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_checkWallpaperAtBoot)
+ com.android.internal.R.bool.config_checkWallpaperAtBoot)
&& !mWmService.mOnlyCore;
final boolean haveBootMsg = drawnWindowTypes.get(TYPE_BOOT_PROGRESS);
@@ -3586,13 +3737,11 @@
}
if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
"after finishPostLayoutPolicyLw", pendingLayoutChanges);
- mInsetsStateController.onPostLayout();
+ mInsetsStateController.onPostLayout();
} while (pendingLayoutChanges != 0);
mTmpApplySurfaceChangesTransactionState.reset();
- mTmpRecoveringMemory = recoveringMemory;
-
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");
try {
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
@@ -3852,12 +4001,56 @@
}
static final class TaskForResizePointSearchResult {
- boolean searchDone;
- Task taskForResize;
+ private Task taskForResize;
+ private int x;
+ private int y;
+ private int delta;
+ private Rect mTmpRect = new Rect();
- void reset() {
- searchDone = false;
+ Task process(WindowContainer root, int x, int y, int delta) {
taskForResize = null;
+ this.x = x;
+ this.y = y;
+ this.delta = delta;
+ mTmpRect.setEmpty();
+
+ final PooledFunction f = PooledLambda.obtainFunction(
+ TaskForResizePointSearchResult::processTask, this, PooledLambda.__(Task.class));
+ root.forAllTasks(f);
+ f.recycle();
+
+ return taskForResize;
+ }
+
+ private boolean processTask(Task task) {
+ if (!task.getStack().getWindowConfiguration().canResizeTask()) {
+ return true;
+ }
+
+ if (task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ return true;
+ }
+
+ // We need to use the task's dim bounds (which is derived from the visible bounds of
+ // its apps windows) for any touch-related tests. Can't use the task's original
+ // bounds because it might be adjusted to fit the content frame. One example is when
+ // the task is put to top-left quadrant, the actual visible area would not start at
+ // (0,0) after it's adjusted for the status bar.
+ task.getDimBounds(mTmpRect);
+ mTmpRect.inset(-delta, -delta);
+ if (mTmpRect.contains(x, y)) {
+ mTmpRect.inset(delta, delta);
+
+ if (!mTmpRect.contains(x, y)) {
+ taskForResize = task;
+ return true;
+ }
+ // User touched inside the task. No need to look further,
+ // focus transfer will be handled in ACTION_UP.
+ return true;
+ }
+
+ return false;
}
}
@@ -4045,8 +4238,7 @@
+ " already exist on display=" + this + " stack=" + stack);
}
mSplitScreenPrimaryStack = stack;
- // TODO(display-merge): Remove cast
- ((ActivityDisplay) this.mDisplayContent).onSplitScreenModeActivated();
+ mDisplayContent.onSplitScreenModeActivated();
mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
}
@@ -4060,8 +4252,7 @@
mPinnedStack = null;
} else if (stack == mSplitScreenPrimaryStack) {
mSplitScreenPrimaryStack = null;
- // TODO(display-merge): Remove cast
- ((ActivityDisplay) this.mDisplayContent).onSplitScreenModeDismissed();
+ mDisplayContent.onSplitScreenModeDismissed();
// Re-set the split-screen create mode whenever the split-screen stack is removed.
mWmService.setDockedStackCreateStateLocked(
SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
@@ -4086,8 +4277,7 @@
@Override
protected void removeChild(ActivityStack stack) {
super.removeChild(stack);
- // TODO(display-merge): Remove cast
- ((ActivityDisplay) this.mDisplayContent).onStackRemoved(stack);
+ mDisplayContent.onStackRemoved(stack);
removeStackReferenceIfNeeded(stack);
}
@@ -4297,7 +4487,7 @@
if (mHomeStack != null && mHomeStack.isVisible()
&& mDividerControllerLocked.isMinimizedDock()
&& !(mDividerControllerLocked.isHomeStackResizable()
- && mHomeStack.matchParentBounds())) {
+ && mHomeStack.matchParentBounds())) {
final int orientation = mHomeStack.getOrientation();
if (orientation != SCREEN_ORIENTATION_UNSET) {
return orientation;
@@ -4310,14 +4500,14 @@
if (orientation != SCREEN_ORIENTATION_UNSET
&& orientation != SCREEN_ORIENTATION_BEHIND) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
- "App is requesting an orientation, return %d for display id=%d",
- orientation, mDisplayId);
+ "App is requesting an orientation, return %d for display id=%d",
+ orientation, mDisplayId);
return orientation;
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
- "No app is requesting an orientation, return %d for display id=%d",
- getLastOrientation(), mDisplayId);
+ "No app is requesting an orientation, return %d for display id=%d",
+ getLastOrientation(), mDisplayId);
// The next app has not been requested to be visible, so we keep the current orientation
// to prevent freezing/unfreezing the display too early.
return getLastOrientation();
@@ -4486,7 +4676,7 @@
wt.windowType, wt.mOwnerCanManageAppTokens);
if (needAssignIme && layer >= mWmService.mPolicy.getWindowLayerFromTypeLw(
- TYPE_INPUT_METHOD_DIALOG, true)) {
+ TYPE_INPUT_METHOD_DIALOG, true)) {
imeContainer.assignRelativeLayer(t, wt.getSurfaceControl(), -1);
needAssignIme = false;
}
@@ -4497,6 +4687,126 @@
}
}
+ private class WindowContainers extends DisplayChildWindowContainer<WindowContainer> {
+ private final String mName;
+
+ WindowContainers(String name, WindowManagerService service) {
+ super(service);
+ mName = name;
+ }
+
+ @Override
+ void assignChildLayers(SurfaceControl.Transaction t) {
+ mBelowAppWindowsContainers.assignLayer(t, 0);
+ mTaskStackContainers.assignLayer(t, 1);
+ mAboveAppWindowsContainers.assignLayer(t, 2);
+
+ final WindowState imeTarget = mInputMethodTarget;
+ boolean needAssignIme = true;
+
+ // In the case where we have an IME target that is not in split-screen mode IME
+ // assignment is easy. We just need the IME to go directly above the target. This way
+ // children of the target will naturally go above the IME and everyone is happy.
+ //
+ // In the case of split-screen windowing mode, we need to elevate the IME above the
+ // docked divider while keeping the app itself below the docked divider, so instead
+ // we use relative layering of the IME targets child windows, and place the IME in
+ // the non-app layer (see {@link AboveAppWindowContainers#assignChildLayers}).
+ //
+ // In the case the IME target is animating, the animation Z order may be different
+ // than the WindowContainer Z order, so it's difficult to be sure we have the correct
+ // IME target. In this case we just layer the IME over all transitions by placing it
+ // in the above applications layer.
+ //
+ // In the case where we have no IME target we assign it where its base layer would
+ // place it in the AboveAppWindowContainers.
+ //
+ // Keep IME window in mAboveAppWindowsContainers as long as app's starting window
+ // exists so it get's layered above the starting window.
+ if (imeTarget != null && !(imeTarget.mActivityRecord != null
+ && imeTarget.mActivityRecord.hasStartingWindow()) && (
+ !(imeTarget.inSplitScreenWindowingMode()
+ || imeTarget.mToken.isAppTransitioning()) && (
+ imeTarget.getSurfaceControl() != null))) {
+ mImeWindowsContainers.assignRelativeLayer(t, imeTarget.getSurfaceControl(),
+ // TODO: We need to use an extra level on the app surface to ensure
+ // this is always above SurfaceView but always below attached window.
+ 1);
+ needAssignIme = false;
+ }
+
+ // Above we have assigned layers to our children, now we ask them to assign
+ // layers to their children.
+ mBelowAppWindowsContainers.assignChildLayers(t);
+ mTaskStackContainers.assignChildLayers(t);
+ mAboveAppWindowsContainers.assignChildLayers(t,
+ needAssignIme ? mImeWindowsContainers : null);
+ mImeWindowsContainers.assignChildLayers(t);
+ }
+
+ @Override
+ String getName() {
+ return mName;
+ }
+
+ void addChildren() {
+ addChild(mBelowAppWindowsContainers, null);
+ addChild(mTaskStackContainers, null);
+ addChild(mAboveAppWindowsContainers, null);
+ addChild(mImeWindowsContainers, null);
+ }
+
+ /**
+ * In split-screen mode we process the IME containers above the docked divider
+ * rather than directly above their target.
+ */
+ private boolean skipTraverseChild(WindowContainer child) {
+ return child == mImeWindowsContainers && mInputMethodTarget != null
+ && !hasSplitScreenPrimaryStack();
+ }
+
+ @Override
+ boolean forAllWindows(ToBooleanFunction<WindowState> callback,
+ boolean traverseTopToBottom) {
+ // Special handling so we can process IME windows with #forAllImeWindows above their IME
+ // target, or here in order if there isn't an IME target.
+ if (traverseTopToBottom) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ if (skipTraverseChild(child)) {
+ continue;
+ }
+
+ if (child.forAllWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ }
+ } 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;
+ }
+
+ if (child.forAllWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ void positionChildAt(int position, WindowContainer child, boolean includingParents) {
+ // Children of the WindowContainers are statically ordered, so the real intention here
+ // is to perform the operation on the display and not the static direct children.
+ getParent().positionChildAt(position, this, includingParents);
+ }
+ }
+
/**
* Window container class that contains all containers on this display that are not related to
* Apps. E.g. status bar.
@@ -4510,7 +4820,7 @@
// Tokens with higher base layer are z-ordered on-top.
mWmService.mPolicy.getWindowLayerFromTypeLw(token1.windowType,
token1.mOwnerCanManageAppTokens)
- < mWmService.mPolicy.getWindowLayerFromTypeLw(token2.windowType,
+ < mWmService.mPolicy.getWindowLayerFromTypeLw(token2.windowType,
token2.mOwnerCanManageAppTokens) ? -1 : 1;
private final Predicate<WindowState> mGetOrientingWindow = w -> {
@@ -4562,7 +4872,7 @@
}
ProtoLog.v(WM_DEBUG_ORIENTATION,
"%s forcing orientation to %d for display id=%d", win, req,
- mDisplayId);
+ mDisplayId);
return (mLastWindowForcedOrientation = req);
}
@@ -4602,11 +4912,6 @@
}
}
- SurfaceControl.Builder makeSurface(SurfaceSession s) {
- return mWmService.makeSurfaceBuilder(s)
- .setParent(mWindowingLayer);
- }
-
@Override
SurfaceSession getSession() {
return mSession;
@@ -4621,7 +4926,7 @@
}
return b.setName(child.getName())
- .setParent(mWindowingLayer);
+ .setParent(mSurfaceControl);
}
/**
@@ -4632,14 +4937,14 @@
*/
SurfaceControl.Builder makeOverlay() {
return mWmService.makeSurfaceBuilder(mSession)
- .setParent(mOverlayLayer);
+ .setParent(mOverlayContainers.getSurfaceControl());
}
/**
- * Reparents the given surface to mOverlayLayer.
+ * Reparents the given surface to {@link #mOverlayContainers}' SurfaceControl.
*/
void reparentToOverlay(Transaction transaction, SurfaceControl surface) {
- transaction.reparent(surface, mOverlayLayer);
+ transaction.reparent(surface, mOverlayContainers.getSurfaceControl());
}
void applyMagnificationSpec(MagnificationSpec spec) {
@@ -4651,7 +4956,11 @@
// Re-parent IME's SurfaceControl when MagnificationSpec changed.
updateImeParent();
- applyMagnificationSpec(getPendingTransaction(), spec);
+ if (spec.scale != 1.0) {
+ applyMagnificationSpec(getPendingTransaction(), spec);
+ } else {
+ clearMagnificationSpec(getPendingTransaction());
+ }
getPendingTransaction().apply();
}
@@ -4671,54 +4980,11 @@
@Override
void assignChildLayers(SurfaceControl.Transaction t) {
+ mWindowContainers.assignLayer(t, 0);
+ mOverlayContainers.assignLayer(t, 1);
- // These are layers as children of "mWindowingLayer"
- mBelowAppWindowsContainers.assignLayer(t, 0);
- mTaskStackContainers.assignLayer(t, 1);
- mAboveAppWindowsContainers.assignLayer(t, 2);
-
- final WindowState imeTarget = mInputMethodTarget;
- boolean needAssignIme = true;
-
- // In the case where we have an IME target that is not in split-screen
- // mode IME assignment is easy. We just need the IME to go directly above
- // the target. This way children of the target will naturally go above the IME
- // and everyone is happy.
- //
- // In the case of split-screen windowing mode, we need to elevate the IME above the
- // docked divider while keeping the app itself below the docked divider, so instead
- // we use relative layering of the IME targets child windows, and place the
- // IME in the non-app layer (see {@link AboveAppWindowContainers#assignChildLayers}).
- //
- // In the case the IME target is animating, the animation Z order may be different
- // than the WindowContainer Z order, so it's difficult to be sure we have the correct
- // IME target. In this case we just layer the IME over all transitions by placing it in the
- // above applications layer.
- //
- // In the case where we have no IME target we assign it where it's base layer would
- // place it in the AboveAppWindowContainers.
- //
- // Keep IME window in mAboveAppWindowsContainers as long as app's starting window exists
- // so it get's layered above the starting window.
- if (imeTarget != null
- && !(imeTarget.mActivityRecord != null && imeTarget.mActivityRecord.hasStartingWindow())
- && (!(imeTarget.inSplitScreenWindowingMode()
- || imeTarget.mToken.isAppTransitioning())
- && (imeTarget.getSurfaceControl() != null))) {
- mImeWindowsContainers.assignRelativeLayer(t, imeTarget.getSurfaceControl(),
- // TODO: We need to use an extra level on the app surface to ensure
- // this is always above SurfaceView but always below attached window.
- 1);
- needAssignIme = false;
- }
-
- // Above we have assigned layers to our children, now we ask them to assign
- // layers to their children.
- mBelowAppWindowsContainers.assignChildLayers(t);
- mTaskStackContainers.assignChildLayers(t);
- mAboveAppWindowsContainers.assignChildLayers(t,
- needAssignIme == true ? mImeWindowsContainers : null);
- mImeWindowsContainers.assignChildLayers(t);
+ mWindowContainers.assignChildLayers(t);
+ mOverlayContainers.assignChildLayers(t);
}
/**
@@ -4820,7 +5086,7 @@
if (mAppTransition.isTransitionSet()) {
ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
"Execute app transition: %s, displayId: %d Callers=%s",
- mAppTransition, mDisplayId, Debug.getCallers(5));
+ mAppTransition, mDisplayId, Debug.getCallers(5));
mAppTransition.setReady();
mWmService.mWindowPlacerLocked.requestTraversal();
}
@@ -4885,8 +5151,8 @@
}
/**
- * Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and
- * {@link #mOverlayLayer} to the specified SurfaceControl.
+ * Re-parent the DisplayContent's top surface, {@link #mSurfaceControl} to the specified
+ * SurfaceControl.
*
* @param win The window which owns the SurfaceControl. This indicates the z-order of the
* windows of this display against the windows on the parent display.
@@ -4964,11 +5230,11 @@
@VisibleForTesting
SurfaceControl getWindowingLayer() {
- return mWindowingLayer;
+ return mWindowContainers.getSurfaceControl();
}
SurfaceControl getOverlayLayer() {
- return mOverlayLayer;
+ return mOverlayContainers.getSurfaceControl();
}
/**
@@ -5247,4 +5513,1150 @@
}
return mMetricsLogger;
}
+
+ void onDisplayChanged() {
+ // The window policy is responsible for stopping activities on the default display.
+ final int displayId = mDisplay.getDisplayId();
+ if (displayId != DEFAULT_DISPLAY) {
+ final int displayState = mDisplay.getState();
+ if (displayState == Display.STATE_OFF && mOffToken == null) {
+ mOffToken = mAtmService.acquireSleepToken("Display-off", displayId);
+ } else if (displayState == Display.STATE_ON && mOffToken != null) {
+ mOffToken.release();
+ mOffToken = null;
+ }
+ }
+
+ mDisplay.getRealSize(mTmpDisplaySize);
+ setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
+ updateDisplayInfo();
+ mWmService.requestTraversal();
+ }
+
+ void addStack(ActivityStack stack, int position) {
+ setStackOnDisplay(stack, position);
+ positionStackAt(stack, position);
+ mAtmService.updateSleepIfNeededLocked();
+ }
+
+ void onStackRemoved(ActivityStack stack) {
+ if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
+ Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId=" + mDisplayId);
+ }
+ if (mPreferredTopFocusableStack == stack) {
+ mPreferredTopFocusableStack = null;
+ }
+ releaseSelfIfNeeded();
+ mAtmService.updateSleepIfNeededLocked();
+ onStackOrderChanged(stack);
+ }
+
+ void positionStackAtTop(ActivityStack stack, boolean includingParents) {
+ positionStackAtTop(stack, includingParents, null /* updateLastFocusedStackReason */);
+ }
+
+ void positionStackAtTop(ActivityStack stack, boolean includingParents,
+ String updateLastFocusedStackReason) {
+ positionStackAt(stack, getStackCount(), includingParents, updateLastFocusedStackReason);
+ }
+
+ void positionStackAtBottom(ActivityStack stack) {
+ positionStackAtBottom(stack, null /* updateLastFocusedStackReason */);
+ }
+
+ void positionStackAtBottom(ActivityStack stack, String updateLastFocusedStackReason) {
+ positionStackAt(stack, 0, false /* includingParents */, updateLastFocusedStackReason);
+ }
+
+ private void positionStackAt(ActivityStack stack, int position) {
+ positionStackAt(stack, position, false /* includingParents */,
+ null /* updateLastFocusedStackReason */);
+ }
+
+ private void positionStackAt(ActivityStack stack, int position, boolean includingParents,
+ String updateLastFocusedStackReason) {
+ // TODO: Keep in sync with WindowContainer.positionChildAt(), once we change that to adjust
+ // the position internally, also update the logic here
+ final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null
+ ? getFocusedStack() : null;
+ final boolean wasContained = getIndexOf(stack) >= 0;
+ if (mSingleTaskInstance && getStackCount() == 1 && !wasContained) {
+ throw new IllegalStateException(
+ "positionStackAt: Can only have one task on display=" + this);
+ }
+
+ // Since positionChildAt() is called during the creation process of pinned stacks,
+ // ActivityStack#getStack() can be null.
+ positionStackAt(position, stack, includingParents);
+
+ // The insert position may be adjusted to non-top when there is always-on-top stack. Since
+ // the original position is preferred to be top, the stack should have higher priority when
+ // we are looking for top focusable stack. The condition {@code wasContained} restricts the
+ // preferred stack is set only when moving an existing stack to top instead of adding a new
+ // stack that may be too early (e.g. in the middle of launching or reparenting).
+ if (wasContained && position >= getStackCount() - 1 && stack.isFocusableAndVisible()) {
+ mPreferredTopFocusableStack = stack;
+ } else if (mPreferredTopFocusableStack == stack) {
+ mPreferredTopFocusableStack = null;
+ }
+
+ if (updateLastFocusedStackReason != null) {
+ final ActivityStack currentFocusedStack = getFocusedStack();
+ if (currentFocusedStack != prevFocusedStack) {
+ mLastFocusedStack = prevFocusedStack;
+ EventLogTags.writeWmFocusedStack(mRootActivityContainer.mCurrentUser, mDisplayId,
+ currentFocusedStack == null ? -1 : currentFocusedStack.getStackId(),
+ mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(),
+ updateLastFocusedStackReason);
+ }
+ }
+
+ onStackOrderChanged(stack);
+ }
+
+ ActivityStack getStack(int stackId) {
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getStackAt(i);
+ if (stack.mStackId == stackId) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ boolean alwaysCreateStack(int windowingMode, int activityType) {
+ // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
+ // modes so that we can manage visual ordering and return types correctly.
+ return activityType == ACTIVITY_TYPE_STANDARD
+ && (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_FREEFORM
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ }
+
+ /**
+ * Returns an existing stack compatible with the windowing mode and activity type or creates one
+ * if a compatible stack doesn't exist.
+ * @see #getStack(int, int)
+ * @see #createStack(int, int, boolean)
+ */
+ ActivityStack getOrCreateStack(int windowingMode, int activityType,
+ boolean onTop) {
+ if (!alwaysCreateStack(windowingMode, activityType)) {
+ ActivityStack stack = getStack(windowingMode, activityType);
+ if (stack != null) {
+ return stack;
+ }
+ }
+ return createStack(windowingMode, activityType, onTop);
+ }
+
+ /**
+ * Returns an existing stack compatible with the input params or creates one
+ * if a compatible stack doesn't exist.
+ * @see #getOrCreateStack(int, int, boolean)
+ */
+ ActivityStack getOrCreateStack(@Nullable ActivityRecord r,
+ @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
+ boolean onTop) {
+ // First preference is the windowing mode in the activity options if set.
+ int windowingMode = (options != null)
+ ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+ // Validate that our desired windowingMode will work under the current conditions.
+ // UNDEFINED windowing mode is a valid result and means that the new stack will inherit
+ // it's display's windowing mode.
+ windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
+ return getOrCreateStack(windowingMode, activityType, onTop);
+ }
+
+ @VisibleForTesting
+ int getNextStackId() {
+ return sNextFreeStackId++;
+ }
+
+ /**
+ * Creates a stack matching the input windowing mode and activity type on this display.
+ * @param windowingMode The windowing mode the stack should be created in. If
+ * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
+ * inherit its parent's windowing mode.
+ * @param activityType The activityType the stack should be created in. If
+ * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
+ * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+ * @param onTop If true the stack will be created at the top of the display, else at the bottom.
+ * @return The newly created stack.
+ */
+ ActivityStack createStack(int windowingMode, int activityType, boolean onTop) {
+ if (mSingleTaskInstance && getStackCount() > 0) {
+ // Create stack on default display instead since this display can only contain 1 stack.
+ // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
+ // this goes away once ActivityView is no longer using virtual displays.
+ return mRootActivityContainer.getDefaultDisplay().createStack(
+ windowingMode, activityType, onTop);
+ }
+
+ if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+ // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
+ // anything else should be passing it in anyways...
+ activityType = ACTIVITY_TYPE_STANDARD;
+ }
+
+ if (activityType != ACTIVITY_TYPE_STANDARD) {
+ // For now there can be only one stack of a particular non-standard activity type on a
+ // display. So, get that ignoring whatever windowing mode it is currently in.
+ ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
+ if (stack != null) {
+ throw new IllegalArgumentException("Stack=" + stack + " of activityType="
+ + activityType + " already on display=" + this + ". Can't have multiple.");
+ }
+ }
+
+ if (!isWindowingModeSupported(windowingMode, mAtmService.mSupportsMultiWindow,
+ mAtmService.mSupportsSplitScreenMultiWindow,
+ mAtmService.mSupportsFreeformWindowManagement,
+ mAtmService.mSupportsPictureInPicture, activityType)) {
+ throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
+ + windowingMode);
+ }
+
+ final int stackId = getNextStackId();
+ return createStackUnchecked(windowingMode, activityType, stackId, onTop);
+ }
+
+ @VisibleForTesting
+ ActivityStack createStackUnchecked(int windowingMode, int activityType,
+ int stackId, boolean onTop) {
+ if (windowingMode == WINDOWING_MODE_PINNED && activityType != ACTIVITY_TYPE_STANDARD) {
+ throw new IllegalArgumentException("Stack with windowing mode cannot with non standard "
+ + "activity type.");
+ }
+ return new ActivityStack(this, stackId, mRootActivityContainer.mStackSupervisor,
+ windowingMode, activityType, onTop);
+ }
+
+ /**
+ * Get the preferred focusable stack in priority. If the preferred stack does not exist, find a
+ * focusable and visible stack from the top of stacks in this display.
+ */
+ ActivityStack getFocusedStack() {
+ if (mPreferredTopFocusableStack != null) {
+ return mPreferredTopFocusableStack;
+ }
+
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getStackAt(i);
+ if (stack.isFocusableAndVisible()) {
+ return stack;
+ }
+ }
+
+ return null;
+ }
+
+ ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) {
+ final int currentWindowingMode = currentFocus != null
+ ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+ ActivityStack candidate = null;
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getStackAt(i);
+ if (ignoreCurrent && stack == currentFocus) {
+ continue;
+ }
+ if (!stack.isFocusableAndVisible()) {
+ continue;
+ }
+
+ if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) {
+ // If the currently focused stack is in split-screen secondary we save off the
+ // top primary split-screen stack as a candidate for focus because we might
+ // prefer focus to move to an other stack to avoid primary split-screen stack
+ // overlapping with a fullscreen stack when a fullscreen stack is higher in z
+ // than the next split-screen stack. Assistant stack, I am looking at you...
+ // We only move the focus to the primary-split screen stack if there isn't a
+ // better alternative.
+ candidate = stack;
+ continue;
+ }
+ if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) {
+ // Use the candidate stack since we are now at the secondary split-screen.
+ return candidate;
+ }
+ return stack;
+ }
+ return candidate;
+ }
+
+ ActivityRecord getResumedActivity() {
+ final ActivityStack focusedStack = getFocusedStack();
+ if (focusedStack == null) {
+ return null;
+ }
+ // TODO(b/111541062): Move this into ActivityStack#getResumedActivity()
+ // Check if the focused stack has the resumed activity
+ ActivityRecord resumedActivity = focusedStack.getResumedActivity();
+ if (resumedActivity == null || resumedActivity.app == null) {
+ // If there is no registered resumed activity in the stack or it is not running -
+ // try to use previously resumed one.
+ resumedActivity = focusedStack.mPausingActivity;
+ if (resumedActivity == null || resumedActivity.app == null) {
+ // If previously resumed activity doesn't work either - find the topmost running
+ // activity that can be focused.
+ resumedActivity = focusedStack.topRunningActivity(true /* focusableOnly */);
+ }
+ }
+ return resumedActivity;
+ }
+
+ ActivityStack getLastFocusedStack() {
+ return mLastFocusedStack;
+ }
+
+ boolean allResumedActivitiesComplete() {
+ for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityRecord r = getStackAt(stackNdx).getResumedActivity();
+ if (r != null && !r.isState(RESUMED)) {
+ return false;
+ }
+ }
+ final ActivityStack currentFocusedStack = getFocusedStack();
+ if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
+ Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from="
+ + mLastFocusedStack + " to=" + currentFocusedStack);
+ }
+ mLastFocusedStack = currentFocusedStack;
+ return true;
+ }
+
+ /**
+ * Pause all activities in either all of the stacks or just the back stacks. This is done before
+ * resuming a new activity and to make sure that previously active activities are
+ * paused in stacks that are no longer visible or in pinned windowing mode. This does not
+ * pause activities in visible stacks, so if an activity is launched within the same stack/task,
+ * then we should explicitly pause that stack's top activity.
+ * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
+ * @param resuming The resuming activity.
+ * @return {@code true} if any activity was paused as a result of this call.
+ */
+ boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
+ boolean someActivityPaused = false;
+ for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = getStackAt(stackNdx);
+ final ActivityRecord resumedActivity = stack.getResumedActivity();
+ if (resumedActivity != null
+ && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
+ || !stack.isFocusable())) {
+ if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
+ + " mResumedActivity=" + resumedActivity);
+ someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
+ resuming);
+ }
+ }
+ return someActivityPaused;
+ }
+
+ /**
+ * Find task for putting the Activity in.
+ */
+ void findTaskLocked(final ActivityRecord r, final boolean isPreferredDisplay,
+ RootActivityContainer.FindTaskResult result) {
+ mTmpFindTaskResult.clear();
+ for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = getStackAt(stackNdx);
+ if (!r.hasCompatibleActivityType(stack)) {
+ if (DEBUG_TASKS) {
+ Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack);
+ }
+ continue;
+ }
+
+ mTmpFindTaskResult.process(r, stack);
+ // It is possible to have tasks in multiple stacks with the same root affinity, so
+ // we should keep looking after finding an affinity match to see if there is a
+ // better match in another stack. Also, task affinity isn't a good enough reason
+ // to target a display which isn't the source of the intent, so skip any affinity
+ // matches not on the specified display.
+ if (mTmpFindTaskResult.mRecord != null) {
+ if (mTmpFindTaskResult.mIdealMatch) {
+ result.setTo(mTmpFindTaskResult);
+ return;
+ } else if (isPreferredDisplay) {
+ // Note: since the traversing through the stacks is top down, the floating
+ // tasks should always have lower priority than any affinity-matching tasks
+ // in the fullscreen stacks
+ result.setTo(mTmpFindTaskResult);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes stacks in the input windowing modes from the system if they are of activity type
+ * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+ */
+ void removeStacksInWindowingModes(int... windowingModes) {
+ if (windowingModes == null || windowingModes.length == 0) {
+ return;
+ }
+
+ // Collect the stacks that are necessary to be removed instead of performing the removal
+ // by looping mStacks, so that we don't miss any stacks after the stack size changed or
+ // stacks reordered.
+ final ArrayList<ActivityStack> stacks = new ArrayList<>();
+ for (int j = windowingModes.length - 1; j >= 0; --j) {
+ final int windowingMode = windowingModes[j];
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getStackAt(i);
+ if (!stack.isActivityTypeStandardOrUndefined()) {
+ continue;
+ }
+ if (stack.getWindowingMode() != windowingMode) {
+ continue;
+ }
+ stacks.add(stack);
+ }
+ }
+
+ for (int i = stacks.size() - 1; i >= 0; --i) {
+ mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i));
+ }
+ }
+
+ void removeStacksWithActivityTypes(int... activityTypes) {
+ if (activityTypes == null || activityTypes.length == 0) {
+ return;
+ }
+
+ // Collect the stacks that are necessary to be removed instead of performing the removal
+ // by looping mStacks, so that we don't miss any stacks after the stack size changed or
+ // stacks reordered.
+ final ArrayList<ActivityStack> stacks = new ArrayList<>();
+ for (int j = activityTypes.length - 1; j >= 0; --j) {
+ final int activityType = activityTypes[j];
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getStackAt(i);
+ if (stack.getActivityType() == activityType) {
+ stacks.add(stack);
+ }
+ }
+ }
+
+ for (int i = stacks.size() - 1; i >= 0; --i) {
+ mRootActivityContainer.mStackSupervisor.removeStack(stacks.get(i));
+ }
+ }
+
+ void onSplitScreenModeDismissed() {
+ mAtmService.deferWindowLayout();
+ try {
+ // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack otherStack = getStackAt(i);
+ if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
+ continue;
+ }
+ otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED, false /* animate */,
+ false /* showRecents */, false /* enteringSplitScreenMode */,
+ true /* deferEnsuringVisibility */, false /* creating */);
+ }
+ } finally {
+ final ActivityStack topFullscreenStack =
+ getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final ActivityStack homeStack = getHomeStack();
+ if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
+ // Whenever split-screen is dismissed we want the home stack directly behind the
+ // current top fullscreen stack so it shows up when the top stack is finished.
+ // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+ // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+ // once we have that.
+ homeStack.moveToFront("onSplitScreenModeDismissed");
+ topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
+ }
+ mAtmService.continueWindowLayout();
+ }
+ }
+
+ void onSplitScreenModeActivated() {
+ mAtmService.deferWindowLayout();
+ try {
+ // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
+ final ActivityStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack otherStack = getStackAt(i);
+ if (otherStack == splitScreenPrimaryStack
+ || !otherStack.affectedBySplitScreenResize()) {
+ continue;
+ }
+ otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ false /* animate */, false /* showRecents */,
+ true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */,
+ false /* creating */);
+ }
+ } finally {
+ mAtmService.continueWindowLayout();
+ }
+ }
+
+ /**
+ * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+ * @param windowingMode The windowing mode we are checking support for.
+ * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+ * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+ * @param supportsFreeform If we should consider support for freeform multi-window.
+ * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+ * @param activityType The activity type under consideration.
+ * @return true if the windowing mode is supported.
+ */
+ private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+ boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+ int activityType) {
+
+ if (windowingMode == WINDOWING_MODE_UNDEFINED
+ || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ return true;
+ }
+ if (!supportsMultiWindow) {
+ return false;
+ }
+
+ final int displayWindowingMode = getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+ return supportsSplitScreen
+ && WindowConfiguration.supportSplitScreenWindowingMode(activityType)
+ // Freeform windows and split-screen windows don't mix well, so prevent
+ // split windowing modes on freeform displays.
+ && displayWindowingMode != WINDOWING_MODE_FREEFORM;
+ }
+
+ if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+ return false;
+ }
+
+ if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this
+ * display with the provided parameters.
+ *
+ * @param r The ActivityRecord in question.
+ * @param options Options to start with.
+ * @param task The task within-which the activity would start.
+ * @param activityType The type of activity to start.
+ * @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
+ */
+ int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+ @Nullable Task task, int activityType) {
+
+ // First preference if the windowing mode in the activity options if set.
+ int windowingMode = (options != null)
+ ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+ // If windowing mode is unset, then next preference is the candidate task, then the
+ // activity record.
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ if (task != null) {
+ windowingMode = task.getWindowingMode();
+ }
+ if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+ windowingMode = r.getWindowingMode();
+ }
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ // Use the display's windowing mode.
+ windowingMode = getWindowingMode();
+ }
+ }
+ windowingMode = validateWindowingMode(windowingMode, r, task, activityType);
+ return windowingMode != WINDOWING_MODE_UNDEFINED
+ ? windowingMode : WINDOWING_MODE_FULLSCREEN;
+ }
+
+ /**
+ * Check that the requested windowing-mode is appropriate for the specified task and/or activity
+ * on this display.
+ *
+ * @param windowingMode The windowing-mode to validate.
+ * @param r The {@link ActivityRecord} to check against.
+ * @param task The {@link Task} to check against.
+ * @param activityType An activity type.
+ * @return The provided windowingMode or the closest valid mode which is appropriate.
+ */
+ int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
+ int activityType) {
+ // Make sure the windowing mode we are trying to use makes sense for what is supported.
+ boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
+ boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow;
+ boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement;
+ boolean supportsPip = mAtmService.mSupportsPictureInPicture;
+ if (supportsMultiWindow) {
+ if (task != null) {
+ supportsMultiWindow = task.isResizeable();
+ supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+ // TODO: Do we need to check for freeform and Pip support here?
+ } else if (r != null) {
+ supportsMultiWindow = r.isResizeable();
+ supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+ supportsFreeform = r.supportsFreeform();
+ supportsPip = r.supportsPictureInPicture();
+ }
+ }
+
+ final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+ if (!inSplitScreenMode
+ && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+ // Switch to the display's windowing mode if we are not in split-screen mode and we are
+ // trying to launch in split-screen secondary.
+ windowingMode = WINDOWING_MODE_UNDEFINED;
+ } else if (inSplitScreenMode && (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_UNDEFINED)
+ && supportsSplitScreen) {
+ windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ }
+
+ if (windowingMode != WINDOWING_MODE_UNDEFINED
+ && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+ supportsFreeform, supportsPip, activityType)) {
+ return windowingMode;
+ }
+ return WINDOWING_MODE_UNDEFINED;
+ }
+
+ boolean isTopStack(ActivityStack stack) {
+ return stack == getTopStack();
+ }
+
+ boolean isTopNotPinnedStack(ActivityStack stack) {
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack current = getStackAt(i);
+ if (!current.inPinnedWindowingMode()) {
+ return current == stack;
+ }
+ }
+ return false;
+ }
+
+ ActivityRecord topRunningActivity() {
+ return topRunningActivity(false /* considerKeyguardState */);
+ }
+
+ /**
+ * Returns the top running activity in the focused stack. In the case the focused stack has no
+ * such activity, the next focusable stack on this display is returned.
+ *
+ * @param considerKeyguardState Indicates whether the locked state should be considered. if
+ * {@code true} and the keyguard is locked, only activities that
+ * can be shown on top of the keyguard will be considered.
+ * @return The top running activity. {@code null} if none is available.
+ */
+ ActivityRecord topRunningActivity(boolean considerKeyguardState) {
+ ActivityRecord topRunning = null;
+ final ActivityStack focusedStack = getFocusedStack();
+ if (focusedStack != null) {
+ topRunning = focusedStack.topRunningActivity();
+ }
+
+ // Look in other focusable stacks.
+ if (topRunning == null) {
+ 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()) {
+ continue;
+ }
+ topRunning = stack.topRunningActivity();
+ if (topRunning != null) {
+ break;
+ }
+ }
+ }
+
+ // This activity can be considered the top running activity if we are not considering
+ // the locked state, the keyguard isn't locked, or we can show when locked.
+ if (topRunning != null && considerKeyguardState
+ && mRootActivityContainer.mStackSupervisor.getKeyguardController()
+ .isKeyguardLocked()
+ && !topRunning.canShowWhenLocked()) {
+ return null;
+ }
+
+ return topRunning;
+ }
+
+ boolean updateDisplayOverrideConfigurationLocked() {
+ Configuration values = new Configuration();
+ computeScreenConfiguration(values);
+
+ mAtmService.mH.sendMessage(PooledLambda.obtainMessage(
+ ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal,
+ mDisplayId));
+
+ Settings.System.clearConfiguration(values);
+ updateDisplayOverrideConfigurationLocked(values, null /* starting */,
+ false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
+ return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
+ }
+
+ /**
+ * Updates override configuration specific for the selected display. If no config is provided,
+ * new one will be computed in WM based on current display info.
+ */
+ boolean updateDisplayOverrideConfigurationLocked(Configuration values,
+ ActivityRecord starting, boolean deferResume,
+ ActivityTaskManagerService.UpdateConfigurationResult result) {
+
+ int changes = 0;
+ boolean kept = true;
+
+ mAtmService.deferWindowLayout();
+ try {
+ if (values != null) {
+ if (mDisplayId == DEFAULT_DISPLAY) {
+ // Override configuration of the default display duplicates global config, so
+ // we're calling global config update instead for default display. It will also
+ // apply the correct override config.
+ changes = mAtmService.updateGlobalConfigurationLocked(values,
+ false /* initLocale */, false /* persistent */,
+ UserHandle.USER_NULL /* userId */, deferResume);
+ } else {
+ changes = performDisplayOverrideConfigUpdate(values, deferResume);
+ }
+ }
+
+ kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
+ } finally {
+ mAtmService.continueWindowLayout();
+ }
+
+ if (result != null) {
+ result.changes = changes;
+ result.activityRelaunched = !kept;
+ }
+ return kept;
+ }
+
+ int performDisplayOverrideConfigUpdate(Configuration values, boolean deferResume) {
+ mTempConfig.setTo(getRequestedOverrideConfiguration());
+ final int changes = mTempConfig.updateFrom(values);
+ if (changes != 0) {
+ Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
+ + mTempConfig + " for displayId=" + mDisplayId);
+ onRequestedOverrideConfigurationChanged(mTempConfig);
+
+ final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
+ if (isDensityChange && mDisplayId == DEFAULT_DISPLAY) {
+ mAtmService.mAppWarnings.onDensityChanged();
+
+ // Post message to start process to avoid possible deadlock of calling into AMS with
+ // the ATMS lock held.
+ final Message msg = PooledLambda.obtainMessage(
+ ActivityManagerInternal::killAllBackgroundProcessesExcept,
+ mAtmService.mAmInternal, N,
+ ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ mAtmService.mH.sendMessage(msg);
+ }
+ mWmService.mDisplayNotificationController.dispatchDisplayChanged(
+ this, getConfiguration());
+ }
+ return changes;
+ }
+
+ @Override
+ public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
+ final int currRotation =
+ getRequestedOverrideConfiguration().windowConfiguration.getRotation();
+ if (currRotation != ROTATION_UNDEFINED
+ && currRotation != overrideConfiguration.windowConfiguration.getRotation()) {
+ applyRotationLocked(currRotation,
+ overrideConfiguration.windowConfiguration.getRotation());
+ }
+ mCurrentOverrideConfigurationChanges =
+ getRequestedOverrideConfiguration().diff(overrideConfiguration);
+ super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
+ mCurrentOverrideConfigurationChanges = 0;
+ mWmService.setNewDisplayOverrideConfiguration(overrideConfiguration, this);
+ mAtmService.addWindowLayoutReasons(
+ ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
+ }
+
+ /** Checks whether the given activity is in size compatibility mode and notifies the change. */
+ void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
+ if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ // The callback is only interested in the foreground changes of fullscreen activity.
+ return;
+ }
+ if (!r.inSizeCompatMode()) {
+ if (mLastCompatModeActivity != null) {
+ mAtmService.getTaskChangeNotificationController()
+ .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
+ }
+ mLastCompatModeActivity = null;
+ return;
+ }
+ if (mLastCompatModeActivity == r) {
+ return;
+ }
+ mLastCompatModeActivity = r;
+ mAtmService.getTaskChangeNotificationController()
+ .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
+ }
+
+ boolean isUidPresent(int uid) {
+ final PooledPredicate p = PooledLambda.obtainPredicate(
+ ActivityRecord::isUid, PooledLambda.__(ActivityRecord.class), uid);
+ final boolean isUidPresent = mDisplayContent.getActivity(p) != null;
+ p.recycle();
+ return isUidPresent;
+ }
+
+ /**
+ * @see #mRemoved
+ */
+ boolean isRemoved() {
+ return mRemoved;
+ }
+
+ /**
+ * @see #mRemoving
+ */
+ boolean isRemoving() {
+ return mRemoving;
+ }
+
+ void remove() {
+ mRemoving = true;
+ final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove();
+ ActivityStack lastReparentedStack = null;
+ mPreferredTopFocusableStack = null;
+
+ // Stacks could be reparented from the removed display to other display. While
+ // reparenting the last stack of the removed display, the remove display is ready to be
+ // released (no more ActivityStack). But, we cannot release it at that moment or the
+ // related WindowContainer will also be removed. So, we set display as removed after
+ // reparenting stack finished.
+ final DisplayContent toDisplay = mRootActivityContainer.getDefaultDisplay();
+ mRootActivityContainer.mStackSupervisor.beginDeferResume();
+ try {
+ int numStacks = getStackCount();
+ // Keep the order from bottom to top.
+ for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
+ final ActivityStack stack = getStackAt(stackNdx);
+ // Always finish non-standard type stacks.
+ if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) {
+ stack.finishAllActivitiesImmediately();
+ } else {
+ // If default display is in split-window mode, set windowing mode of the stack
+ // to split-screen secondary. Otherwise, set the windowing mode to undefined by
+ // default to let stack inherited the windowing mode from the new display.
+ final int windowingMode = toDisplay.hasSplitScreenPrimaryStack()
+ ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ : WINDOWING_MODE_UNDEFINED;
+ stack.reparent(toDisplay, true /* onTop */);
+ stack.setWindowingMode(windowingMode);
+ lastReparentedStack = stack;
+ }
+ // Stacks may be removed from this display. Ensure each stack will be processed and
+ // the loop will end.
+ stackNdx -= numStacks - getStackCount();
+ numStacks = getStackCount();
+ }
+ } finally {
+ mRootActivityContainer.mStackSupervisor.endDeferResume();
+ }
+ mRemoved = true;
+
+ // Only update focus/visibility for the last one because there may be many stacks are
+ // reparented and the intermediate states are unnecessary.
+ if (lastReparentedStack != null) {
+ lastReparentedStack.postReparent();
+ }
+ releaseSelfIfNeeded();
+
+ if (!mAllSleepTokens.isEmpty()) {
+ mRootActivityContainer.mSleepTokens.removeAll(mAllSleepTokens);
+ mAllSleepTokens.clear();
+ mAtmService.updateSleepIfNeededLocked();
+ }
+ }
+
+ private void releaseSelfIfNeeded() {
+ if (!mRemoved) {
+ return;
+ }
+
+ final ActivityStack stack = getStackCount() == 1 ? getStackAt(0) : null;
+ if (stack != null && stack.isActivityTypeHome() && !stack.hasChild()) {
+ // Release this display if an empty home stack is the only thing left.
+ // Since it is the last stack, this display will be released along with the stack
+ // removal.
+ stack.removeIfPossible();
+ } else if (getTopStack() == null) {
+ removeIfPossible();
+ mRootActivityContainer.removeChild(this);
+ mRootActivityContainer.mStackSupervisor
+ .getKeyguardController().onDisplayRemoved(mDisplayId);
+ }
+ }
+
+ /** Update and get all UIDs that are present on the display and have access to it. */
+ IntArray getPresentUIDs() {
+ mDisplayAccessUIDs.clear();
+ final PooledConsumer c = PooledLambda.obtainConsumer(DisplayContent::addActivityUid,
+ PooledLambda.__(ActivityRecord.class), mDisplayAccessUIDs);
+ mDisplayContent.forAllActivities(c);
+ c.recycle();
+ return mDisplayAccessUIDs;
+ }
+
+ private static void addActivityUid(ActivityRecord r, IntArray uids) {
+ uids.add(r.getUid());
+ }
+
+ @VisibleForTesting
+ boolean shouldDestroyContentOnRemove() {
+ return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
+ }
+
+ boolean shouldSleep() {
+ return (getStackCount() == 0 || !mAllSleepTokens.isEmpty())
+ && (mAtmService.mRunningVoice == null);
+ }
+
+ void setFocusedApp(ActivityRecord r, boolean moveFocusNow) {
+ final ActivityRecord newFocus;
+ final IBinder token = r.appToken;
+ if (token == null) {
+ ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Clearing focused app, displayId=%d",
+ mDisplayId);
+ newFocus = null;
+ } else {
+ newFocus = mWmService.mRoot.getActivityRecord(token);
+ if (newFocus == null) {
+ Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token
+ + ", displayId=" + mDisplayId);
+ }
+ ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
+ "Set focused app to: %s moveFocusNow=%b displayId=%d", newFocus,
+ moveFocusNow, mDisplayId);
+ }
+
+ final boolean changed = setFocusedApp(newFocus);
+ if (moveFocusNow && changed) {
+ mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /*updateInputWindows*/);
+ }
+ }
+
+ /**
+ * @return the stack currently above the {@param stack}. Can be null if the {@param stack} is
+ * already top-most.
+ */
+ ActivityStack getStackAbove(ActivityStack stack) {
+ final int stackIndex = getIndexOf(stack) + 1;
+ return (stackIndex < getStackCount()) ? getStackAt(stackIndex) : null;
+ }
+
+ /**
+ * Adjusts the {@param stack} behind the last visible stack in the display if necessary.
+ * Generally used in conjunction with {@link #moveStackBehindStack}.
+ */
+ void moveStackBehindBottomMostVisibleStack(ActivityStack stack) {
+ if (stack.shouldBeVisible(null)) {
+ // Skip if the stack is already visible
+ return;
+ }
+
+ // Move the stack to the bottom to not affect the following visibility checks
+ positionStackAtBottom(stack);
+
+ // Find the next position where the stack should be placed
+ final int numStacks = getStackCount();
+ for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
+ final ActivityStack s = getStackAt(stackNdx);
+ if (s == stack) {
+ continue;
+ }
+ final int winMode = s.getWindowingMode();
+ final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN
+ || winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ if (s.shouldBeVisible(null) && isValidWindowingMode) {
+ // Move the provided stack to behind this stack
+ positionStackAt(stack, Math.max(0, stackNdx - 1));
+ break;
+ }
+ }
+ }
+
+ /**
+ * Moves the {@param stack} behind the given {@param behindStack} if possible. If
+ * {@param behindStack} is not currently in the display, then then the stack is moved to the
+ * back. Generally used in conjunction with {@link #moveStackBehindBottomMostVisibleStack}.
+ */
+ void moveStackBehindStack(ActivityStack stack, ActivityStack behindStack) {
+ if (behindStack == null || behindStack == stack) {
+ return;
+ }
+
+ // Note that positionChildAt will first remove the given stack before inserting into the
+ // list, so we need to adjust the insertion index to account for the removed index
+ // TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
+ // position internally
+ final int stackIndex = getIndexOf(stack);
+ final int behindStackIndex = getIndexOf(behindStack);
+ final int insertIndex = stackIndex <= behindStackIndex
+ ? behindStackIndex - 1 : behindStackIndex;
+ positionStackAt(stack, Math.max(0, insertIndex));
+ }
+
+ void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+ boolean preserveWindows, boolean notifyClients) {
+ for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = getStackAt(stackNdx);
+ stack.ensureActivitiesVisible(starting, configChanges, preserveWindows,
+ notifyClients);
+ }
+ }
+
+ void moveHomeStackToFront(String reason) {
+ final ActivityStack homeStack = getHomeStack();
+ if (homeStack != null) {
+ homeStack.moveToFront(reason);
+ }
+ }
+
+ /**
+ * Moves the focusable home activity to top. If there is no such activity, the home stack will
+ * still move to top.
+ */
+ void moveHomeActivityToTop(String reason) {
+ final ActivityRecord top = getHomeActivity();
+ if (top == null) {
+ moveHomeStackToFront(reason);
+ return;
+ }
+ top.moveFocusableActivityToTop(reason);
+ }
+
+ @Nullable
+ ActivityRecord getHomeActivity() {
+ return getHomeActivityForUser(mRootActivityContainer.mCurrentUser);
+ }
+
+ @Nullable
+ ActivityRecord getHomeActivityForUser(int userId) {
+ final ActivityStack homeStack = getHomeStack();
+ if (homeStack == null) {
+ return null;
+ }
+
+ final PooledPredicate p = PooledLambda.obtainPredicate(
+ DisplayContent::isHomeActivityForUser, PooledLambda.__(ActivityRecord.class),
+ userId);
+ final ActivityRecord r = homeStack.getActivity(p);
+ p.recycle();
+ return r;
+ }
+
+ private static boolean isHomeActivityForUser(ActivityRecord r, int userId) {
+ return r.isActivityTypeHome() && (userId == UserHandle.USER_ALL || r.mUserId == userId);
+ }
+
+ boolean isSleeping() {
+ return mSleeping;
+ }
+
+ void setIsSleeping(boolean asleep) {
+ mSleeping = asleep;
+ }
+
+ /**
+ * Adds a listener to be notified whenever the stack order in the display changes. Currently
+ * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
+ * current animation when the system state changes.
+ */
+ void registerStackOrderChangedListener(OnStackOrderChangedListener listener) {
+ if (!mStackOrderChangedCallbacks.contains(listener)) {
+ mStackOrderChangedCallbacks.add(listener);
+ }
+ }
+
+ /**
+ * Removes a previously registered stack order change listener.
+ */
+ void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) {
+ mStackOrderChangedCallbacks.remove(listener);
+ }
+
+ /**
+ * Notifies of a stack order change
+ * @param stack The stack which triggered the order change
+ */
+ private void onStackOrderChanged(ActivityStack stack) {
+ for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
+ mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
+ }
+ }
+
+ void setDisplayToSingleTaskInstance() {
+ final int childCount = getStackCount();
+ if (childCount > 1) {
+ throw new IllegalArgumentException("Display already has multiple stacks. display="
+ + this);
+ }
+ if (childCount > 0) {
+ final ActivityStack stack = getStackAt(0);
+ if (stack.getChildCount() > 1) {
+ throw new IllegalArgumentException("Display stack already has multiple tasks."
+ + " display=" + this + " stack=" + stack);
+ }
+ }
+
+ mSingleTaskInstance = true;
+ }
+
+ /** Returns true if the display can only contain one task */
+ boolean isSingleTaskInstance() {
+ return mSingleTaskInstance;
+ }
+
+ @VisibleForTesting
+ void removeAllTasks() {
+ forAllTasks((t) -> { t.getStack().removeChild(t, "removeAllTasks"); });
+ }
+
+ /**
+ * Callback for when the order of the stacks in the display changes.
+ */
+ interface OnStackOrderChangedListener {
+ void onStackOrderChanged(ActivityStack stack);
+ }
+
+ public void dumpStacks(PrintWriter pw) {
+ for (int i = getStackCount() - 1; i >= 0; --i) {
+ pw.print(getStackAt(i).mStackId);
+ if (i > 0) {
+ pw.print(",");
+ }
+ }
+ }
+
+ /**
+ * Similar to {@link RootWindowContainer#isAnyNonToastWindowVisibleForUid(int)}, but
+ * used for pid.
+ */
+ boolean isAnyNonToastWindowVisibleForPid(int pid) {
+ final PooledPredicate p = PooledLambda.obtainPredicate(
+ WindowState::isNonToastWindowVisibleForPid,
+ PooledLambda.__(WindowState.class), pid);
+
+ final WindowState w = getWindow(p);
+ p.recycle();
+ return w != null;
+ }
+
+ Context getDisplayUiContext() {
+ return mDisplayPolicy.getSystemUiContext();
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 2283041..fbbc941 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -33,6 +33,7 @@
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
@@ -49,7 +50,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -63,6 +63,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -149,13 +150,17 @@
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InsetsFlags;
+import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.Surface;
import android.view.View;
import android.view.ViewRootImpl;
-import android.view.WindowInsets;
+import android.view.WindowInsets.Side;
+import android.view.WindowInsets.Side.InsetsSide;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
@@ -227,6 +232,7 @@
private final WindowManagerService mService;
private final Context mContext;
+ private final Context mUiContext;
private final DisplayContent mDisplayContent;
private final Object mLock;
private final Handler mHandler;
@@ -337,6 +343,7 @@
private int mLastAppearance;
private int mLastFullscreenAppearance;
private int mLastDockedAppearance;
+ private int mLastBehavior;
private final Rect mNonDockedStackBounds = new Rect();
private final Rect mDockedStackBounds = new Rect();
private final Rect mLastNonDockedStackBounds = new Rect();
@@ -353,6 +360,7 @@
private static final Rect sTmpRect = new Rect();
private static final Rect sTmpNavFrame = new Rect();
private static final Rect sTmpLastParentFrame = new Rect();
+ private static final int[] sTmpTypesAndSides = new int[2];
private WindowState mTopFullscreenOpaqueWindowState;
private WindowState mTopFullscreenOpaqueOrDimmingWindowState;
@@ -442,6 +450,9 @@
mService = service;
mContext = displayContent.isDefaultDisplay ? service.mContext
: service.mContext.createDisplayContext(displayContent.getDisplay());
+ mUiContext = displayContent.isDefaultDisplay ? service.mAtmService.mUiContext
+ : service.mAtmService.mSystemThread
+ .createSystemUiContext(displayContent.getDisplayId());
mDisplayContent = displayContent;
mLock = service.getWindowManagerLock();
@@ -1228,7 +1239,8 @@
final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
if (layoutInScreenAndInsetDecor && !screenDecor) {
- if ((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
+ if ((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+ || (attrs.getFitWindowInsetsTypes() & Type.navigationBars()) == 0) {
outFrame.set(displayFrames.mUnrestricted);
} else {
outFrame.set(displayFrames.mRestricted);
@@ -1281,11 +1293,27 @@
}
}
+ private static void getImpliedTypesAndSidesToFit(LayoutParams attrs, int[] typesAndSides) {
+ typesAndSides[0] = attrs.getFitWindowInsetsTypes();
+ typesAndSides[1] = attrs.getFitWindowInsetsSides();
+ final boolean forceDrawsBarBackgrounds =
+ (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
+ && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT;
+ if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || forceDrawsBarBackgrounds) {
+ if ((attrs.privateFlags & PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND) != 0) {
+ typesAndSides[1] &= ~Side.BOTTOM;
+ } else {
+ typesAndSides[0] &= ~Type.systemBars();
+ }
+ }
+ }
+
+ // TODO(b/118118435): remove after migration
private static int getImpliedSysUiFlagsForLayout(LayoutParams attrs) {
int impliedFlags = 0;
final boolean forceWindowDrawsBarBackgrounds =
(attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
- && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT;
+ && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT;
if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
|| forceWindowDrawsBarBackgrounds) {
impliedFlags |= SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -1300,6 +1328,13 @@
synchronized (mLock) {
mForceClearedSystemUiFlags &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
mDisplayContent.reevaluateStatusBarVisibility();
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL && mNavigationBar != null) {
+ final InsetsControlTarget target =
+ mNavigationBar.getControllableInsetProvider().getControlTarget();
+ if (target != null) {
+ target.showInsets(Type.navigationBars(), false /* fromIme */);
+ }
+ }
}
}
};
@@ -1376,11 +1411,18 @@
// For purposes of putting out fake window up to steal focus, we will
// drive nav being hidden only by whether it is requested.
final int sysui = mLastSystemUiFlags;
- boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+ final int behavior = mLastBehavior;
+ boolean navVisible = ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL
+ ? (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
+ : mNavigationBar != null && mNavigationBar.getControllableInsetProvider() != null
+ && mNavigationBar.getControllableInsetProvider().isClientVisible()
+ && !mDisplayContent.getInsetsPolicy().isTransient(ITYPE_NAVIGATION_BAR);
boolean navTranslucent = (sysui
& (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) != 0;
- boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
- boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
+ boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0
+ || (behavior & BEHAVIOR_SHOW_BARS_BY_SWIPE) != 0;
+ boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0
+ || (behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0;
boolean navAllowedHidden = immersive || immersiveSticky;
navTranslucent &= !immersiveSticky; // transient trumps translucent
boolean isKeyguardShowing = isStatusBarKeyguard()
@@ -1464,6 +1506,9 @@
displayFrames.mUnrestricted /* stableFrame */);
w.getWindowFrames().setDisplayCutout(displayFrames.mDisplayCutout);
w.computeFrameLw();
+ if (w.getControllableInsetProvider() != null) {
+ w.getControllableInsetProvider().updateSourceFrame();
+ }
final Rect frame = w.getFrameLw();
if (frame.left <= 0 && frame.top <= 0) {
@@ -1526,6 +1571,11 @@
// Let the status bar determine its size.
mStatusBar.computeFrameLw();
+ // Update the source frame to provide insets to other windows during layout.
+ if (mStatusBar.getControllableInsetProvider() != null) {
+ mStatusBar.getControllableInsetProvider().updateSourceFrame();
+ }
+
// For layout, the status bar is always at the top with our fixed height.
displayFrames.mStable.top = displayFrames.mUnrestricted.top
+ mStatusBarHeightForRotation[displayFrames.mRotation];
@@ -1540,7 +1590,8 @@
sTmpRect.bottom = displayFrames.mStable.top; // Use collapsed status bar size
mStatusBarController.setContentFrame(sTmpRect);
- boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
+ boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0
+ || mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR);
boolean statusBarTranslucent = (sysui
& (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
@@ -1676,6 +1727,9 @@
navigationFrame /* stableFrame */);
mNavigationBar.getWindowFrames().setDisplayCutout(displayFrames.mDisplayCutout);
mNavigationBar.computeFrameLw();
+ if (mNavigationBar.getControllableInsetProvider() != null) {
+ mNavigationBar.getControllableInsetProvider().updateSourceFrame();
+ }
mNavigationBarController.setContentFrame(mNavigationBar.getContentFrameLw());
if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + navigationFrame);
@@ -1811,19 +1865,44 @@
final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
- final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
- || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
-
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
sf.set(displayFrames.mStable);
- if (type == TYPE_INPUT_METHOD) {
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) {
+ getImpliedTypesAndSidesToFit(attrs, sTmpTypesAndSides);
+ final @InsetsType int typesToFit = sTmpTypesAndSides[0];
+ final @InsetsSide int sidesToFit = sTmpTypesAndSides[1];
+ final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit);
+ final Rect dfu = displayFrames.mUnrestricted;
+ Insets insets = Insets.of(0, 0, 0, 0);
+ for (int i = types.size() - 1; i >= 0; i--) {
+ insets = Insets.max(insets, mDisplayContent.getInsetsStateController()
+ .getInsetsForDispatch(win).getSource(types.valueAt(i))
+ .calculateInsets(dfu, attrs.getFitIgnoreVisibility()));
+ }
+ final int left = (sidesToFit & Side.LEFT) != 0 ? insets.left : 0;
+ final int top = (sidesToFit & Side.TOP) != 0 ? insets.top : 0;
+ final int right = (sidesToFit & Side.RIGHT) != 0 ? insets.right : 0;
+ final int bottom = (sidesToFit & Side.BOTTOM) != 0 ? insets.bottom : 0;
+ df.set(left, top, dfu.right - right, dfu.bottom - bottom);
+ if (attached == null) {
+ pf.set(df);
+ vf.set(adjust != SOFT_INPUT_ADJUST_NOTHING
+ ? displayFrames.mCurrent : displayFrames.mDock);
+ } else {
+ pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df);
+ vf.set(attached.getVisibleFrameLw());
+ }
+ cf.set(adjust != SOFT_INPUT_ADJUST_RESIZE
+ ? displayFrames.mDock : displayFrames.mContent);
+ dcf.set(displayFrames.mSystem);
+ } else if (type == TYPE_INPUT_METHOD) {
vf.set(displayFrames.mDock);
cf.set(displayFrames.mDock);
df.set(displayFrames.mDock);
- windowFrames.mParentFrame.set(displayFrames.mDock);
+ pf.set(displayFrames.mDock);
// IM dock windows layout below the nav bar...
pf.bottom = df.bottom = displayFrames.mUnrestricted.bottom;
// ...with content insets above the nav bar
@@ -1989,7 +2068,7 @@
}
}
} else if (layoutInScreen || (sysUiFl
- & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ "): IN_SCREEN");
@@ -2039,7 +2118,7 @@
cf.set(displayFrames.mUnrestricted);
df.set(displayFrames.mUnrestricted);
pf.set(displayFrames.mUnrestricted);
- } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
+ } else if ((sysUiFl & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
df.set(displayFrames.mRestricted);
pf.set(displayFrames.mRestricted);
@@ -2111,8 +2190,12 @@
final int cutoutMode = attrs.layoutInDisplayCutoutMode;
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();
final boolean requestedHideNavigation =
- (requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+ (requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
+ || !win.getClientInsetsState().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.
@@ -2195,6 +2278,9 @@
}
win.computeFrameLw();
+ if (win.getControllableInsetProvider() != null) {
+ win.getControllableInsetProvider().updateSourceFrame();
+ }
// Dock windows carve out the bottom of the screen, so normal windows
// can't appear underneath them.
if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
@@ -2683,10 +2769,8 @@
return mContext;
}
- private Context getSystemUiContext() {
- final Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
- return mDisplayContent.isDefaultDisplay
- ? uiContext : uiContext.createDisplayContext(mDisplayContent.getDisplay());
+ Context getSystemUiContext() {
+ return mUiContext;
}
private int getNavigationBarWidth(int rotation, int uiMode) {
@@ -2985,7 +3069,7 @@
mDisplayContent.getInsetsPolicy().showTransient(IntArray.wrap(
new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
} else {
- controlTarget.showInsets(WindowInsets.Type.systemBars(), false);
+ controlTarget.showInsets(Type.systemBars(), false);
}
} else {
boolean sb = mStatusBarController.checkShowTransientBarLw();
@@ -3109,6 +3193,7 @@
&& mLastAppearance == appearance
&& mLastFullscreenAppearance == fullscreenAppearance
&& mLastDockedAppearance == dockedAppearance
+ && mLastBehavior == behavior
&& mLastFocusIsFullscreen == isFullscreen
&& mLastFocusIsImmersive == isImmersive
&& mFocusedApp == win.getAppToken()
@@ -3125,6 +3210,7 @@
mLastAppearance = appearance;
mLastFullscreenAppearance = fullscreenAppearance;
mLastDockedAppearance = dockedAppearance;
+ mLastBehavior = behavior;
mLastFocusIsFullscreen = isFullscreen;
mLastFocusIsImmersive = isImmersive;
mFocusedApp = win.getAppToken();
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c7c3f8a..b59c4e3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -442,13 +442,17 @@
final int lastOrientation = mLastOrientation;
final int rotation = rotationForOrientation(lastOrientation, oldRotation);
ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Computed rotation=%d for display id=%d based on lastOrientation=%d and "
- + "oldRotation=%d",
- rotation, displayId, lastOrientation, oldRotation);
+ "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
+ + "oldRotation=%s (%d)",
+ Surface.rotationToString(rotation), rotation,
+ displayId,
+ ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
+ Surface.rotationToString(oldRotation), oldRotation);
ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Display id=%d selected orientation %d, got rotation %d", displayId,
- lastOrientation, rotation);
+ "Display id=%d selected orientation %s (%d), got rotation %s (%d)", displayId,
+ ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
+ Surface.rotationToString(rotation), rotation);
if (oldRotation == rotation) {
// No change.
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index 78fea74..bb31d45 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -41,7 +41,7 @@
try {
for (int i = 0; i < mService.mAtmService.mRootActivityContainer.getChildCount();
++i) {
- ActivityDisplay d = mService.mAtmService.mRootActivityContainer.getChildAt(i);
+ DisplayContent d = mService.mAtmService.mRootActivityContainer.getChildAt(i);
listener.onDisplayAdded(d.mDisplayId);
}
} catch (RemoteException e) { }
@@ -52,7 +52,7 @@
mDisplayListeners.unregister(listener);
}
- void dispatchDisplayAdded(ActivityDisplay display) {
+ void dispatchDisplayAdded(DisplayContent display) {
int count = mDisplayListeners.beginBroadcast();
for (int i = 0; i < count; ++i) {
try {
@@ -85,7 +85,7 @@
mDisplayListeners.finishBroadcast();
}
- void dispatchDisplayRemoved(ActivityDisplay display) {
+ void dispatchDisplayRemoved(DisplayContent display) {
int count = mDisplayListeners.beginBroadcast();
for (int i = 0; i < count; ++i) {
try {
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 1a1a7d4..6b5859d 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -259,7 +259,7 @@
if (homeStack == null) {
return false;
}
- final Task homeTask = homeStack.findHomeTask();
+ final Task homeTask = homeStack.getTopMostTask();
return homeTask != null && homeTask.isResizeable();
}
@@ -708,7 +708,7 @@
if (homeStack == null) {
return;
}
- final Task homeTask = homeStack.findHomeTask();
+ final Task homeTask = homeStack.getTopMostTask();
if (homeTask == null || !isWithinDisplay(homeTask)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 3aae1b1..949ff19 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -45,7 +45,7 @@
void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
boolean notifyClients) {
mStarting = starting;
- mTop = mContiner.topRunningActivityLocked();
+ mTop = mContiner.topRunningActivity();
// If the top activity is not fullscreen, then we need to make sure any activities under it
// are now visible.
mAboveTop = mTop != null;
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 82ac6b8..bef1442 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -46,6 +46,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -189,6 +190,7 @@
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
+ lp.setFitWindowInsetsTypes(lp.getFitWindowInsetsTypes() & ~Type.statusBars());
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("ImmersiveModeConfirmation");
lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index d50bcc4..af84836 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -45,7 +45,7 @@
private final IntArray mShowingTransientTypes = new IntArray();
private WindowState mFocusedWin;
- private BarWindow mTopBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
+ private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
private BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
@@ -57,15 +57,15 @@
/** Updates the target which can control system bars. */
void updateBarControlTarget(@Nullable WindowState focusedWin) {
mFocusedWin = focusedWin;
- mStateController.onBarControlTargetChanged(getTopControlTarget(focusedWin),
- getFakeTopControlTarget(focusedWin),
+ mStateController.onBarControlTargetChanged(getStatusControlTarget(focusedWin),
+ getFakeStatusControlTarget(focusedWin),
getNavControlTarget(focusedWin),
getFakeNavControlTarget(focusedWin));
if (ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
return;
}
- mTopBar.setVisible(focusedWin == null
- || focusedWin != getTopControlTarget(focusedWin)
+ mStatusBar.setVisible(focusedWin == null
+ || focusedWin != getStatusControlTarget(focusedWin)
|| focusedWin.getClientInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
mNavBar.setVisible(focusedWin == null
|| focusedWin != getNavControlTarget(focusedWin)
@@ -110,6 +110,10 @@
mStateController.notifyInsetsChanged();
}
+ boolean isTransient(@InternalInsetsType int type) {
+ return mShowingTransientTypes.indexOf(type) != -1;
+ }
+
/**
* @see InsetsStateController#getInsetsForDispatch
*/
@@ -130,8 +134,8 @@
if (ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL) {
return;
}
- if (windowState == getTopControlTarget(mFocusedWin)) {
- mTopBar.setVisible(state.getSource(ITYPE_STATUS_BAR).isVisible());
+ if (windowState == getStatusControlTarget(mFocusedWin)) {
+ mStatusBar.setVisible(state.getSource(ITYPE_STATUS_BAR).isVisible());
}
if (windowState == getNavControlTarget(mFocusedWin)) {
mNavBar.setVisible(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
@@ -164,7 +168,8 @@
}
}
- private @Nullable InsetsControlTarget getFakeTopControlTarget(@Nullable WindowState focused) {
+ private @Nullable InsetsControlTarget getFakeStatusControlTarget(
+ @Nullable WindowState focused) {
if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1) {
return focused;
}
@@ -178,7 +183,7 @@
return null;
}
- private @Nullable InsetsControlTarget getTopControlTarget(@Nullable WindowState focusedWin) {
+ private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin) {
if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1) {
return mTransientControlTarget;
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 69e8fdc..184e7d6 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -24,6 +24,8 @@
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.ViewRootImpl.sNewInsetsMode;
+import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Point;
@@ -36,7 +38,6 @@
import android.view.SurfaceControl.Transaction;
import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import java.io.PrintWriter;
@@ -141,9 +142,10 @@
}
/**
- * Called when a layout pass has occurred.
+ * The source frame can affect the layout of other windows, so this should be called once the
+ * window gets laid out.
*/
- void onPostLayout() {
+ void updateSourceFrame() {
if (mWin == null) {
return;
}
@@ -155,6 +157,17 @@
mTmpRect.inset(mWin.mGivenContentInsets);
}
mSource.setFrame(mTmpRect);
+ }
+
+ /**
+ * Called when a layout pass has occurred.
+ */
+ void onPostLayout() {
+ if (mWin == null) {
+ return;
+ }
+
+ updateSourceFrame();
if (mControl != null) {
final Rect frame = mWin.getWindowFrames().mFrame;
if (mControl.setSurfacePosition(frame.left, frame.top)) {
@@ -210,8 +223,8 @@
return;
}
mClientVisible = clientVisible;
- mDisplayContent.mWmService.mH.sendMessage(PooledLambda.obtainMessage(
- DisplayContent::layoutAndAssignWindowLayersIfNeeded, mDisplayContent));
+ mDisplayContent.mWmService.mH.obtainMessage(
+ LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget();
updateVisibility();
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index ac5c96b..6f81957 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -296,7 +296,7 @@
boolean requestDismissKeyguard = false;
for (int displayNdx = mRootActivityContainer.getChildCount() - 1;
displayNdx >= 0; displayNdx--) {
- final ActivityDisplay display = mRootActivityContainer.getChildAt(displayNdx);
+ final DisplayContent display = mRootActivityContainer.getChildAt(displayNdx);
final KeyguardDisplayState state = getDisplay(display.mDisplayId);
state.visibilitiesUpdated(this, display);
requestDismissKeyguard |= state.mRequestDismissKeyguard;
@@ -420,7 +420,7 @@
private void updateKeyguardSleepToken() {
for (int displayNdx = mRootActivityContainer.getChildCount() - 1;
displayNdx >= 0; displayNdx--) {
- final ActivityDisplay display = mRootActivityContainer.getChildAt(displayNdx);
+ final DisplayContent display = mRootActivityContainer.getChildAt(displayNdx);
updateKeyguardSleepToken(display.mDisplayId);
}
}
@@ -483,7 +483,7 @@
}
}
- void visibilitiesUpdated(KeyguardController controller, ActivityDisplay display) {
+ void visibilitiesUpdated(KeyguardController controller, DisplayContent display) {
final boolean lastOccluded = mOccluded;
final ActivityRecord lastDismissActivity = mDismissingKeyguardActivity;
mRequestDismissKeyguard = false;
@@ -494,7 +494,7 @@
if (stack != null) {
final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null
- && stack.topRunningActivityLocked() == topDismissing
+ && stack.topRunningActivity() == topDismissing
&& controller.canShowWhileOccluded(
true /* dismissKeyguard */,
false /* showWhenLocked */));
@@ -530,7 +530,7 @@
* Only the top non-pinned activity of the focusable stack on each display can control its
* occlusion state.
*/
- private ActivityStack getStackForControllingOccluding(ActivityDisplay display) {
+ private ActivityStack getStackForControllingOccluding(DisplayContent display) {
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
if (stack != null && stack.isFocusableAndVisible()
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index c3bcc74..8cf3dc8 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -223,8 +223,8 @@
private boolean saveTaskToLaunchParam(Task task, PersistableLaunchParams params) {
final ActivityStack stack = task.getStack();
final int displayId = stack.mDisplayId;
- final ActivityDisplay display =
- mSupervisor.mRootActivityContainer.getActivityDisplay(displayId);
+ final DisplayContent display =
+ mSupervisor.mRootActivityContainer.getDisplayContent(displayId);
final DisplayInfo info = new DisplayInfo();
display.mDisplay.getDisplayInfo(info);
@@ -260,7 +260,7 @@
return;
}
- final ActivityDisplay display = mSupervisor.mRootActivityContainer.getActivityDisplay(
+ final DisplayContent display = mSupervisor.mRootActivityContainer.getDisplayContent(
persistableParams.mDisplayUniqueId);
if (display != null) {
outParams.mPreferredDisplayId = display.mDisplayId;
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 2ece6e2..7a72b43 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -150,7 +150,7 @@
* The first task in the list, which started the current LockTask session, is called the root
* task. It coincides with the Home task in a typical multi-app kiosk deployment. When there are
* more than one locked tasks, the root task can't be finished. Nor can it be moved to the back
- * of the stack by {@link ActivityStack#moveTaskToBackLocked(int)};
+ * of the stack by {@link ActivityStack#moveTaskToBack(Task)};
*
* Calling {@link Activity#stopLockTask()} on the root task will finish all tasks but itself in
* this list, and the device will exit LockTask mode.
@@ -251,7 +251,7 @@
/**
* @return whether the given task can be moved to the back of the stack with
- * {@link ActivityStack#moveTaskToBackLocked(int)}
+ * {@link ActivityStack#moveTaskToBack(Task)}
* @see #mLockTaskModeTasks
*/
boolean canMoveTaskToBack(Task task) {
@@ -653,10 +653,7 @@
taskChanged = true;
}
- for (int displayNdx = mSupervisor.mRootActivityContainer.getChildCount() - 1;
- displayNdx >= 0; --displayNdx) {
- mSupervisor.mRootActivityContainer.getChildAt(displayNdx).onLockTaskPackagesUpdated();
- }
+ mSupervisor.mRootActivityContainer.mRootWindowContainer.forAllTasks(Task::setLockTaskAuth);
final ActivityRecord r = mSupervisor.mRootActivityContainer.topRunningActivity();
final Task task = (r != null) ? r.getTask() : null;
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index a8e7aea..b4f75e5 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -16,38 +16,29 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static com.android.server.wm.PinnedStackControllerProto.DEFAULT_BOUNDS;
import static com.android.server.wm.PinnedStackControllerProto.MOVEMENT_BOUNDS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import android.annotation.NonNull;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.Size;
import android.util.Slog;
-import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
-import android.view.Gravity;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
-import com.android.internal.policy.PipSnapAlgorithm;
-import com.android.internal.util.Preconditions;
import com.android.server.UiThread;
import java.io.PrintWriter;
@@ -74,7 +65,6 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
- private static final float INVALID_SNAP_FRACTION = -1f;
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final Handler mHandler = UiThread.getHandler();
@@ -84,7 +74,6 @@
new PinnedStackListenerDeathHandler();
private final PinnedStackControllerCallback mCallbacks = new PinnedStackControllerCallback();
- private final PipSnapAlgorithm mSnapAlgorithm;
// States that affect how the PIP can be manipulated
private boolean mIsMinimized;
@@ -97,13 +86,9 @@
// Used to calculate stack bounds across rotations
private final DisplayInfo mDisplayInfo = new DisplayInfo();
- private final Rect mStableInsets = new Rect();
// The size and position information that describes where the pinned stack will go by default.
- private int mDefaultMinSize;
- private int mDefaultStackGravity;
private float mDefaultAspectRatio;
- private Point mScreenEdgeInsets;
// The aspect ratio bounds of the PIP.
private float mMinAspectRatio;
@@ -111,10 +96,11 @@
// Temp vars for calculation
private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
- private final Rect mTmpInsets = new Rect();
- private final Rect mTmpRect = new Rect();
- private final Point mTmpDisplaySize = new Point();
+ // TODO(b/141200935): remove this when we have default/movement bounds tests in SysUI.
+ // Keep record of the default and movement bounds
+ private final Rect mLastReportedDefaultBounds = new Rect();
+ private final Rect mLastReportedMovementBounds = new Rect();
/**
* The callback object passed to listeners for them to notify the controller of state changes.
@@ -125,7 +111,6 @@
public void setIsMinimized(final boolean isMinimized) {
mHandler.post(() -> {
mIsMinimized = isMinimized;
- mSnapAlgorithm.setMinimized(isMinimized);
});
}
@@ -145,6 +130,27 @@
sourceRectHint, animationDuration, true /* fromFullscreen */);
}
}
+
+ @Override
+ public void resetBoundsAnimation(Rect bounds) {
+ synchronized (mService.mGlobalLock) {
+ if (mDisplayContent.hasPinnedStack()) {
+ final ActivityStack pinnedStack = mDisplayContent.getTopStackInWindowingMode(
+ WINDOWING_MODE_PINNED);
+ if (pinnedStack != null) {
+ pinnedStack.resetCurrentBoundsAnimation(bounds);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void reportBounds(Rect defaultBounds, Rect movementBounds) {
+ synchronized (mService.mGlobalLock) {
+ mLastReportedDefaultBounds.set(defaultBounds);
+ mLastReportedMovementBounds.set(movementBounds);
+ }
+ }
}
/**
@@ -165,7 +171,6 @@
PinnedStackController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
mDisplayContent = displayContent;
- mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
reloadResources();
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
@@ -183,21 +188,9 @@
*/
private void reloadResources() {
final Resources res = mService.mContext.getResources();
- mDefaultMinSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
mDefaultAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
- final String screenEdgeInsetsDpString = res.getString(
- com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
- final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
- ? Size.parseSize(screenEdgeInsetsDpString)
- : null;
- mDefaultStackGravity = res.getInteger(
- com.android.internal.R.integer.config_defaultPictureInPictureGravity);
mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics);
- mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
- : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics),
- dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics));
mMinAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
mMaxAspectRatio = res.getFloat(
@@ -215,7 +208,7 @@
notifyDisplayInfoChanged(mDisplayInfo);
notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
// The movement bounds notification needs to be sent before the minimized state, since
- // SystemUI may use the bounds to retore the minimized position
+ // SystemUI may use the bounds to restore the minimized position
notifyMovementBoundsChanged(false /* fromImeAdjustment */,
false /* fromShelfAdjustment */);
notifyActionsChanged(mActions);
@@ -257,30 +250,6 @@
}
}
- /**
- * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
- * will apply the default bounds to the provided snap fraction.
- */
- private Rect getDefaultBounds(float snapFraction) {
- synchronized (mService.mGlobalLock) {
- final Rect insetBounds = new Rect();
- getInsetBounds(insetBounds);
-
- final Rect defaultBounds = new Rect();
- final Size size = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
- mDefaultMinSize, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
- if (snapFraction != INVALID_SNAP_FRACTION) {
- defaultBounds.set(0, 0, size.getWidth(), size.getHeight());
- final Rect movementBounds = getMovementBounds(defaultBounds);
- mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
- } else {
- Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
- 0, mIsImeShowing ? mImeHeight : 0, defaultBounds);
- }
- return defaultBounds;
- }
- }
-
private void setDisplayInfo(DisplayInfo displayInfo) {
mDisplayInfo.copyFrom(displayInfo);
notifyDisplayInfoChanged(mDisplayInfo);
@@ -300,51 +269,6 @@
}
/**
- * Updates the display info, calculating and returning the new stack and movement bounds in the
- * new orientation of the device if necessary.
- */
- boolean onTaskStackBoundsChanged(Rect targetBounds, Rect outBounds) {
- synchronized (mService.mGlobalLock) {
- final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
- if (isSameDimensionAndRotation(mDisplayInfo, displayInfo)) {
- // No dimension/rotation change, ignore
- outBounds.setEmpty();
- return false;
- } else if (targetBounds.isEmpty()) {
- // The stack is null, we are just initializing the stack, so just store the display
- // info and ignore
- setDisplayInfo(displayInfo);
- outBounds.setEmpty();
- return false;
- }
-
- mTmpRect.set(targetBounds);
- final Rect postChangeStackBounds = mTmpRect;
-
- // Calculate the snap fraction of the current stack along the old movement bounds
- final float snapFraction = getSnapFraction(postChangeStackBounds);
-
- setDisplayInfo(displayInfo);
-
- // Calculate the stack bounds in the new orientation to the same same fraction along the
- // rotated movement bounds.
- final Rect postChangeMovementBounds = getMovementBounds(postChangeStackBounds,
- false /* adjustForIme */);
- mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
- snapFraction);
- if (mIsMinimized) {
- applyMinimizedOffset(postChangeStackBounds, postChangeMovementBounds);
- }
-
- notifyMovementBoundsChanged(false /* fromImeAdjustment */,
- false /* fromShelfAdjustment */);
-
- outBounds.set(postChangeStackBounds);
- return true;
- }
- }
-
- /**
* Sets the Ime state and height.
*/
void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
@@ -400,15 +324,6 @@
notifyPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
}
- private boolean isSameDimensionAndRotation(@NonNull DisplayInfo display1,
- @NonNull DisplayInfo display2) {
- Preconditions.checkNotNull(display1);
- Preconditions.checkNotNull(display2);
- return ((display1.rotation == display2.rotation)
- && (display1.logicalWidth == display2.logicalWidth)
- && (display1.logicalHeight == display2.logicalHeight));
- }
-
/**
* Notifies listeners that the PIP needs to be adjusted for the IME.
*/
@@ -504,86 +419,11 @@
}
}
- /**
- * @return the bounds on the screen that the PIP can be visible in.
- */
- private void getInsetBounds(Rect outRect) {
- synchronized (mService.mGlobalLock) {
- mDisplayContent.getDisplayPolicy().getStableInsetsLw(mDisplayInfo.rotation,
- mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
- mDisplayInfo.displayCutout, mTmpInsets);
- outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
- mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
- mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
- }
- }
-
- /**
- * @return the movement bounds for the given {@param stackBounds} and the current state of the
- * controller.
- */
- private Rect getMovementBounds(Rect stackBounds) {
- synchronized (mService.mGlobalLock) {
- return getMovementBounds(stackBounds, true /* adjustForIme */);
- }
- }
-
- /**
- * @return the movement bounds for the given {@param stackBounds} and the current state of the
- * controller.
- */
- private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
- synchronized (mService.mGlobalLock) {
- final Rect movementBounds = new Rect();
- getInsetBounds(movementBounds);
-
- // Apply the movement bounds adjustments based on the current state.
- // Note that shelf offset does not affect the movement bounds here
- // since it's been taken care of in system UI.
- mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
- (adjustForIme && mIsImeShowing) ? mImeHeight : 0);
- return movementBounds;
- }
- }
-
- /**
- * Applies the minimized offsets to the given stack bounds.
- */
- private void applyMinimizedOffset(Rect stackBounds, Rect movementBounds) {
- synchronized (mService.mGlobalLock) {
- mTmpDisplaySize.set(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
- mService.getStableInsetsLocked(mDisplayContent.getDisplayId(), mStableInsets);
- mSnapAlgorithm.applyMinimizedOffset(stackBounds, movementBounds, mTmpDisplaySize,
- mStableInsets);
- }
- }
-
- /**
- * @return the default snap fraction to apply instead of the default gravity when calculating
- * the default stack bounds when first entering PiP.
- */
- private float getSnapFraction(Rect stackBounds) {
- return mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds));
- }
-
- /**
- * @return the pixels for a given dp value.
- */
- private int dpToPx(float dpValue, DisplayMetrics dm) {
- return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
- }
-
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "PinnedStackController");
- pw.print(prefix + " defaultBounds=");
- getDefaultBounds(INVALID_SNAP_FRACTION).printShortString(pw);
- pw.println();
- pw.println(prefix + " mDefaultMinSize=" + mDefaultMinSize);
- pw.println(prefix + " mDefaultStackGravity=" + mDefaultStackGravity);
+ pw.println(prefix + " mLastReportedDefaultBounds=" + mLastReportedDefaultBounds);
+ pw.println(prefix + " mLastReportedMovementBounds=" + mLastReportedMovementBounds);
pw.println(prefix + " mDefaultAspectRatio=" + mDefaultAspectRatio);
- mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
- pw.print(prefix + " movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
- pw.println();
pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
pw.println(prefix + " mImeHeight=" + mImeHeight);
pw.println(prefix + " mIsMinimized=" + mIsMinimized);
@@ -606,9 +446,8 @@
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- getDefaultBounds(INVALID_SNAP_FRACTION).dumpDebug(proto, DEFAULT_BOUNDS);
- mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
- getMovementBounds(mTmpRect).dumpDebug(proto, MOVEMENT_BOUNDS);
+ mLastReportedDefaultBounds.dumpDebug(proto, DEFAULT_BOUNDS);
+ mLastReportedMovementBounds.dumpDebug(proto, MOVEMENT_BOUNDS);
proto.end(token);
}
}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f0e441f..71bbb70 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -208,10 +208,10 @@
synchronized (mService.mGlobalLock) {
// Unfreeze the task list once we touch down in a task
final RootActivityContainer rac = mService.mRootActivityContainer;
- final DisplayContent dc = rac.getActivityDisplay(displayId).mDisplayContent;
+ final DisplayContent dc = rac.getDisplayContent(displayId).mDisplayContent;
if (dc.pointWithinAppWindow(x, y)) {
final ActivityStack stack = mService.getTopDisplayFocusedStack();
- final Task topTask = stack != null ? stack.topTask() : null;
+ final Task topTask = stack != null ? stack.getTopMostTask() : null;
resetFreezeTaskListReordering(topTask);
}
}
@@ -319,7 +319,7 @@
void resetFreezeTaskListReorderingOnTimeout() {
synchronized (mService.mGlobalLock) {
final ActivityStack focusedStack = mService.getTopDisplayFocusedStack();
- final Task topTask = focusedStack != null ? focusedStack.topTask() : null;
+ final Task topTask = focusedStack != null ? focusedStack.getTopMostTask() : null;
resetFreezeTaskListReordering(topTask);
}
}
@@ -630,8 +630,7 @@
&& task.realActivitySuspended != suspended) {
task.realActivitySuspended = suspended;
if (suspended) {
- mSupervisor.removeTaskByIdLocked(task.mTaskId, false,
- REMOVE_FROM_RECENTS, "suspended-package");
+ mSupervisor.removeTask(task, false, REMOVE_FROM_RECENTS, "suspended-package");
}
notifyTaskPersisterLocked(task, false);
}
@@ -658,8 +657,7 @@
if (task.mUserId != userId) continue;
if (!taskPackageName.equals(packageName)) continue;
- mSupervisor.removeTaskByIdLocked(task.mTaskId, true, REMOVE_FROM_RECENTS,
- "remove-package-task");
+ mSupervisor.removeTask(task, true, REMOVE_FROM_RECENTS, "remove-package-task");
}
}
@@ -687,8 +685,7 @@
final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
&& (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
if (sameComponent) {
- mSupervisor.removeTaskByIdLocked(task.mTaskId, false,
- REMOVE_FROM_RECENTS, "disabled-package");
+ mSupervisor.removeTask(task, false, REMOVE_FROM_RECENTS, "disabled-package");
}
}
}
@@ -1147,6 +1144,7 @@
// Trim the set of tasks to the active set
trimInactiveRecentTasks();
+ notifyTaskPersisterLocked(task, false /* flush */);
}
/**
@@ -1306,9 +1304,9 @@
case WINDOWING_MODE_PINNED:
return false;
case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
- if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\ttop=" + task.getStack().topTask());
+ if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\ttop=" + task.getStack().getTopMostTask());
final ActivityStack stack = task.getStack();
- if (stack != null && stack.topTask() == task) {
+ if (stack != null && stack.getTopMostTask() == task) {
// Only the non-top task of the primary split screen mode is visible
return false;
}
@@ -1319,7 +1317,7 @@
// TODO(b/126185105): Find a different signal to use besides isSingleTaskInstance
final ActivityStack stack = task.getStack();
if (stack != null) {
- ActivityDisplay display = stack.getDisplay();
+ DisplayContent display = stack.getDisplay();
if (display != null && display.isSingleTaskInstance()) {
return false;
}
@@ -1389,7 +1387,7 @@
}
// Trim tasks that are in stacks that are behind the home stack
- final ActivityDisplay display = stack.getDisplay();
+ final DisplayContent display = stack.getDisplay();
return display.getIndexOf(stack) < display.getIndexOf(display.getHomeStack());
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index d5bbe6b..0a8e747 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -53,14 +53,14 @@
* cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
*/
class RecentsAnimation implements RecentsAnimationCallbacks,
- ActivityDisplay.OnStackOrderChangedListener {
+ DisplayContent.OnStackOrderChangedListener {
private static final String TAG = RecentsAnimation.class.getSimpleName();
private final ActivityTaskManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
private final ActivityStartController mActivityStartController;
private final WindowManagerService mWindowManager;
- private final ActivityDisplay mDefaultDisplay;
+ private final DisplayContent mDefaultDisplay;
private final Intent mTargetIntent;
private final ComponentName mRecentsComponent;
private final int mRecentsUid;
@@ -180,7 +180,7 @@
ActivityRecord targetActivity = getTargetActivity(targetStack);
final boolean hasExistingActivity = targetActivity != null;
if (hasExistingActivity) {
- final ActivityDisplay display = targetActivity.getDisplay();
+ final DisplayContent display = targetActivity.getDisplay();
mRestoreTargetBehindStack = display.getStackAbove(targetStack);
if (mRestoreTargetBehindStack == null) {
notifyAnimationCancelBeforeStart(recentsAnimationRunner);
@@ -216,7 +216,7 @@
// and default launchers coexisting), then move the task to the top as a part of
// moving the stack to the front
final Task task = targetActivity.getTask();
- if (targetStack.topTask() != task) {
+ if (targetStack.getTopMostTask() != task) {
targetStack.positionChildAtTop(task);
}
} else {
@@ -250,7 +250,7 @@
mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION,
"startRecentsActivity");
mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
- this, mDefaultDisplay.mDisplayId,
+ this, mDefaultDisplay.getDisplayId(),
mStackSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
// If we updated the launch-behind state, update the visibility of the activities after
@@ -351,7 +351,7 @@
}
} else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
// Restore the target stack to its previous position
- final ActivityDisplay display = targetActivity.getDisplay();
+ final DisplayContent display = targetActivity.getDisplay();
display.moveStackBehindStack(targetStack, mRestoreTargetBehindStack);
if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
final ActivityStack aboveTargetStack =
@@ -432,7 +432,7 @@
// cases:
// 1) The next launching task is not being animated by the recents animation
// 2) The next task is home activity. (i.e. pressing home key to back home in recents).
- if ((!controller.isAnimatingTask(stack.getTopChild())
+ if ((!controller.isAnimatingTask(stack.getTopMostTask())
|| controller.isTargetApp(stack.getTopNonFinishingActivity()))
&& controller.shouldDeferCancelUntilNextTransition()) {
// Always prepare an app transition since we rely on the transition callbacks to cleanup
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 39091a6..b255b5e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -754,8 +754,7 @@
// Only apply the input consumer if it is enabled, it is not the target (home/recents)
// being revealed with the transition, and we are actively animating the app as a part of
// the animation
- return mInputConsumerEnabled && mTargetActivityRecord != activity
- && isAnimatingApp(activity);
+ return mInputConsumerEnabled && !isTargetApp(activity) && isAnimatingApp(activity);
}
boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
@@ -810,7 +809,9 @@
PooledLambda.__(ActivityRecord.class));
boolean isAnimatingApp = task.forAllActivities(f);
f.recycle();
- return isAnimatingApp;
+ if (isAnimatingApp) {
+ return true;
+ }
}
return false;
}
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 3aa91d5..d787cbc 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -258,7 +258,7 @@
private void processCreatedTasks() {
if (mCreatedTasks.isEmpty()) return;
- ActivityDisplay display = mParent.getDisplay();
+ DisplayContent display = mParent.getDisplay();
final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
if (singleTaskInstanceDisplay) {
display = mParent.mRootActivityContainer.getDefaultDisplay();
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 5653ec0..f778e4d 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -109,7 +109,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
-import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -129,6 +128,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Function;
/**
* Root node for activity containers.
@@ -168,17 +168,17 @@
WindowManagerService mWindowManager;
DisplayManager mDisplayManager;
private DisplayManagerInternal mDisplayManagerInternal;
- // TODO: Remove after object merge with RootWindowContainer.
- private RootWindowContainer mRootWindowContainer;
+ // TODO(root-unify): Remove after object merge with RootWindowContainer.
+ RootWindowContainer mRootWindowContainer;
/**
* List of displays which contain activities, sorted by z-order.
* The last entry in the list is the topmost.
*/
- private final ArrayList<ActivityDisplay> mActivityDisplays = new ArrayList<>();
+ private final ArrayList<DisplayContent> mDisplayContents = new ArrayList<>();
/** Reference to default display so we can quickly look it up. */
- private ActivityDisplay mDefaultDisplay;
+ private DisplayContent mDefaultDisplay;
private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
/** The current user */
@@ -214,7 +214,7 @@
private RemoteException mTmpRemoteException;
private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();
- static class FindTaskResult implements ToBooleanFunction<Task> {
+ static class FindTaskResult implements Function<Task, Boolean> {
ActivityRecord mRecord;
boolean mIdealMatch;
@@ -259,7 +259,7 @@
}
@Override
- public boolean apply(Task task) {
+ public Boolean apply(Task task) {
if (task.voiceSession != null) {
// We never match voice sessions; those always run independently.
if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": voice session");
@@ -354,35 +354,35 @@
final Display[] displays = mDisplayManager.getDisplays();
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
final Display display = displays[displayNdx];
- final ActivityDisplay activityDisplay = new ActivityDisplay(this, display);
- if (activityDisplay.mDisplayId == DEFAULT_DISPLAY) {
- mDefaultDisplay = activityDisplay;
+ final DisplayContent displayContent = new DisplayContent(display, this);
+ if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
+ mDefaultDisplay = displayContent;
}
- addChild(activityDisplay, ActivityDisplay.POSITION_TOP);
+ addChild(displayContent, DisplayContent.POSITION_TOP);
}
calculateDefaultMinimalSizeOfResizeableTasks();
- final ActivityDisplay defaultDisplay = getDefaultDisplay();
+ final DisplayContent defaultDisplay = getDefaultDisplay();
defaultDisplay.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
- positionChildAt(defaultDisplay, ActivityDisplay.POSITION_TOP);
+ positionChildAt(defaultDisplay, DisplayContent.POSITION_TOP);
}
// TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display.
- ActivityDisplay getDefaultDisplay() {
+ DisplayContent getDefaultDisplay() {
return mDefaultDisplay;
}
/**
- * Get an existing instance of {@link ActivityDisplay} that has the given uniqueId. Unique ID is
+ * Get an existing instance of {@link DisplayContent} that has the given uniqueId. Unique ID is
* defined in {@link DisplayInfo#uniqueId}.
*
* @param uniqueId the unique ID of the display
- * @return the {@link ActivityDisplay} or {@code null} if nothing is found.
+ * @return the {@link DisplayContent} or {@code null} if nothing is found.
*/
- ActivityDisplay getActivityDisplay(String uniqueId) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityDisplay display = mActivityDisplays.get(i);
+ DisplayContent getDisplayContent(String uniqueId) {
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final DisplayContent display = getChildAt(i);
final boolean isValid = display.mDisplay.isValid();
if (isValid && display.mDisplay.getUniqueId().equals(uniqueId)) {
return display;
@@ -392,26 +392,26 @@
return null;
}
- // TODO: Look into consolidating with getActivityDisplayOrCreate()
- ActivityDisplay getActivityDisplay(int displayId) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityDisplay activityDisplay = mActivityDisplays.get(i);
- if (activityDisplay.mDisplayId == displayId) {
- return activityDisplay;
+ // TODO: Look into consolidating with getDisplayContentOrCreate()
+ DisplayContent getDisplayContent(int displayId) {
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final DisplayContent displayContent = getChildAt(i);
+ if (displayContent.mDisplayId == displayId) {
+ return displayContent;
}
}
return null;
}
/**
- * Get an existing instance of {@link ActivityDisplay} or create new if there is a
+ * Get an existing instance of {@link DisplayContent} or create new if there is a
* corresponding record in display manager.
*/
- // TODO: Look into consolidating with getActivityDisplay()
- @Nullable ActivityDisplay getActivityDisplayOrCreate(int displayId) {
- ActivityDisplay activityDisplay = getActivityDisplay(displayId);
- if (activityDisplay != null) {
- return activityDisplay;
+ // TODO: Look into consolidating with getDisplayContent()
+ @Nullable DisplayContent getDisplayContentOrCreate(int displayId) {
+ DisplayContent displayContent = getDisplayContent(displayId);
+ if (displayContent != null) {
+ return displayContent;
}
if (mDisplayManager == null) {
// The system isn't fully initialized yet.
@@ -423,9 +423,9 @@
return null;
}
// The display hasn't been added to ActivityManager yet, create a new record now.
- activityDisplay = new ActivityDisplay(this, display);
- addChild(activityDisplay, ActivityDisplay.POSITION_BOTTOM);
- return activityDisplay;
+ displayContent = new DisplayContent(display, this);
+ addChild(displayContent, DisplayContent.POSITION_BOTTOM);
+ return displayContent;
}
ActivityRecord getDefaultDisplayHomeActivity() {
@@ -433,21 +433,21 @@
}
ActivityRecord getDefaultDisplayHomeActivityForUser(int userId) {
- return getActivityDisplay(DEFAULT_DISPLAY).getHomeActivityForUser(userId);
+ return getDisplayContent(DEFAULT_DISPLAY).getHomeActivityForUser(userId);
}
boolean startHomeOnAllDisplays(int userId, String reason) {
boolean homeStarted = false;
- for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
- final int displayId = mActivityDisplays.get(i).mDisplayId;
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final int displayId = getChildAt(i).mDisplayId;
homeStarted |= startHomeOnDisplay(userId, reason, displayId);
}
return homeStarted;
}
void startHomeOnEmptyDisplays(String reason) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
- final ActivityDisplay display = mActivityDisplays.get(i);
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final DisplayContent display = getChildAt(i);
if (display.topRunningActivity() == null) {
startHomeOnDisplay(mCurrentUser, reason, display.mDisplayId);
}
@@ -629,7 +629,7 @@
displayId = DEFAULT_DISPLAY;
}
- final ActivityRecord r = getActivityDisplay(displayId).getHomeActivity();
+ final ActivityRecord r = getDisplayContent(displayId).getHomeActivity();
final String myReason = reason + " resumeHomeActivity";
// Only resume home activity if isn't finishing.
@@ -671,7 +671,7 @@
return false;
}
- final ActivityDisplay display = getActivityDisplay(displayId);
+ final DisplayContent display = getDisplayContent(displayId);
if (display == null || display.isRemoved() || !display.supportsSystemDecorations()) {
// Can't launch home on display that doesn't support system decorations.
return false;
@@ -771,10 +771,8 @@
if (displayContent != null) {
// Update the configuration of the activities on the display.
- // TODO(display-merge): Remove cast
- return ((ActivityDisplay) displayContent)
- .updateDisplayOverrideConfigurationLocked(config, starting, deferResume,
- null /* result */);
+ return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
+ deferResume, null /* result */);
} else {
return true;
}
@@ -788,8 +786,8 @@
final ArrayList<IBinder> topActivityTokens = new ArrayList<>();
final ActivityStack topFocusedStack = getTopDisplayFocusedStack();
// Traverse all displays.
- for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
- final ActivityDisplay display = mActivityDisplays.get(i);
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final DisplayContent display = getChildAt(i);
// Traverse all stacks on a display.
for (int j = display.getStackCount() - 1; j >= 0; --j) {
final ActivityStack stack = display.getStackAt(j);
@@ -810,8 +808,8 @@
}
ActivityStack getTopDisplayFocusedStack() {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityStack focusedStack = mActivityDisplays.get(i).getFocusedStack();
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack focusedStack = getChildAt(i).getFocusedStack();
if (focusedStack != null) {
return focusedStack;
}
@@ -830,8 +828,8 @@
}
// The top focused stack might not have a resumed activity yet - look on all displays in
// focus order.
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityDisplay display = mActivityDisplays.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final DisplayContent display = getChildAt(i);
final ActivityRecord resumedActivityOnDisplay = display.getResumedActivity();
if (resumedActivityOnDisplay != null) {
return resumedActivityOnDisplay;
@@ -860,8 +858,8 @@
// previous app if this activity is being hosted by the process that is actually still the
// foreground.
WindowProcessController fgApp = null;
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
if (isTopDisplayFocusedStack(stack)) {
@@ -888,8 +886,8 @@
boolean attachApplication(WindowProcessController app) throws RemoteException {
final String processName = app.mName;
boolean didSomething = false;
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
final ActivityStack stack = display.getFocusedStack();
if (stack == null) {
continue;
@@ -899,7 +897,7 @@
mTmpBoolean = false; // Set to true if an activity was started.
final PooledFunction c = PooledLambda.obtainFunction(
RootActivityContainer::startActivityForAttachedApplicationIfNeeded, this,
- PooledLambda.__(ActivityRecord.class), app, stack.topRunningActivityLocked());
+ PooledLambda.__(ActivityRecord.class), app, stack.topRunningActivity());
stack.forAllActivities(c);
c.recycle();
if (mTmpRemoteException != null) {
@@ -957,8 +955,8 @@
try {
mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
// First the front stacks. In case any are not fullscreen and are in front of home.
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
notifyClients);
}
@@ -987,12 +985,12 @@
mCurrentUser = userId;
mStackSupervisor.mStartingUsers.add(uss);
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
stack.switchUser(userId);
- Task task = stack.topTask();
+ Task task = stack.getTopMostTask();
if (task != null) {
stack.positionChildAtTop(task);
}
@@ -1035,8 +1033,8 @@
* @param onTop Indicates whether container should be place on top or on bottom.
*/
void moveStackToDisplay(int stackId, int displayId, boolean onTop) {
- final ActivityDisplay activityDisplay = getActivityDisplayOrCreate(displayId);
- if (activityDisplay == null) {
+ final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
+ if (displayContent == null) {
throw new IllegalArgumentException("moveStackToDisplay: Unknown displayId="
+ displayId);
}
@@ -1046,7 +1044,7 @@
+ stackId);
}
- final ActivityDisplay currentDisplay = stack.getDisplay();
+ final DisplayContent currentDisplay = stack.getDisplay();
if (currentDisplay == null) {
throw new IllegalStateException("moveStackToDisplay: Stack with stack=" + stack
+ " is not attached to any display.");
@@ -1057,14 +1055,14 @@
+ " to its current displayId=" + displayId);
}
- if (activityDisplay.isSingleTaskInstance() && activityDisplay.getStackCount() > 0) {
+ if (displayContent.isSingleTaskInstance() && displayContent.getStackCount() > 0) {
// We don't allow moving stacks to single instance display that already has a child.
Slog.e(TAG, "Can not move stack=" + stack
- + " to single task instance display=" + activityDisplay);
+ + " to single task instance display=" + displayContent);
return;
}
- stack.reparent(activityDisplay.mDisplayContent, onTop);
+ stack.reparent(displayContent.mDisplayContent, onTop);
// TODO(multi-display): resize stacks properly if moved from split-screen.
}
@@ -1075,7 +1073,7 @@
"moveTopStackActivityToPinnedStack: Unknown stackId=" + stackId);
}
- final ActivityRecord r = stack.topRunningActivityLocked();
+ final ActivityRecord r = stack.topRunningActivity();
if (r == null) {
Slog.w(TAG, "moveTopStackActivityToPinnedStack: No top running activity"
+ " in stack=" + stack);
@@ -1097,7 +1095,7 @@
String reason) {
mService.deferWindowLayout();
- final ActivityDisplay display = r.getActivityStack().getDisplay();
+ final DisplayContent display = r.getActivityStack().getDisplay();
try {
final Task task = r.getTask();
@@ -1167,8 +1165,8 @@
}
void executeAppTransitionForAllDisplay() {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
display.mDisplayContent.executeAppTransition();
}
}
@@ -1193,7 +1191,7 @@
mTmpFindTaskResult.clear();
// Looking up task on preferred display first
- final ActivityDisplay preferredDisplay = getActivityDisplay(preferredDisplayId);
+ final DisplayContent preferredDisplay = getDisplayContent(preferredDisplayId);
if (preferredDisplay != null) {
preferredDisplay.findTaskLocked(r, true /* isPreferredDisplay */, mTmpFindTaskResult);
if (mTmpFindTaskResult.mIdealMatch) {
@@ -1201,8 +1199,8 @@
}
}
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
if (display.mDisplayId == preferredDisplayId) {
continue;
}
@@ -1226,8 +1224,8 @@
int finishTopCrashedActivities(WindowProcessController app, String reason) {
Task finishedTask = null;
ActivityStack focusedStack = getTopDisplayFocusedStack();
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
// It is possible that request to finish activity might also remove its task and stack,
// so we need to be careful with indexes in the loop and check child count every time.
for (int stackNdx = 0; stackNdx < display.getStackCount(); ++stackNdx) {
@@ -1258,12 +1256,12 @@
result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
}
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
boolean resumedOnDisplay = false;
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
- final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();
+ final ActivityRecord topRunningActivity = stack.topRunningActivity();
if (!stack.isFocusableAndVisible() || topRunningActivity == null) {
continue;
}
@@ -1290,7 +1288,7 @@
final ActivityStack focusedStack = display.getFocusedStack();
if (focusedStack != null) {
result |= focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
- } else if (targetStack == null && !display.hasChild()) {
+ } else if (targetStack == null && display.getStackCount() == 0) {
result |= resumeHomeActivity(null /* prev */, "empty-display",
display.mDisplayId);
}
@@ -1301,9 +1299,9 @@
}
void applySleepTokens(boolean applyToStacks) {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
// Set the sleeping state of the display.
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ final DisplayContent display = getChildAt(displayNdx);
final boolean displayShouldSleep = display.shouldSleep();
if (displayShouldSleep == display.isSleeping()) {
continue;
@@ -1357,8 +1355,8 @@
}
protected ActivityStack getStack(int stackId) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityStack stack = mActivityDisplays.get(i).getStack(stackId);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getChildAt(i).getStack(stackId);
if (stack != null) {
return stack;
}
@@ -1366,11 +1364,10 @@
return null;
}
- /** @see ActivityDisplay#getStack(int, int) */
+ /** @see DisplayContent#getStack(int, int) */
ActivityStack getStack(int windowingMode, int activityType) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityStack stack =
- mActivityDisplays.get(i).getStack(windowingMode, activityType);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getChildAt(i).getStack(windowingMode, activityType);
if (stack != null) {
return stack;
}
@@ -1380,7 +1377,7 @@
private ActivityStack getStack(int windowingMode, int activityType,
int displayId) {
- ActivityDisplay display = getActivityDisplay(displayId);
+ DisplayContent display = getDisplayContent(displayId);
if (display == null) {
return null;
}
@@ -1389,7 +1386,7 @@
private ActivityManager.StackInfo getStackInfo(ActivityStack stack) {
final int displayId = stack.mDisplayId;
- final ActivityDisplay display = getActivityDisplay(displayId);
+ final DisplayContent display = getDisplayContent(displayId);
ActivityManager.StackInfo info = new ActivityManager.StackInfo();
stack.getBounds(info.bounds);
info.displayId = displayId;
@@ -1401,32 +1398,37 @@
info.position = display != null ? display.getIndexOf(stack) : 0;
info.configuration.setTo(stack.getConfiguration());
- ArrayList<Task> tasks = stack.getAllTasks();
- final int numTasks = tasks.size();
- int[] taskIds = new int[numTasks];
- String[] taskNames = new String[numTasks];
- Rect[] taskBounds = new Rect[numTasks];
- int[] taskUserIds = new int[numTasks];
- for (int i = 0; i < numTasks; ++i) {
- final Task task = tasks.get(i);
- taskIds[i] = task.mTaskId;
- taskNames[i] = task.origActivity != null ? task.origActivity.flattenToString()
- : task.realActivity != null ? task.realActivity.flattenToString()
- : task.getTopNonFinishingActivity() != null
- ? task.getTopNonFinishingActivity().packageName : "unknown";
- taskBounds[i] = mService.getTaskBounds(task.mTaskId);
- taskUserIds[i] = task.mUserId;
- }
- info.taskIds = taskIds;
- info.taskNames = taskNames;
- info.taskBounds = taskBounds;
- info.taskUserIds = taskUserIds;
+ final int numTasks = stack.getChildCount();
+ info.taskIds = new int[numTasks];
+ info.taskNames = new String[numTasks];
+ info.taskBounds = new Rect[numTasks];
+ info.taskUserIds = new int[numTasks];
+ final int[] currenIndex = {0};
- final ActivityRecord top = stack.topRunningActivityLocked();
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ RootActivityContainer::processTaskForStackInfo, PooledLambda.__(Task.class), info,
+ currenIndex);
+ stack.forAllTasks(c, false);
+ c.recycle();
+
+ final ActivityRecord top = stack.topRunningActivity();
info.topActivity = top != null ? top.intent.getComponent() : null;
return info;
}
+ private static void processTaskForStackInfo(
+ Task task, ActivityManager.StackInfo info, int[] currentIndex) {
+ int i = currentIndex[0];
+ info.taskIds[i] = task.mTaskId;
+ info.taskNames[i] = task.origActivity != null ? task.origActivity.flattenToString()
+ : task.realActivity != null ? task.realActivity.flattenToString()
+ : task.getTopNonFinishingActivity() != null
+ ? task.getTopNonFinishingActivity().packageName : "unknown";
+ info.taskBounds[i] = task.mAtmService.getTaskBounds(task.mTaskId);
+ info.taskUserIds[i] = task.mUserId;
+ currentIndex[0] = ++i;
+ }
+
ActivityManager.StackInfo getStackInfo(int stackId) {
ActivityStack stack = getStack(stackId);
if (stack != null) {
@@ -1449,8 +1451,8 @@
ArrayList<ActivityManager.StackInfo> getAllStackInfos(int displayId) {
ArrayList<ActivityManager.StackInfo> list = new ArrayList<>();
if (displayId == INVALID_DISPLAY) {
- for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = 0; displayNdx < getChildCount(); ++displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
list.add(getStackInfo(stack));
@@ -1458,7 +1460,7 @@
}
return list;
}
- final ActivityDisplay display = getActivityDisplay(displayId);
+ final DisplayContent display = getDisplayContent(displayId);
if (display == null) {
return list;
}
@@ -1487,7 +1489,7 @@
public void onDisplayAdded(int displayId) {
if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId);
synchronized (mService.mGlobalLock) {
- final ActivityDisplay display = getActivityDisplayOrCreate(displayId);
+ final DisplayContent display = getDisplayContentOrCreate(displayId);
if (display == null) {
return;
}
@@ -1513,12 +1515,12 @@
}
synchronized (mService.mGlobalLock) {
- final ActivityDisplay activityDisplay = getActivityDisplay(displayId);
- if (activityDisplay == null) {
+ final DisplayContent displayContent = getDisplayContent(displayId);
+ if (displayContent == null) {
return;
}
- activityDisplay.remove();
+ displayContent.remove();
}
}
@@ -1526,9 +1528,9 @@
public void onDisplayChanged(int displayId) {
if (DEBUG_STACK) Slog.v(TAG, "Display changed displayId=" + displayId);
synchronized (mService.mGlobalLock) {
- final ActivityDisplay activityDisplay = getActivityDisplay(displayId);
- if (activityDisplay != null) {
- activityDisplay.onDisplayChanged();
+ final DisplayContent displayContent = getDisplayContent(displayId);
+ if (displayContent != null) {
+ displayContent.onDisplayChanged();
}
}
}
@@ -1536,12 +1538,12 @@
/** Update lists of UIDs that are present on displays and have access to them. */
void updateUIDsPresentOnDisplay() {
mDisplayAccessUIDs.clear();
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent displayContent = getChildAt(displayNdx);
// Only bother calculating the whitelist for private displays
- if (activityDisplay.isPrivate()) {
+ if (displayContent.isPrivate()) {
mDisplayAccessUIDs.append(
- activityDisplay.mDisplayId, activityDisplay.getPresentUIDs());
+ displayContent.mDisplayId, displayContent.getPresentUIDs());
}
}
// Store updated lists in DisplayManager. Callers from outside of AM should get them there.
@@ -1549,7 +1551,7 @@
}
ActivityStack findStackBehind(ActivityStack stack) {
- final ActivityDisplay display = getActivityDisplay(stack.mDisplayId);
+ final DisplayContent display = getDisplayContent(stack.mDisplayId);
if (display != null) {
for (int i = display.getStackCount() - 1; i >= 0; i--) {
if (display.getStackAt(i) == stack && i > 0) {
@@ -1563,12 +1565,12 @@
@Override
protected int getChildCount() {
- return mActivityDisplays.size();
+ return mDisplayContents.size();
}
@Override
- protected ActivityDisplay getChildAt(int index) {
- return mActivityDisplays.get(index);
+ protected DisplayContent getChildAt(int index) {
+ return mDisplayContents.get(index);
}
@Override
@@ -1586,61 +1588,59 @@
/** Change the z-order of the given display. */
private void positionChildAt(DisplayContent display, int position) {
- if (position >= mActivityDisplays.size()) {
- position = mActivityDisplays.size() - 1;
+ if (position >= mDisplayContents.size()) {
+ position = mDisplayContents.size() - 1;
} else if (position < 0) {
position = 0;
}
- // TODO(display-merge): Remove cast
- final ActivityDisplay activityDisplay = (ActivityDisplay) display;
- if (mActivityDisplays.isEmpty()) {
- mActivityDisplays.add(activityDisplay);
- } else if (mActivityDisplays.get(position) != display) {
- mActivityDisplays.remove(display);
- mActivityDisplays.add(position, activityDisplay);
+ if (mDisplayContents.isEmpty()) {
+ mDisplayContents.add(display);
+ } else if (mDisplayContents.get(position) != display) {
+ mDisplayContents.remove(display);
+ mDisplayContents.add(position, display);
}
mStackSupervisor.updateTopResumedActivityIfNeeded();
}
@VisibleForTesting
- void addChild(ActivityDisplay activityDisplay, int position) {
- positionChildAt(activityDisplay, position);
- mRootWindowContainer.positionChildAt(position, activityDisplay.mDisplayContent);
+ void addChild(DisplayContent displayContent, int position) {
+ positionChildAt(displayContent, position);
+ mRootWindowContainer.positionChildAt(position, displayContent);
}
- void removeChild(ActivityDisplay activityDisplay) {
- // The caller must tell the controller of {@link ActivityDisplay} to release its container
- // {@link DisplayContent}. That is done in {@link ActivityDisplay#releaseSelfIfNeeded}).
- mActivityDisplays.remove(activityDisplay);
+ void removeChild(DisplayContent displayContent) {
+ // The caller must tell the controller of {@link DisplayContent} to release its container
+ // {@link DisplayContent}. That is done in {@link DisplayContent#releaseSelfIfNeeded}).
+ mDisplayContents.remove(displayContent);
}
Configuration getDisplayOverrideConfiguration(int displayId) {
- final ActivityDisplay activityDisplay = getActivityDisplayOrCreate(displayId);
- if (activityDisplay == null) {
+ final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
+ if (displayContent == null) {
throw new IllegalArgumentException("No display found with id: " + displayId);
}
- return activityDisplay.getRequestedOverrideConfiguration();
+ return displayContent.getRequestedOverrideConfiguration();
}
void setDisplayOverrideConfiguration(Configuration overrideConfiguration, int displayId) {
- final ActivityDisplay activityDisplay = getActivityDisplayOrCreate(displayId);
- if (activityDisplay == null) {
+ final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
+ if (displayContent == null) {
throw new IllegalArgumentException("No display found with id: " + displayId);
}
- activityDisplay.onRequestedOverrideConfigurationChanged(overrideConfiguration);
+ displayContent.onRequestedOverrideConfigurationChanged(overrideConfiguration);
}
void prepareForShutdown() {
- for (int i = 0; i < mActivityDisplays.size(); i++) {
- createSleepToken("shutdown", mActivityDisplays.get(i).mDisplayId);
+ for (int i = 0; i < getChildCount(); i++) {
+ createSleepToken("shutdown", getChildAt(i).mDisplayId);
}
}
ActivityTaskManagerInternal.SleepToken createSleepToken(String tag, int displayId) {
- final ActivityDisplay display = getActivityDisplay(displayId);
+ final DisplayContent display = getDisplayContent(displayId);
if (display == null) {
throw new IllegalArgumentException("Invalid display: " + displayId);
}
@@ -1654,7 +1654,7 @@
private void removeSleepToken(SleepTokenImpl token) {
mSleepTokens.remove(token);
- final ActivityDisplay display = getActivityDisplay(token.mDisplayId);
+ final DisplayContent display = getDisplayContent(token.mDisplayId);
if (display != null) {
display.mAllSleepTokens.remove(token);
if (display.mAllSleepTokens.isEmpty()) {
@@ -1711,8 +1711,8 @@
}
void scheduleDestroyAllActivities(WindowProcessController app, String reason) {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
stack.scheduleDestroyActivities(app, reason);
@@ -1724,8 +1724,8 @@
// successfully put to sleep.
boolean putStacksToSleep(boolean allowDelay, boolean shuttingDown) {
boolean allSleep = true;
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
// Stacks and activities could be removed while putting activities to sleep if
// the app process was gone. This prevents us getting exception by accessing an
@@ -1795,8 +1795,8 @@
}
boolean hasAwakeDisplay() {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
if (!display.shouldSleep()) {
return true;
}
@@ -1872,7 +1872,7 @@
return stack;
}
}
- final ActivityDisplay display = getActivityDisplayOrCreate(displayId);
+ final DisplayContent display = getDisplayContentOrCreate(displayId);
if (display != null) {
stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
if (stack != null) {
@@ -1884,7 +1884,7 @@
// Give preference to the stack and display of the input task and activity if they match the
// mode we want to launch into.
stack = null;
- ActivityDisplay display = null;
+ DisplayContent display = null;
if (candidateTask != null) {
stack = candidateTask.getStack();
}
@@ -1905,7 +1905,7 @@
}
if (windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY
&& display.getSplitScreenPrimaryStack() == stack
- && candidateTask == stack.topTask()) {
+ && candidateTask == stack.getTopMostTask()) {
// This is a special case when we try to launch an activity that is currently on
// top of split-screen primary stack, but is targeting split-screen secondary.
// In this case we don't want to move it to another stack.
@@ -1942,8 +1942,8 @@
private ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
@Nullable Task candidateTask, @Nullable ActivityOptions options,
@Nullable LaunchParamsController.LaunchParams launchParams) {
- final ActivityDisplay activityDisplay = getActivityDisplayOrCreate(displayId);
- if (activityDisplay == null) {
+ final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
+ if (displayContent == null) {
throw new IllegalArgumentException(
"Display with displayId=" + displayId + " not found.");
}
@@ -1972,12 +1972,12 @@
windowingMode = options != null ? options.getLaunchWindowingMode()
: r.getWindowingMode();
}
- windowingMode = activityDisplay.validateWindowingMode(windowingMode, r, candidateTask,
+ windowingMode = displayContent.validateWindowingMode(windowingMode, r, candidateTask,
r.getActivityType());
// Return the topmost valid stack on the display.
- for (int i = activityDisplay.getStackCount() - 1; i >= 0; --i) {
- final ActivityStack stack = activityDisplay.getStackAt(i);
+ for (int i = displayContent.getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = displayContent.getStackAt(i);
if (isValidLaunchStack(stack, r, windowingMode)) {
return stack;
}
@@ -1988,7 +1988,7 @@
final int activityType =
options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED
? options.getLaunchActivityType() : r.getActivityType();
- return activityDisplay.createStack(windowingMode, activityType, true /*onTop*/);
+ return displayContent.createStack(windowingMode, activityType, true /*onTop*/);
}
return null;
@@ -2050,11 +2050,11 @@
ActivityStack getNextFocusableStack(@NonNull ActivityStack currentFocus,
boolean ignoreCurrent) {
// First look for next focusable stack on the same display
- ActivityDisplay preferredDisplay = currentFocus.getDisplay();
+ DisplayContent preferredDisplay = currentFocus.getDisplay();
if (preferredDisplay == null) {
// Stack is currently detached because it is being removed. Use the previous display it
// was on.
- preferredDisplay = getActivityDisplay(currentFocus.mPrevDisplayId);
+ preferredDisplay = getDisplayContent(currentFocus.mPrevDisplayId);
}
final ActivityStack preferredFocusableStack = preferredDisplay.getNextFocusableStack(
currentFocus, ignoreCurrent);
@@ -2069,8 +2069,8 @@
}
// Now look through all displays
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityDisplay display = mActivityDisplays.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final DisplayContent display = getChildAt(i);
if (display == preferredDisplay) {
// We've already checked this one
continue;
@@ -2096,8 +2096,8 @@
* @return Next valid {@link ActivityStack}, null if not found.
*/
ActivityStack getNextValidLaunchStack(@NonNull ActivityRecord r, int currentFocus) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityDisplay display = mActivityDisplays.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final DisplayContent display = getChildAt(i);
if (display.mDisplayId == currentFocus) {
continue;
}
@@ -2112,8 +2112,8 @@
boolean handleAppDied(WindowProcessController app) {
boolean hasVisibleActivities = false;
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
hasVisibleActivities |= stack.handleAppDiedLocked(app);
@@ -2225,8 +2225,8 @@
}
void finishVoiceTask(IVoiceInteractionSession session) {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
final int numStacks = display.getStackCount();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
@@ -2240,20 +2240,20 @@
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
*/
void removeStacksInWindowingModes(int... windowingModes) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- mActivityDisplays.get(i).removeStacksInWindowingModes(windowingModes);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ getChildAt(i).removeStacksInWindowingModes(windowingModes);
}
}
void removeStacksWithActivityTypes(int... activityTypes) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- mActivityDisplays.get(i).removeStacksWithActivityTypes(activityTypes);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ getChildAt(i).removeStacksWithActivityTypes(activityTypes);
}
}
ActivityRecord topRunningActivity() {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityRecord topActivity = mActivityDisplays.get(i).topRunningActivity();
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord topActivity = getChildAt(i).topRunningActivity();
if (topActivity != null) {
return topActivity;
}
@@ -2262,9 +2262,9 @@
}
boolean allResumedActivitiesIdle() {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
// TODO(b/117135575): Check resumed activities on all visible stacks.
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ final DisplayContent display = getChildAt(displayNdx);
if (display.isSleeping()) {
// No resumed activities while display is sleeping.
continue;
@@ -2292,8 +2292,8 @@
boolean allResumedActivitiesVisible() {
boolean foundResumed = false;
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
final ActivityRecord r = stack.getResumedActivity();
@@ -2310,8 +2310,8 @@
boolean allPausedActivitiesComplete() {
boolean pausing = true;
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
final ActivityRecord r = stack.mPausingActivity;
@@ -2338,24 +2338,11 @@
void lockAllProfileTasks(@UserIdInt int userId) {
mService.deferWindowLayout();
try {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
- for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getStackAt(stackNdx);
- final List<Task> tasks = stack.getAllTasks();
- for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) {
- final Task task = tasks.get(taskNdx);
-
- // Check the task for a top activity belonging to userId, or returning a
- // result to an activity belonging to userId. Example case: a document
- // picker for personal files, opened by a work app, should still get locked.
- if (taskTopActivityIsUser(task, userId)) {
- mService.getTaskChangeNotificationController().notifyTaskProfileLocked(
- task.mTaskId, userId);
- }
- }
- }
- }
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ RootActivityContainer::taskTopActivityIsUser, this, PooledLambda.__(Task.class),
+ userId);
+ mRootWindowContainer.forAllTasks(c);
+ c.recycle();
} finally {
mService.continueWindowLayout();
}
@@ -2372,18 +2359,24 @@
*
* @return {@code true} if the top activity looks like it belongs to {@param userId}.
*/
- private boolean taskTopActivityIsUser(Task task, @UserIdInt int userId) {
+ private void taskTopActivityIsUser(Task task, @UserIdInt int userId) {
// To handle the case that work app is in the task but just is not the top one.
final ActivityRecord activityRecord = task.getTopNonFinishingActivity();
final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null);
- return (activityRecord != null && activityRecord.mUserId == userId)
- || (resultTo != null && resultTo.mUserId == userId);
+ // Check the task for a top activity belonging to userId, or returning a
+ // result to an activity belonging to userId. Example case: a document
+ // picker for personal files, opened by a work app, should still get locked.
+ if ((activityRecord != null && activityRecord.mUserId == userId)
+ || (resultTo != null && resultTo.mUserId == userId)) {
+ mService.getTaskChangeNotificationController().notifyTaskProfileLocked(
+ task.mTaskId, userId);
+ }
}
void cancelInitializingActivities() {
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
stack.cancelInitializingActivities();
@@ -2414,29 +2407,25 @@
+ " lookup");
}
- int numDisplays = mActivityDisplays.size();
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
- for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getStackAt(stackNdx);
- final Task task = stack.taskForIdLocked(id);
- if (task == null) {
- continue;
+ final PooledPredicate p = PooledLambda.obtainPredicate(
+ Task::isTaskId, PooledLambda.__(Task.class), id);
+ Task task = mRootWindowContainer.getTask(p);
+ p.recycle();
+
+ if (task != null) {
+ if (aOptions != null) {
+ // Resolve the stack the task should be placed in now based on options
+ // and reparent if needed.
+ final ActivityStack launchStack =
+ getLaunchStack(null, aOptions, task, onTop);
+ if (launchStack != null && task.getStack() != launchStack) {
+ final int reparentMode = onTop
+ ? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;
+ task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,
+ "anyTaskForId");
}
- if (aOptions != null) {
- // Resolve the stack the task should be placed in now based on options
- // and reparent if needed.
- final ActivityStack launchStack =
- getLaunchStack(null, aOptions, task, onTop);
- if (launchStack != null && stack != launchStack) {
- final int reparentMode = onTop
- ? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;
- task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,
- "anyTaskForId");
- }
- }
- return task;
}
+ return task;
}
// If we are matching stack tasks only, return now
@@ -2447,7 +2436,7 @@
// Otherwise, check the recent tasks and return if we find it there and we are not restoring
// the task from recents
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents");
- final Task task = mStackSupervisor.mRecentTasks.getTask(id);
+ task = mStackSupervisor.mRecentTasks.getTask(id);
if (task == null) {
if (DEBUG_RECENTS) {
@@ -2472,9 +2461,9 @@
}
ActivityRecord isInAnyStack(IBinder token) {
- int numDisplays = mActivityDisplays.size();
+ int numDisplays = getChildCount();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
final ActivityRecord r = stack.isInStackLocked(token);
@@ -2492,7 +2481,7 @@
@WindowConfiguration.WindowingMode int ignoreWindowingMode, int callingUid,
boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
mStackSupervisor.getRunningTasks().getTasks(maxNum, list, ignoreActivityType,
- ignoreWindowingMode, mActivityDisplays, callingUid, allowed, crossUser, profileIds);
+ ignoreWindowingMode, this, callingUid, allowed, crossUser, profileIds);
}
void sendPowerHintForLaunchStartIfNeeded(boolean forceSend, ActivityRecord targetActivity) {
@@ -2508,9 +2497,9 @@
// activity on all displays, or if there are no resumed activities in the system.
boolean noResumedActivities = true;
boolean allFocusedProcessesDiffer = true;
- for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
- final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx);
- final ActivityRecord resumedActivity = activityDisplay.getResumedActivity();
+ for (int displayNdx = 0; displayNdx < getChildCount(); ++displayNdx) {
+ final DisplayContent displayContent = getChildAt(displayNdx);
+ final ActivityRecord resumedActivity = displayContent.getResumedActivity();
final WindowProcessController resumedActivityProcess =
resumedActivity == null ? null : resumedActivity.app;
@@ -2555,9 +2544,9 @@
return getTopDisplayFocusedStack().getDumpActivitiesLocked(name);
} else {
ArrayList<ActivityRecord> activities = new ArrayList<>();
- int numDisplays = mActivityDisplays.size();
+ int numDisplays = getChildCount();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
+ final DisplayContent display = getChildAt(displayNdx);
for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getStackAt(stackNdx);
if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
@@ -2572,9 +2561,9 @@
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println("topDisplayFocusedStack=" + getTopDisplayFocusedStack());
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityDisplay display = mActivityDisplays.get(i);
- display.dump(pw, prefix);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final DisplayContent display = getChildAt(i);
+ display.dump(pw, prefix, true /* dumpAll */);
}
}
@@ -2584,17 +2573,17 @@
*/
void dumpDisplayConfigs(PrintWriter pw, String prefix) {
pw.print(prefix); pw.println("Display override configurations:");
- final int displayCount = mActivityDisplays.size();
+ final int displayCount = getChildCount();
for (int i = 0; i < displayCount; i++) {
- final ActivityDisplay activityDisplay = mActivityDisplays.get(i);
- pw.print(prefix); pw.print(" "); pw.print(activityDisplay.mDisplayId); pw.print(": ");
- pw.println(activityDisplay.getRequestedOverrideConfiguration());
+ final DisplayContent displayContent = getChildAt(i);
+ pw.print(prefix); pw.print(" "); pw.print(displayContent.mDisplayId); pw.print(": ");
+ pw.println(displayContent.getRequestedOverrideConfiguration());
}
}
public void dumpDisplays(PrintWriter pw) {
- for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
- final ActivityDisplay display = mActivityDisplays.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final DisplayContent display = getChildAt(i);
pw.print("[id:" + display.mDisplayId + " stacks:");
display.dumpStacks(pw);
pw.print("]");
@@ -2605,18 +2594,17 @@
String dumpPackage) {
boolean printed = false;
boolean needSep = false;
- for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
- ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx);
- pw.print("Display #"); pw.print(activityDisplay.mDisplayId);
+ for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+ DisplayContent displayContent = getChildAt(displayNdx);
+ pw.print("Display #"); pw.print(displayContent.mDisplayId);
pw.println(" (activities from top to bottom):");
- final ActivityDisplay display = mActivityDisplays.get(displayNdx);
- for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getStackAt(stackNdx);
+ for (int stackNdx = displayContent.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = displayContent.getStackAt(stackNdx);
pw.println();
printed = stack.dump(fd, pw, dumpAll, dumpClient, dumpPackage, needSep);
needSep = printed;
}
- printThisActivity(pw, activityDisplay.getResumedActivity(), dumpPackage, needSep,
+ printThisActivity(pw, displayContent.getResumedActivity(), dumpPackage, needSep,
" ResumedActivity:");
}
@@ -2637,9 +2625,9 @@
@WindowTraceLogLevel int logLevel) {
final long token = proto.start(fieldId);
super.dumpDebug(proto, CONFIGURATION_CONTAINER, logLevel);
- for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
- final ActivityDisplay activityDisplay = mActivityDisplays.get(displayNdx);
- activityDisplay.dumpDebug(proto, DISPLAYS, logLevel);
+ for (int displayNdx = 0; displayNdx < getChildCount(); ++displayNdx) {
+ final DisplayContent displayContent = getChildAt(displayNdx);
+ displayContent.dumpDebug(proto, DISPLAYS, logLevel);
}
mStackSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
// TODO(b/111541062): Update tests to look for resumed activities on all displays
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 361bbe4..8b08344 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -22,10 +22,8 @@
import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -54,6 +52,8 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
import android.content.res.Configuration;
import android.hardware.power.V1_0.PowerHint;
import android.os.Binder;
@@ -74,11 +74,14 @@
import android.view.SurfaceControl;
import android.view.WindowManager;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.protolog.common.ProtoLog;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.function.Consumer;
/** Root {@link WindowContainer} for the device. */
@@ -286,14 +289,19 @@
}
/**
- * Returns true if the callingUid has any non-toast window currently visible to the user.
- * Also ignores TYPE_APPLICATION_STARTING, since those windows don't belong to apps.
+ * Returns {@code true} if the callingUid has any non-toast window currently visible to the
+ * user. Also ignores {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_STARTING},
+ * since those windows don't belong to apps.
+ * @see WindowState#isNonToastOrStarting()
*/
boolean isAnyNonToastWindowVisibleForUid(int callingUid) {
- return forAllWindows(w ->
- w.getOwningUid() == callingUid && w.mAttrs.type != TYPE_TOAST
- && w.mAttrs.type != TYPE_APPLICATION_STARTING && w.isVisibleNow(),
- true /* traverseTopToBottom */);
+ final PooledPredicate p = PooledLambda.obtainPredicate(
+ WindowState::isNonToastWindowVisibleForUid,
+ PooledLambda.__(WindowState.class), callingUid);
+
+ final WindowState w = getWindow(p);
+ p.recycle();
+ return w != null;
}
/**
@@ -1085,4 +1093,21 @@
}
return null;
}
+
+ void getDisplayContextsWithNonToastVisibleWindows(int pid, List<Context> outContexts) {
+ if (outContexts == null) {
+ return;
+ }
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ DisplayContent dc = mChildren.get(i);
+ if (dc.isAnyNonToastWindowVisibleForPid(pid)) {
+ outContexts.add(dc.getDisplayUiContext());
+ }
+ }
+ }
+
+ @Nullable Context getDisplayUiContext(int displayId) {
+ return getDisplayContent(displayId) != null
+ ? getDisplayContent(displayId).getDisplayUiContext() : null;
+ }
}
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index ca9d91e..5783713 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -16,11 +16,18 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
+import android.os.UserHandle;
import android.util.ArraySet;
+import com.android.internal.util.function.pooled.PooledConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
+
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
@@ -39,8 +46,17 @@
private final TreeSet<Task> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
private final ArrayList<Task> mTmpStackTasks = new ArrayList<>();
+ private int mCallingUid;
+ private int mUserId;
+ private boolean mCrossUser;
+ private ArraySet<Integer> mProfileIds;
+ private boolean mAllowed;
+ private int mIgnoreActivityType;
+ private int mIgnoreWindowingMode;
+ private ActivityStack mTopDisplayFocusStack;
+
void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
- @WindowingMode int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays,
+ @WindowingMode int ignoreWindowingMode, RootActivityContainer root,
int callingUid, boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
// Return early if there are no tasks to fetch
if (maxNum <= 0) {
@@ -49,17 +65,19 @@
// Gather all of the tasks across all of the tasks, and add them to the sorted set
mTmpSortedSet.clear();
- final int numDisplays = activityDisplays.size();
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
- final ActivityDisplay display = activityDisplays.get(displayNdx);
- for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getStackAt(stackNdx);
- mTmpStackTasks.clear();
- stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode,
- callingUid, allowed, crossUser, profileIds);
- mTmpSortedSet.addAll(mTmpStackTasks);
- }
- }
+ mCallingUid = callingUid;
+ mUserId = UserHandle.getUserId(callingUid);
+ mCrossUser = crossUser;
+ mProfileIds = profileIds;
+ mAllowed = allowed;
+ mIgnoreActivityType = ignoreActivityType;
+ mIgnoreWindowingMode = ignoreWindowingMode;
+ mTopDisplayFocusStack = root.getTopDisplayFocusedStack();
+
+ final PooledConsumer c = PooledLambda.obtainConsumer(RunningTasks::processTask, this,
+ PooledLambda.__(Task.class));
+ root.mRootWindowContainer.forAllTasks(c, false);
+ c.recycle();
// Take the first {@param maxNum} tasks and create running task infos for them
final Iterator<Task> iter = mTmpSortedSet.iterator();
@@ -74,9 +92,45 @@
}
}
- /**
- * Constructs a {@link RunningTaskInfo} from a given {@param task}.
- */
+ private void processTask(Task task) {
+ if (task.getTopNonFinishingActivity() == null) {
+ // Skip if there are no activities in the task
+ return;
+ }
+ if (task.effectiveUid != mCallingUid) {
+ if (task.mUserId != mUserId && !mCrossUser && !mProfileIds.contains(task.mUserId)) {
+ // Skip if the caller does not have cross user permission or cannot access
+ // the task's profile
+ return;
+ }
+ if (!mAllowed && !task.isActivityTypeHome()) {
+ // Skip if the caller isn't allowed to fetch this task, except for the home
+ // task which we always return.
+ return;
+ }
+ }
+ if (mIgnoreActivityType != ACTIVITY_TYPE_UNDEFINED
+ && task.getActivityType() == mIgnoreActivityType) {
+ // Skip ignored activity type
+ return;
+ }
+ if (mIgnoreWindowingMode != WINDOWING_MODE_UNDEFINED
+ && task.getWindowingMode() == mIgnoreWindowingMode) {
+ // Skip ignored windowing mode
+ return;
+ }
+
+ final ActivityStack stack = task.getStack();
+ if (stack == mTopDisplayFocusStack && stack.getTopMostTask() == task) {
+ // For the focused stack top task, update the last stack active time so that it can be
+ // used to determine the order of the tasks (it may not be set for newly created tasks)
+ task.touchActiveTime();
+ }
+
+ mTmpSortedSet.add(task);
+ }
+
+ /** Constructs a {@link RunningTaskInfo} from a given {@param task}. */
private RunningTaskInfo createRunningTaskInfo(Task task) {
final RunningTaskInfo rti = new RunningTaskInfo();
task.fillTaskInfo(rti);
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 1a7d214..399c5d3 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -172,7 +172,7 @@
.setContainerLayer()
.build();
- mSurfaceControl = displayContent.makeSurface(null)
+ mSurfaceControl = mService.makeSurfaceBuilder(null)
.setName("ScreenshotSurface")
.setParent(mRotationLayer)
.setBufferSize(mWidth, mHeight)
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
new file mode 100644
index 0000000..9732637
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ShellRoot.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.server.wm;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+
+/**
+ * Represents a piece of the hierarchy under which a client Shell can manage sub-windows.
+ */
+public class ShellRoot {
+ private static final String TAG = "ShellRoot";
+ private final DisplayContent mDisplayContent;
+ private IWindow mClient;
+ private WindowToken mToken;
+ private final IBinder.DeathRecipient mDeathRecipient;
+ private SurfaceControl mSurfaceControl = null;
+
+ ShellRoot(@NonNull IWindow client, @NonNull DisplayContent dc, final int windowType) {
+ mDisplayContent = dc;
+ mDeathRecipient = () -> mDisplayContent.removeShellRoot(windowType);
+ try {
+ client.asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to add shell root for layer " + windowType + " on display "
+ + dc.getDisplayId(), e);
+ return;
+ }
+ mClient = client;
+ mToken = new WindowToken(
+ dc.mWmService, client.asBinder(), windowType, true, dc, true, false);
+ mSurfaceControl = mToken.makeChildSurface(null)
+ .setContainerLayer().setName("Shell Root Leash " + dc.getDisplayId()).build();
+ mToken.getPendingTransaction().show(mSurfaceControl);
+ }
+
+ void clear() {
+ if (mClient != null) {
+ mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
+ mClient = null;
+ }
+ if (mToken != null) {
+ mToken.removeImmediately();
+ mToken = null;
+ }
+ }
+
+ SurfaceControl getSurfaceControl() {
+ return mSurfaceControl;
+ }
+
+ IWindow getClient() {
+ return mClient;
+ }
+}
+
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b1d0692..45c012e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -135,7 +135,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
@@ -155,6 +154,7 @@
import java.util.ArrayList;
import java.util.Objects;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
class Task extends WindowContainer<WindowContainer> {
@@ -684,7 +684,7 @@
final boolean toTopOfStack = position == MAX_VALUE;
if (toTopOfStack && toStack.getResumedActivity() != null
- && toStack.topRunningActivityLocked() != null) {
+ && toStack.topRunningActivity() != null) {
// Pause the resumed activity on the target stack while re-parenting task on top of it.
toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
null /* resuming */);
@@ -720,7 +720,7 @@
// Whenever we are moving the top activity from the front stack we want to make sure to
// move the stack to the front.
final boolean wasFront = r != null && sourceStack.isTopStackOnDisplay()
- && (sourceStack.topRunningActivityLocked() == r);
+ && (sourceStack.topRunningActivity() == r);
final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
|| (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
@@ -1256,7 +1256,7 @@
// work.
// TODO: If the callers to removeChild() changes such that we have multiple places
// where we are destroying the task, move this back into removeChild()
- mAtmService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false /* killProcess */,
+ mAtmService.mStackSupervisor.removeTask(this, false /* killProcess */,
!REMOVE_FROM_RECENTS, reason);
}
} else if (!mReuseTask) {
@@ -1582,8 +1582,8 @@
if (!inPinnedWindowingMode() && mStack != null) {
final int defaultMinSizeDp =
mAtmService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp;
- final ActivityDisplay display =
- mAtmService.mRootActivityContainer.getActivityDisplay(mStack.mDisplayId);
+ final DisplayContent display =
+ mAtmService.mRootActivityContainer.getDisplayContent(mStack.mDisplayId);
final float density =
(float) display.getConfiguration().densityDpi / DisplayMetrics.DENSITY_DEFAULT;
final int defaultMinSize = (int) (defaultMinSizeDp * density);
@@ -1881,7 +1881,10 @@
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
|| inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- if (insideParentBounds && mStack != null) {
+ if (insideParentBounds && WindowConfiguration.isFloating(windowingMode)) {
+ mTmpNonDecorBounds.set(mTmpFullBounds);
+ mTmpStableBounds.set(mTmpFullBounds);
+ } else if (insideParentBounds && mStack != null) {
final DisplayInfo di = new DisplayInfo();
mStack.getDisplay().mDisplay.getDisplayInfo(di);
@@ -1994,7 +1997,7 @@
((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
final Rect parentBounds =
new Rect(newParentConfig.windowConfiguration.getBounds());
- final ActivityDisplay display = mStack.getDisplay();
+ final DisplayContent display = mStack.getDisplay();
if (display != null && display.mDisplayContent != null) {
// If a freeform window moves below system bar, there is no way to move it again
// by touch. Because its caption is covered by system bar. So we exclude them
@@ -2182,22 +2185,12 @@
void reparent(ActivityStack stack, int position, boolean moveParents, String reason) {
if (DEBUG_STACK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId
+ " from stack=" + getTaskStack());
- EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask");
-
- final ActivityStack prevStack = getTaskStack();
- final boolean wasTopFocusedStack =
- mAtmService.mRootActivityContainer.isTopDisplayFocusedStack(prevStack);
- final ActivityDisplay prevStackDisplay = prevStack.getDisplay();
+ EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason);
position = stack.findPositionForTask(this, position, showForAllUsers());
reparent(stack, position);
- if (!moveParents) {
- // Only move home stack forward if we are not going to move the new parent forward.
- prevStack.moveHomeStackToFrontIfNeeded(wasTopFocusedStack, prevStackDisplay, reason);
- }
-
stack.positionChildAt(position, this, moveParents);
// If we are moving from the fullscreen stack to the pinned stack then we want to preserve
@@ -2659,12 +2652,13 @@
}
@Override
- void forAllTasks(Consumer<Task> callback) {
+ void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
+ // TODO(task-hierarchy): Change to traverse children when tasks can contain other tasks.
callback.accept(this);
}
@Override
- boolean forAllTasks(ToBooleanFunction<Task> callback) {
+ boolean forAllTasks(Function<Task, Boolean> callback) {
return callback.apply(this);
}
@@ -2707,6 +2701,10 @@
return mDimmer;
}
+ boolean isTaskForUser(int userId) {
+ return mUserId == userId;
+ }
+
@Override
void prepareSurfaces() {
mDimmer.resetDimStates();
@@ -2800,6 +2798,10 @@
return info;
}
+ boolean isTaskId(int taskId) {
+ return mTaskId == taskId;
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(mUserId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 9d19cfe..4de61f0 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -137,7 +137,7 @@
// STEP 1: Determine the display to launch the activity/task.
final int displayId = getPreferredLaunchDisplay(task, options, source, currentParams);
outParams.mPreferredDisplayId = displayId;
- ActivityDisplay display = mSupervisor.mRootActivityContainer.getActivityDisplay(displayId);
+ DisplayContent display = mSupervisor.mRootActivityContainer.getDisplayContent(displayId);
if (DEBUG) {
appendLog("display-id=" + outParams.mPreferredDisplayId + " display-windowing-mode="
+ display.getWindowingMode());
@@ -282,7 +282,7 @@
if (source != null && source.inFreeformWindowingMode()
&& resolvedMode == WINDOWING_MODE_FREEFORM
&& outParams.mBounds.isEmpty()
- && source.getDisplayId() == display.mDisplayId) {
+ && source.getDisplayId() == display.getDisplayId()) {
// Set bounds to be not very far from source activity.
cascadeBounds(source.getConfiguration().windowConfiguration.getBounds(),
display, outParams.mBounds);
@@ -333,17 +333,17 @@
}
if (displayId != INVALID_DISPLAY
- && mSupervisor.mRootActivityContainer.getActivityDisplay(displayId) == null) {
+ && mSupervisor.mRootActivityContainer.getDisplayContent(displayId) == null) {
displayId = currentParams.mPreferredDisplayId;
}
displayId = (displayId == INVALID_DISPLAY) ? currentParams.mPreferredDisplayId : displayId;
return (displayId != INVALID_DISPLAY
- && mSupervisor.mRootActivityContainer.getActivityDisplay(displayId) != null)
+ && mSupervisor.mRootActivityContainer.getDisplayContent(displayId) != null)
? displayId : DEFAULT_DISPLAY;
}
- private boolean canInheritWindowingModeFromSource(@NonNull ActivityDisplay display,
+ private boolean canInheritWindowingModeFromSource(@NonNull DisplayContent display,
@Nullable ActivityRecord source) {
if (source == null) {
return false;
@@ -365,10 +365,10 @@
// Only inherit windowing mode if both source and target activities are on the same display.
// Otherwise we may have unintended freeform windows showing up if an activity in freeform
// window launches an activity on a fullscreen display by specifying display ID.
- return display.mDisplayId == source.getDisplayId();
+ return display.getDisplayId() == source.getDisplayId();
}
- private boolean canApplyFreeformWindowPolicy(@NonNull ActivityDisplay display, int launchMode) {
+ private boolean canApplyFreeformWindowPolicy(@NonNull DisplayContent display, int launchMode) {
return mSupervisor.mService.mSupportsFreeformWindowManagement
&& (display.inFreeformWindowingMode() || launchMode == WINDOWING_MODE_FREEFORM);
}
@@ -378,7 +378,7 @@
&& launchMode == WINDOWING_MODE_PINNED;
}
- private void getLayoutBounds(@NonNull ActivityDisplay display, @NonNull ActivityRecord root,
+ private void getLayoutBounds(@NonNull DisplayContent display, @NonNull ActivityRecord root,
@NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect outBounds) {
final int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
final int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
@@ -488,7 +488,7 @@
return orientation;
}
- private void cascadeBounds(@NonNull Rect srcBounds, @NonNull ActivityDisplay display,
+ private void cascadeBounds(@NonNull Rect srcBounds, @NonNull DisplayContent display,
@NonNull Rect outBounds) {
outBounds.set(srcBounds);
float density = (float) display.getConfiguration().densityDpi / DENSITY_DEFAULT;
@@ -500,7 +500,7 @@
outBounds.offset(dx, dy);
}
- private void getTaskBounds(@NonNull ActivityRecord root, @NonNull ActivityDisplay display,
+ private void getTaskBounds(@NonNull ActivityRecord root, @NonNull DisplayContent display,
@NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds,
@NonNull Rect inOutBounds) {
if (resolvedMode == WINDOWING_MODE_FULLSCREEN) {
@@ -567,7 +567,7 @@
}
}
- private int resolveOrientation(@NonNull ActivityRecord root, @NonNull ActivityDisplay display,
+ private int resolveOrientation(@NonNull ActivityRecord root, @NonNull DisplayContent display,
@NonNull Rect bounds) {
int orientation = resolveOrientation(root);
@@ -593,7 +593,7 @@
return orientation;
}
- private void getDefaultFreeformSize(@NonNull ActivityDisplay display,
+ private void getDefaultFreeformSize(@NonNull DisplayContent display,
@NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) {
// Default size, which is letterboxing/pillarboxing in display. That's to say the large
// dimension of default size is the small dimension of display size, and the small dimension
@@ -637,7 +637,7 @@
* Gets centered bounds of width x height. If inOutBounds is not empty, the result bounds
* centers at its center or display's app bounds center if inOutBounds is empty.
*/
- private void centerBounds(@NonNull ActivityDisplay display, int width, int height,
+ private void centerBounds(@NonNull DisplayContent display, int width, int height,
@NonNull Rect inOutBounds) {
if (inOutBounds.isEmpty()) {
inOutBounds.set(display.mDisplayContent.mDisplayFrames.mStable);
@@ -647,7 +647,7 @@
inOutBounds.set(left, top, left + width, top + height);
}
- private void adjustBoundsToFitInDisplay(@NonNull ActivityDisplay display,
+ private void adjustBoundsToFitInDisplay(@NonNull DisplayContent display,
@NonNull Rect inOutBounds) {
final Rect displayStableBounds = display.mDisplayContent.mDisplayFrames.mStable;
@@ -702,7 +702,7 @@
* @param display the display which tasks are to check
* @param inOutBounds the bounds used to input initial bounds and output result bounds
*/
- private void adjustBoundsToAvoidConflictInDisplay(@NonNull ActivityDisplay display,
+ private void adjustBoundsToAvoidConflictInDisplay(@NonNull DisplayContent display,
@NonNull Rect inOutBounds) {
final List<Rect> taskBoundsToCheck = new ArrayList<>();
for (int i = 0; i < display.getStackCount(); ++i) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 5cbab5d..7b0d841 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -109,7 +109,7 @@
removeRunningEntry(taskId);
}
- private void removeRunningEntry(int taskId) {
+ void removeRunningEntry(int taskId) {
final CacheEntry entry = mRunningCache.get(taskId);
if (entry != null) {
mAppTaskMap.remove(entry.topApp);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c1a36c4..e4744db 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -89,6 +89,14 @@
@VisibleForTesting
static final int SNAPSHOT_MODE_NONE = 2;
+ /**
+ * Constant for <code>scaleFactor</code> when calling {@link #snapshotTask} which is
+ * interpreted as using the most appropriate scale ratio for the system.
+ * This may yield a smaller ratio on low memory devices.
+ */
+ @VisibleForTesting
+ static final float SNAPSHOT_SCALE_AUTO = -1f;
+
private final WindowManagerService mService;
private final TaskSnapshotCache mCache;
@@ -171,22 +179,30 @@
}
void snapshotTasks(ArraySet<Task> tasks) {
+ snapshotTasks(tasks, false /* allowSnapshotHome */);
+ }
+
+ private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task task = tasks.valueAt(i);
- final int mode = getSnapshotMode(task);
final TaskSnapshot snapshot;
- switch (mode) {
- case SNAPSHOT_MODE_NONE:
- continue;
- case SNAPSHOT_MODE_APP_THEME:
- snapshot = drawAppThemeSnapshot(task);
- break;
- case SNAPSHOT_MODE_REAL:
- snapshot = snapshotTask(task);
- break;
- default:
- snapshot = null;
- break;
+ final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
+ if (snapshotHome) {
+ snapshot = snapshotTask(task);
+ } else {
+ switch (getSnapshotMode(task)) {
+ case SNAPSHOT_MODE_NONE:
+ continue;
+ case SNAPSHOT_MODE_APP_THEME:
+ snapshot = drawAppThemeSnapshot(task);
+ break;
+ case SNAPSHOT_MODE_REAL:
+ snapshot = snapshotTask(task);
+ break;
+ default:
+ snapshot = null;
+ break;
+ }
}
if (snapshot != null) {
final GraphicBuffer buffer = snapshot.getSnapshot();
@@ -196,8 +212,11 @@
+ buffer.getHeight());
} else {
mCache.putSnapshot(task, snapshot);
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- task.onSnapshotChanged(snapshot);
+ // Don't persist or notify the change for the temporal snapshot.
+ if (!snapshotHome) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ task.onSnapshotChanged(snapshot);
+ }
}
}
}
@@ -249,6 +268,86 @@
});
}
+ /**
+ * Validates the state of the Task is appropriate to capture a snapshot, collects
+ * information from the task and populates the builder.
+ *
+ * @param task the task to capture
+ * @param scaleFraction the scale fraction between 0-1.0, or {@link #SNAPSHOT_SCALE_AUTO}
+ * to automatically select
+ * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
+ * automatically select
+ * @param builder the snapshot builder to populate
+ *
+ * @return true if the state of the task is ok to proceed
+ */
+ @VisibleForTesting
+ boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
+ TaskSnapshot.Builder builder) {
+ if (!mService.mPolicy.isScreenOn()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+ }
+ return false;
+ }
+ final ActivityRecord activity = findAppTokenForSnapshot(task);
+ if (activity == null) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
+ }
+ return false;
+ }
+ if (activity.hasCommittedReparentToAnimationLeash()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
+ }
+ return false;
+ }
+
+ final WindowState mainWindow = activity.findMainWindow();
+ if (mainWindow == null) {
+ Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
+ return false;
+ }
+
+ builder.setIsRealSnapshot(true);
+ builder.setId(System.currentTimeMillis());
+ builder.setContentInsets(getInsets(mainWindow));
+
+ final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
+
+ if (scaleFraction == SNAPSHOT_SCALE_AUTO) {
+ builder.setScaleFraction(isLowRamDevice
+ ? mPersister.getReducedScale()
+ : mFullSnapshotScale);
+ builder.setReducedResolution(isLowRamDevice);
+ } else {
+ builder.setScaleFraction(scaleFraction);
+ builder.setReducedResolution(scaleFraction < 1.0f);
+ }
+
+ final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
+ final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
+
+ if (pixelFormat == PixelFormat.UNKNOWN) {
+ pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
+ && !(isWindowTranslucent && isShowWallpaper)
+ ? PixelFormat.RGB_565
+ : PixelFormat.RGBA_8888;
+ }
+
+ final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
+ && (!activity.fillsParent() || isWindowTranslucent);
+
+ builder.setTopActivityComponent(activity.mActivityComponent);
+ builder.setPixelFormat(pixelFormat);
+ builder.setIsTranslucent(isTranslucent);
+ builder.setOrientation(activity.getTask().getConfiguration().orientation);
+ builder.setWindowingMode(task.getWindowingMode());
+ builder.setSystemUiVisibility(getSystemUiVisibility(task));
+ return true;
+ }
+
@Nullable
SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task,
float scaleFraction) {
@@ -277,63 +376,31 @@
return screenshotBuffer;
}
- @Nullable private TaskSnapshot snapshotTask(Task task) {
- if (!mService.mPolicy.isScreenOn()) {
- if (DEBUG_SCREENSHOT) {
- Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
- }
+ @Nullable
+ TaskSnapshot snapshotTask(Task task) {
+ return snapshotTask(task, SNAPSHOT_SCALE_AUTO, PixelFormat.UNKNOWN);
+ }
+
+ @Nullable
+ TaskSnapshot snapshotTask(Task task, float scaleFraction, int pixelFormat) {
+ TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
+
+ if (!prepareTaskSnapshot(task, scaleFraction, pixelFormat, builder)) {
+ // Failed some pre-req. Has been logged.
return null;
}
- final ActivityRecord activity = findAppTokenForSnapshot(task);
- if (activity == null) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
- }
- return null;
- }
- if (activity.hasCommittedReparentToAnimationLeash()) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
- }
- return null;
- }
-
- final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
- final float scaleFraction = isLowRamDevice
- ? mPersister.getReducedScale()
- : mFullSnapshotScale;
-
- final WindowState mainWindow = activity.findMainWindow();
- if (mainWindow == null) {
- Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
- return null;
- }
- final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
- final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0;
- final int pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
- && !(isWindowTranslucent && isShowWallpaper)
- ? PixelFormat.RGB_565
- : PixelFormat.RGBA_8888;
final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer =
- createTaskSnapshot(task, scaleFraction, pixelFormat);
+ createTaskSnapshot(task, builder.getScaleFraction(),
+ builder.getPixelFormat());
if (screenshotBuffer == null) {
- if (DEBUG_SCREENSHOT) {
- Slog.w(TAG_WM, "Failed to take screenshot for " + task);
- }
+ // Failed to acquire image. Has been logged.
return null;
}
- final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
- && (!activity.fillsParent() || isWindowTranslucent);
- return new TaskSnapshot(
- System.currentTimeMillis() /* id */,
- activity.mActivityComponent, screenshotBuffer.getGraphicBuffer(),
- screenshotBuffer.getColorSpace(),
- activity.getTask().getConfiguration().orientation,
- getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */,
- true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task),
- isTranslucent);
+ builder.setSnapshot(screenshotBuffer.getGraphicBuffer());
+ builder.setColorSpace(screenshotBuffer.getColorSpace());
+ return builder.build();
}
private boolean shouldDisableSnapshots() {
@@ -403,7 +470,7 @@
final LayoutParams attrs = mainWindow.getAttrs();
final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(),
- mFullSnapshotScale);
+ mFullSnapshotScale, mainWindow.getClientInsetsState());
final int width = (int) (task.getBounds().width() * mFullSnapshotScale);
final int height = (int) (task.getBounds().height() * mFullSnapshotScale);
@@ -450,6 +517,10 @@
mPersister.onTaskRemovedFromRecents(taskId, userId);
}
+ void removeSnapshotCache(int taskId) {
+ mCache.removeRunningEntry(taskId);
+ }
+
/**
* See {@link TaskSnapshotPersister#removeObsoleteFiles}
*/
@@ -485,7 +556,9 @@
mTmpTasks.add(task);
}
});
- snapshotTasks(mTmpTasks);
+ // Allow taking snapshot of home when turning screen off to reduce the delay of
+ // unlocking/waking to home.
+ snapshotTasks(mTmpTasks, true /* allowSnapshotHome */);
}
} finally {
listener.onScreenOff();
@@ -494,16 +567,16 @@
}
/**
- * @return The SystemUI visibility flags for the top fullscreen window in the given
+ * @return The SystemUI visibility flags for the top fullscreen opaque window in the given
* {@param task}.
*/
private int getSystemUiVisibility(Task task) {
final ActivityRecord topFullscreenActivity = task.getTopFullscreenActivity();
- final WindowState topFullscreenWindow = topFullscreenActivity != null
- ? topFullscreenActivity.getTopFullscreenWindow()
+ final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null
+ ? topFullscreenActivity.getTopFullscreenOpaqueWindow()
: null;
- if (topFullscreenWindow != null) {
- return topFullscreenWindow.getSystemUiVisibility();
+ if (topFullscreenOpaqueWindow != null) {
+ return topFullscreenOpaqueWindow.getSystemUiVisibility();
}
return 0;
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 5915590..d36a5d4 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -54,7 +54,6 @@
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_RECENTS_REDUCED_SCALE = .1f;
static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
private static final long DELAY_MS = 100;
private static final int QUALITY = 95;
@@ -85,14 +84,8 @@
TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
mDirectoryResolver = resolver;
- if (service.mLowRamTaskSnapshotsAndRecents) {
- // Use very low res snapshots if we are using Go version of recents.
- mReducedScale = LOW_RAM_RECENTS_REDUCED_SCALE;
- } else {
- // TODO(122671846) Replace the low RAM value scale with the above when it is fully built
- mReducedScale = ActivityManager.isLowRamDeviceStatic()
- ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE;
- }
+ mReducedScale = ActivityManager.isLowRamDeviceStatic()
+ ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE;
mUse16BitFormat = service.mContext.getResources().getBoolean(
com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
}
@@ -373,8 +366,6 @@
}
boolean writeBuffer() {
- // TODO(b/116112787) TaskSnapshot needs bookkeep the ColorSpace of the
- // hardware bitmap when created.
final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
mSnapshot.getSnapshot(), mSnapshot.getColorSpace());
if (bitmap == null) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index b3750e9..5b458d8 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -71,6 +71,7 @@
import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -154,6 +155,7 @@
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
final TaskDescription taskDescription = new TaskDescription();
taskDescription.setBackgroundColor(WHITE);
+ final WindowState topFullscreenOpaqueWindow;
final int sysUiVis;
final int windowFlags;
final int windowPrivateFlags;
@@ -173,15 +175,15 @@
+ task);
return null;
}
- final WindowState topFullscreenWindow = topFullscreenActivity.getTopFullscreenWindow();
- if (mainWindow == null || topFullscreenWindow == null) {
+ topFullscreenOpaqueWindow = topFullscreenActivity.getTopFullscreenOpaqueWindow();
+ if (mainWindow == null || topFullscreenOpaqueWindow == null) {
Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find main window for activity="
+ activity);
return null;
}
- sysUiVis = topFullscreenWindow.getSystemUiVisibility();
- windowFlags = topFullscreenWindow.getAttrs().flags;
- windowPrivateFlags = topFullscreenWindow.getAttrs().privateFlags;
+ sysUiVis = topFullscreenOpaqueWindow.getSystemUiVisibility();
+ windowFlags = topFullscreenOpaqueWindow.getAttrs().flags;
+ windowPrivateFlags = topFullscreenOpaqueWindow.getAttrs().privateFlags;
layoutParams.packageName = mainWindow.getAttrs().packageName;
layoutParams.windowAnimations = mainWindow.getAttrs().windowAnimations;
@@ -200,11 +202,11 @@
final TaskDescription td = task.getTaskDescription();
if (td != null) {
- taskDescription.copyFrom(td);
+ taskDescription.copyFromPreserveHiddenFields(td);
}
taskBounds = new Rect();
task.getBounds(taskBounds);
- currentOrientation = topFullscreenWindow.getConfiguration().orientation;
+ currentOrientation = topFullscreenOpaqueWindow.getConfiguration().orientation;
}
try {
final int res = session.addToDisplay(window, window.mSeq, layoutParams,
@@ -220,7 +222,7 @@
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis,
windowFlags, windowPrivateFlags, taskBounds,
- currentOrientation);
+ currentOrientation, topFullscreenOpaqueWindow.getClientInsetsState());
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
@@ -238,7 +240,7 @@
TaskSnapshotSurface(WindowManagerService service, Window window, SurfaceControl surfaceControl,
TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
int sysUiVis, int windowFlags, int windowPrivateFlags, Rect taskBounds,
- int currentOrientation) {
+ int currentOrientation, InsetsState insetsState) {
mService = service;
mSurface = service.mSurfaceFactory.get();
mHandler = new Handler(mService.mH.getLooper());
@@ -251,7 +253,7 @@
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
mTaskBounds = taskBounds;
mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
- windowPrivateFlags, sysUiVis, taskDescription, 1f);
+ windowPrivateFlags, sysUiVis, taskDescription, 1f, insetsState);
mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
mTransaction = mService.mTransactionFactory.get();
@@ -502,9 +504,10 @@
private final int mWindowPrivateFlags;
private final int mSysUiVis;
private final float mScale;
+ private final InsetsState mInsetsState;
SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int sysUiVis,
- TaskDescription taskDescription, float scale) {
+ TaskDescription taskDescription, float scale, InsetsState insetsState) {
mWindowFlags = windowFlags;
mWindowPrivateFlags = windowPrivateFlags;
mSysUiVis = sysUiVis;
@@ -524,6 +527,7 @@
&& context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
mStatusBarPaint.setColor(mStatusBarColor);
mNavigationBarPaint.setColor(mNavigationBarColor);
+ mInsetsState = insetsState;
}
void setInsets(Rect contentInsets, Rect stableInsets) {
@@ -534,8 +538,11 @@
int getStatusBarColorViewHeight() {
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mSysUiVis, mStatusBarColor, mWindowFlags, forceBarBackground)) {
+ if (ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL
+ ? STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mSysUiVis, mStatusBarColor, mWindowFlags, forceBarBackground)
+ : STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
return (int) (getColorViewTopInset(mStableInsets.top, mContentInsets.top) * mScale);
} else {
return 0;
@@ -545,8 +552,11 @@
private boolean isNavigationBarColorViewVisible() {
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mSysUiVis, mNavigationBarColor, mWindowFlags, forceBarBackground);
+ return ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL
+ ? NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mSysUiVis, mNavigationBarColor, mWindowFlags, forceBarBackground)
+ : NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
}
void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e80f3b8..ce8e6dd 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -30,6 +30,7 @@
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -41,7 +42,6 @@
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.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
@@ -70,7 +70,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
-import com.android.server.policy.WindowManagerPolicy;
import com.android.server.protolog.common.ProtoLog;
import com.android.server.wm.SurfaceAnimator.Animatable;
@@ -132,6 +131,7 @@
protected final WindowList<E> mChildren = new WindowList<E>();
// The specified orientation for this window container.
+ @ActivityInfo.ScreenOrientation
protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
private final Pools.SynchronizedPool<ForAllWindowsConsumerWrapper> mConsumerWrapperPool =
@@ -244,6 +244,8 @@
protected final Rect mTmpRect = new Rect();
final Rect mTmpPrevBounds = new Rect();
+ private MagnificationSpec mLastMagnificationSpec;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -984,6 +986,7 @@
}
}
+ @ActivityInfo.ScreenOrientation
int getOrientation() {
return getOrientation(mOrientation);
}
@@ -1036,6 +1039,8 @@
if (wc.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
// Use the orientation if the container fills its parent or requested an explicit
// orientation that isn't SCREEN_ORIENTATION_UNSPECIFIED.
+ ProtoLog.v(WM_DEBUG_ORIENTATION, "%s is requesting orientation %d (%s)", toString(),
+ orientation, ActivityInfo.screenOrientationToString(orientation));
return orientation;
}
}
@@ -1202,9 +1207,17 @@
}
ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+ return getActivity(callback, traverseTopToBottom, null /*boundary*/);
+ }
+
+ ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
+ WindowContainer boundary) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mChildren.get(i).getActivity(callback, traverseTopToBottom);
+ final WindowContainer wc = mChildren.get(i);
+ if (wc == boundary) return null;
+
+ final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
if (r != null) {
return r;
}
@@ -1212,7 +1225,10 @@
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
- final ActivityRecord r = mChildren.get(i).getActivity(callback, traverseTopToBottom);
+ final WindowContainer wc = mChildren.get(i);
+ if (wc == boundary) return null;
+
+ final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
if (r != null) {
return r;
}
@@ -1320,14 +1336,57 @@
/**
* For all tasks at or below this container call the callback.
*
+ * @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
+ * stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
+ */
+ boolean forAllTasks(Function<Task, Boolean> callback) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ if (mChildren.get(i).forAllTasks(callback)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * For all tasks at or below this container call the callback.
+ *
* @param callback Callback to be called for every task.
*/
void forAllTasks(Consumer<Task> callback) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- mChildren.get(i).forAllTasks(callback);
+ forAllTasks(callback, true /*traverseTopToBottom*/);
+ }
+
+ void forAllTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
+ final int count = mChildren.size();
+ if (traverseTopToBottom) {
+ for (int i = count - 1; i >= 0; --i) {
+ mChildren.get(i).forAllTasks(callback, traverseTopToBottom);
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ mChildren.get(i).forAllTasks(callback, traverseTopToBottom);
+ }
}
}
+ Task getTaskAbove(Task t) {
+ return getTask(
+ (above) -> true, t, false /*includeBoundary*/, false /*traverseTopToBottom*/);
+ }
+
+ Task getTaskBelow(Task t) {
+ return getTask((below) -> true, t, false /*includeBoundary*/, true /*traverseTopToBottom*/);
+ }
+
+ Task getBottomMostTask() {
+ return getTask((t) -> true, false /*traverseTopToBottom*/);
+ }
+
+ Task getTopMostTask() {
+ return getTask((t) -> true, true /*traverseTopToBottom*/);
+ }
+
Task getTask(Predicate<Task> callback) {
return getTask(callback, true /*traverseTopToBottom*/);
}
@@ -1354,18 +1413,59 @@
}
/**
- * For all tasks at or below this container call the callback.
+ * Gets an task in a branch of the tree.
*
- * @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
- * stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
+ * @param callback called to test if this is the task that should be returned.
+ * @param boundary We don't return tasks via {@param callback} until we get to this node in
+ * the tree.
+ * @param includeBoundary If the boundary from be processed to return tasks.
+ * @param traverseTopToBottom direction to traverse the tree.
+ * @return The task if found or null.
*/
- boolean forAllTasks(ToBooleanFunction<Task> callback) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- if (mChildren.get(i).forAllTasks(callback)) {
- return true;
+ final Task getTask(Predicate<Task> callback, WindowContainer boundary, boolean includeBoundary,
+ boolean traverseTopToBottom) {
+ return getTask(callback, boundary, includeBoundary, traverseTopToBottom, new boolean[1]);
+ }
+
+ private Task getTask(Predicate<Task> callback,
+ WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
+ boolean[] boundaryFound) {
+ if (traverseTopToBottom) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final Task t = processGetTaskWithBoundary(callback, boundary,
+ includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
+ if (t != null) {
+ return t;
+ }
+ }
+ } else {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ final Task t = processGetTaskWithBoundary(callback, boundary,
+ includeBoundary, traverseTopToBottom, boundaryFound, mChildren.get(i));
+ if (t != null) {
+ return t;
+ }
}
}
- return false;
+
+ return null;
+ }
+
+ private Task processGetTaskWithBoundary(Predicate<Task> callback,
+ WindowContainer boundary, boolean includeBoundary, boolean traverseTopToBottom,
+ boolean[] boundaryFound, WindowContainer wc) {
+ if (wc == boundary || boundary == null) {
+ boundaryFound[0] = true;
+ if (!includeBoundary) return null;
+ }
+
+ if (boundaryFound[0]) {
+ return wc.getTask(callback, traverseTopToBottom);
+ }
+
+ return wc.getTask(
+ callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
}
WindowState getWindow(Predicate<WindowState> callback) {
@@ -1631,13 +1731,26 @@
if (shouldMagnify()) {
t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
.setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+ mLastMagnificationSpec = spec;
} else {
+ clearMagnificationSpec(t);
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).applyMagnificationSpec(t, spec);
}
}
}
+ void clearMagnificationSpec(Transaction t) {
+ if (mLastMagnificationSpec != null) {
+ t.setMatrix(mSurfaceControl, 1, 0, 0, 1)
+ .setPosition(mSurfaceControl, 0, 0);
+ }
+ mLastMagnificationSpec = null;
+ for (int i = 0; i < mChildren.size(); i++) {
+ mChildren.get(i).clearMagnificationSpec(t);
+ }
+ }
+
void prepareSurfaces() {
// If a leash has been set when the transaction was committed, then the leash reparent has
// been committed.
@@ -2056,15 +2169,8 @@
}
void waitForAllWindowsDrawn() {
- final WindowManagerPolicy policy = mWmService.mPolicy;
forAllWindows(w -> {
- final boolean keyguard = policy.isKeyguardHostWindow(w.mAttrs);
- if (w.isVisibleLw() && (w.mActivityRecord != null || keyguard)) {
- w.mWinAnimator.mDrawState = DRAW_PENDING;
- // Force add to mResizingWindows.
- w.resetLastContentInsets();
- mWaitingForDrawn.add(w);
- }
+ w.requestDrawIfNeeded(mWaitingForDrawn);
}, true /* traverseTopToBottom */);
}
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index 0490bac..97186b4 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -207,6 +207,7 @@
return (mLastFrame.width() != mFrame.width()) || (mLastFrame.height() != mFrame.height());
}
+ // TODO(b/118118435): Remove after migration.
/**
* Calculate the insets for the type
* {@link android.view.WindowManager.LayoutParams#TYPE_DOCK_DIVIDER}
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index 10c8ef0..74d5c04 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -19,6 +19,8 @@
import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE;
import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.provider.AndroidDeviceConfig;
import android.provider.DeviceConfig;
@@ -72,8 +74,8 @@
WindowManagerConstants(WindowManagerGlobalLock globalLock,
Runnable updateSystemGestureExclusionCallback,
DeviceConfigInterface deviceConfig) {
- mGlobalLock = globalLock;
- mUpdateSystemGestureExclusionCallback = updateSystemGestureExclusionCallback;
+ mGlobalLock = checkNotNull(globalLock);
+ mUpdateSystemGestureExclusionCallback = checkNotNull(updateSystemGestureExclusionCallback);
mDeviceConfig = deviceConfig;
mListenerAndroid = this::onAndroidPropertiesChanged;
mListenerWindowManager = this::onWindowPropertiesChanged;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index ea90e49..6e243f0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ClipData;
+import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
@@ -494,6 +495,11 @@
public abstract int getTopFocusedDisplayId();
/**
+ * @return The UI context of top focused display.
+ */
+ public abstract Context getTopFocusedDisplayUiContext();
+
+ /**
* Checks if this display is configured and allowed to show system decorations.
*/
public abstract boolean shouldShowSystemDecorOnDisplay(int displayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 95eb4dd..ba9e9ce 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
@@ -80,7 +79,6 @@
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.LockGuard.installLock;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_BOOT;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -407,8 +405,7 @@
private static final int ANIMATION_COMPLETED_TIMEOUT_MS = 5000;
- final WindowManagerConstants mConstants = new WindowManagerConstants(this,
- DeviceConfigInterface.REAL);
+ final WindowManagerConstants mConstants;
final WindowTracing mWindowTracing;
@@ -492,13 +489,6 @@
final long mDrawLockTimeoutMillis;
final boolean mAllowAnimationsInLowPowerMode;
- // TODO(b/122671846) Remove the flag below in favor of isLowRam once feature is stable
- /**
- * Use very low resolution task snapshots. Replaces task snapshot starting windows with
- * splashscreen starting windows. Used on low RAM devices to save memory.
- */
- final boolean mLowRamTaskSnapshotsAndRecents;
-
final boolean mAllowBootMessages;
final boolean mLimitedAlphaCompositing;
@@ -1115,8 +1105,6 @@
com.android.internal.R.bool.config_disableTransitionAnimation);
mPerDisplayFocusEnabled = context.getResources().getBoolean(
com.android.internal.R.bool.config_perDisplayFocusEnabled);
- mLowRamTaskSnapshotsAndRecents = context.getResources().getBoolean(
- com.android.internal.R.bool.config_lowRamTaskSnapshotsAndRecents);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -1243,6 +1231,7 @@
mHighRefreshRateBlacklist = HighRefreshRateBlacklist.create(context.getResources());
+ mConstants = new WindowManagerConstants(this, DeviceConfigInterface.REAL);
mConstants.start(new HandlerExecutor(mH));
LocalServices.addService(WindowManagerInternal.class, new LocalService());
@@ -1732,7 +1721,7 @@
}
}
- return mAtmService.mRootActivityContainer.getActivityDisplayOrCreate(displayId);
+ return mAtmService.mRootActivityContainer.getDisplayContentOrCreate(displayId);
}
private boolean doesAddToastWindowRequireToken(String packageName, int callingUid,
@@ -3722,6 +3711,26 @@
}
@Override
+ public SurfaceControl addShellRoot(int displayId, IWindow client, int windowType) {
+ 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 null;
+ }
+ return dc.addShellRoot(client, windowType);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
public int watchRotation(IRotationWatcher watcher, int displayId) {
final DisplayContent displayContent;
synchronized (mGlobalLock) {
@@ -4502,6 +4511,7 @@
public static final int ANIMATION_FAILSAFE = 60;
public static final int RECOMPUTE_FOCUS = 61;
public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
+ public static final int LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED = 63;
/**
* Used to denote that an integer field in a message will not be used.
@@ -4876,6 +4886,13 @@
}
break;
}
+ case LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED: {
+ synchronized (mGlobalLock) {
+ final DisplayContent displayContent = (DisplayContent) msg.obj;
+ displayContent.layoutAndAssignWindowLayersIfNeeded();
+ }
+ break;
+ }
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG_WM, "handleMessage: exit");
@@ -7354,6 +7371,13 @@
}
@Override
+ public Context getTopFocusedDisplayUiContext() {
+ synchronized (mGlobalLock) {
+ return mRoot.getTopFocusedDisplayContent().getDisplayUiContext();
+ }
+ }
+
+ @Override
public boolean shouldShowSystemDecorOnDisplay(int displayId) {
synchronized (mGlobalLock) {
return WindowManagerService.this.shouldShowSystemDecors(displayId);
@@ -7640,7 +7664,11 @@
return;
}
- handleDisplayFocusChange(touchedWindow);
+ final DisplayContent displayContent = touchedWindow.getDisplayContent();
+ if (!displayContent.isOnTop()) {
+ displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent,
+ true /* includingParents */);
+ }
handleTaskFocusChange(touchedWindow.getTask());
}
@@ -7663,29 +7691,6 @@
}
}
- private void handleDisplayFocusChange(WindowState window) {
- final DisplayContent displayContent = window.getDisplayContent();
- if (displayContent == null) {
- return;
- }
-
- final WindowContainer parent = displayContent.getParent();
- if (parent != null && parent.getTopChild() != displayContent) {
- parent.positionChildAt(WindowContainer.POSITION_TOP, displayContent,
- true /* includingParents */);
- // For compatibility, only the topmost activity is allowed to be resumed for pre-Q
- // app. Ensure the topmost activities are resumed whenever a display is moved to top.
- // TODO(b/123761773): Investigate whether we can move this into
- // RootActivityContainer#updateTopResumedActivityIfNeeded(). Currently, it is risky
- // to do so because it seems possible to resume activities as part of a larger
- // transaction and it's too early to resume based on current order when performing
- // updateTopResumedActivityIfNeeded().
- // TODO(display-merge): Remove cast
- ((ActivityDisplay) displayContent).ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */);
- }
- }
-
/**
* Assigns an InputChannel to a SurfaceControl and configures it to receive
* touch input according to it's on-screen geometry.
@@ -7815,8 +7820,8 @@
@Override
public boolean mirrorDisplay(int displayId, SurfaceControl outSurfaceControl) {
- if (!checkCallingPermission(ACCESS_SURFACE_FLINGER, "mirrorDisplay()")) {
- throw new SecurityException("Requires ACCESS_SURFACE_FLINGER permission");
+ if (!checkCallingPermission(READ_FRAME_BUFFER, "mirrorDisplay()")) {
+ throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
final SurfaceControl displaySc;
@@ -7827,7 +7832,7 @@
return false;
}
- displaySc = displayContent.getSurfaceControl();
+ displaySc = displayContent.getWindowingLayer();
}
final SurfaceControl mirror = SurfaceControl.mirrorSurface(displaySc);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index ddf8e9b..f6dd71b 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -44,6 +44,7 @@
import android.app.IApplicationThread;
import android.app.ProfilerInfo;
import android.app.servertransaction.ConfigurationChangeItem;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -67,6 +68,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* The Activity Manager (AM) package manages the lifecycle of processes in the system through
@@ -575,14 +577,14 @@
return true;
}
- final ActivityDisplay display = activity.getDisplay();
+ final DisplayContent display = activity.getDisplay();
if (display == null) {
// No need to update if the activity hasn't attach to any display.
return false;
}
boolean canUpdate = false;
- final ActivityDisplay topDisplay =
+ final DisplayContent topDisplay =
mPreQTopResumedActivity != null ? mPreQTopResumedActivity.getDisplay() : null;
// Update the topmost activity if current top activity is
// - not on any display OR
@@ -767,6 +769,30 @@
}
}
+ /**
+ * Returns display UI context list which there is any app window shows or starting activities
+ * int this process.
+ */
+ public void getDisplayContextsWithErrorDialogs(List<Context> displayContexts) {
+ if (displayContexts == null) {
+ return;
+ }
+ synchronized (mAtm.mGlobalLock) {
+ final RootWindowContainer root = mAtm.mWindowManager.mRoot;
+ root.getDisplayContextsWithNonToastVisibleWindows(mPid, displayContexts);
+
+ for (int i = mActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord r = mActivities.get(i);
+ final int displayId = r.getDisplayId();
+ final Context c = root.getDisplayUiContext(displayId);
+
+ if (r.mVisibleRequested && !displayContexts.contains(c)) {
+ displayContexts.add(c);
+ }
+ }
+ }
+ }
+
public interface ComputeOomAdjCallback {
void onVisibleActivity();
void onPausedActivity();
@@ -938,15 +964,15 @@
mAtm.mH.sendMessage(m);
}
- void registerDisplayConfigurationListenerLocked(ActivityDisplay activityDisplay) {
- if (activityDisplay == null) {
+ void registerDisplayConfigurationListenerLocked(DisplayContent displayContent) {
+ if (displayContent == null) {
return;
}
// A process can only register to one display to listener to the override configuration
// change. Unregister existing listener if it has one before register the new one.
unregisterDisplayConfigurationListenerLocked();
- mDisplayId = activityDisplay.mDisplayId;
- activityDisplay.registerConfigurationChangeListener(this);
+ mDisplayId = displayContent.mDisplayId;
+ displayContent.registerConfigurationChangeListener(this);
}
@VisibleForTesting
@@ -954,10 +980,10 @@
if (mDisplayId == INVALID_DISPLAY) {
return;
}
- final ActivityDisplay activityDisplay =
- mAtm.mRootActivityContainer.getActivityDisplay(mDisplayId);
- if (activityDisplay != null) {
- activityDisplay.unregisterConfigurationChangeListener(this);
+ final DisplayContent displayContent =
+ mAtm.mRootActivityContainer.getDisplayContent(mDisplayId);
+ if (displayContent != null) {
+ displayContent.unregisterConfigurationChangeListener(this);
}
mDisplayId = INVALID_DISPLAY;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6a1aa02..3039d69 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1239,11 +1239,6 @@
}
@Override
- public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) {
- return getDisplayContent().getNeedsMenu(this, bottom);
- }
-
- @Override
public int getSystemUiVisibility() {
return mSystemUiVisibility;
}
@@ -1647,11 +1642,17 @@
&& (parentAndClientVisible || isAnimating(TRANSITION | PARENTS));
}
- // TODO: Another visibility method that was added late in the release to minimize risk.
- @Override
- public boolean canAffectSystemUiFlags() {
- final boolean translucent = mAttrs.alpha == 0.0f;
- if (translucent) {
+ boolean isFullyTransparent() {
+ return mAttrs.alpha == 0f;
+ }
+
+ /**
+ * @return Whether the window can affect SystemUI flags, meaning that SystemUI (system bars,
+ * for example) will be affected by the flags specified in this window. This is the
+ * case when the surface is on screen but not exiting.
+ */
+ boolean canAffectSystemUiFlags() {
+ if (isFullyTransparent()) {
return false;
}
if (mActivityRecord == null) {
@@ -1731,6 +1732,39 @@
&& isDrawnLw() && !isAnimating(TRANSITION | PARENTS);
}
+ /** @see WindowManagerInternal#waitForAllWindowsDrawn */
+ void requestDrawIfNeeded(List<WindowState> outWaitingForDrawn) {
+ if (!isVisible()) {
+ return;
+ }
+ if (mActivityRecord != null) {
+ if (mActivityRecord.allDrawn) {
+ // The allDrawn of activity is reset when the visibility is changed to visible, so
+ // the content should be ready if allDrawn is set.
+ return;
+ }
+ if (mAttrs.type == TYPE_APPLICATION_STARTING) {
+ if (isDrawnLw()) {
+ // Unnecessary to redraw a drawn starting window.
+ return;
+ }
+ } else if (mActivityRecord.startingWindow != null) {
+ // If the activity has an active starting window, there is no need to wait for the
+ // main window.
+ return;
+ }
+ } else if (!mPolicy.isKeyguardHostWindow(mAttrs)) {
+ return;
+ // Always invalidate keyguard host window to make sure it shows the latest content
+ // because its visibility may not be changed.
+ }
+
+ mWinAnimator.mDrawState = DRAW_PENDING;
+ // Force add to {@link WindowManagerService#mResizingWindows}.
+ resetLastContentInsets();
+ outWaitingForDrawn.add(this);
+ }
+
@Override
void onMovedByResize() {
ProtoLog.d(WM_DEBUG_RESIZE, "onMovedByResize: Moving %s", this);
@@ -1738,28 +1772,22 @@
super.onMovedByResize();
}
- boolean onAppVisibilityChanged(boolean visible, boolean runningAppAnimation) {
- boolean changed = false;
-
+ void onAppVisibilityChanged(boolean visible, boolean runningAppAnimation) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- changed |= c.onAppVisibilityChanged(visible, runningAppAnimation);
+ mChildren.get(i).onAppVisibilityChanged(visible, runningAppAnimation);
}
+ final boolean isVisibleNow = isVisibleNow();
if (mAttrs.type == TYPE_APPLICATION_STARTING) {
// 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(TRANSITION)) {
mAnimatingExit = true;
mRemoveOnExit = true;
mWindowRemovalAllowed = true;
}
- return changed;
- }
-
- final boolean isVisibleNow = isVisibleNow();
- if (visible != isVisibleNow) {
+ } else if (visible != isVisibleNow) {
// Run exit animation if:
// 1. App visibility and WS visibility are different
// 2. App is not running an animation
@@ -1773,11 +1801,8 @@
accessibilityController.onWindowTransitionLocked(this, winTransit);
}
}
- changed = true;
setDisplayLayoutNeeded();
}
-
- return changed;
}
boolean onSetAppExiting() {
@@ -2163,13 +2188,27 @@
return false;
}
- final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
- final int type = mAttrs.type;
+ if (PixelFormat.formatHasAlpha(mAttrs.format)) {
+ // Support legacy use cases where transparent windows can still be ime target with
+ // FLAG_NOT_FOCUSABLE and ALT_FOCUSABLE_IM set.
+ // Certain apps listen for IME insets using transparent windows and ADJUST_NOTHING to
+ // manually synchronize app content to IME animation b/144619551.
+ // TODO(b/145812508): remove this once new focus management is complete b/141738570
+ final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+ final int type = mAttrs.type;
- // Can only be an IME target if both FLAG_NOT_FOCUSABLE and FLAG_ALT_FOCUSABLE_IM are set or
- // both are cleared...and not a starting window.
- if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
- && type != TYPE_APPLICATION_STARTING) {
+ // Can only be an IME target if both FLAG_NOT_FOCUSABLE and FLAG_ALT_FOCUSABLE_IM are
+ // set or both are cleared...and not a starting window.
+ if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
+ && type != TYPE_APPLICATION_STARTING) {
+ return false;
+ }
+ } else if (!WindowManager.LayoutParams.mayUseInputMethod(mAttrs.flags)
+ || mAttrs.type == TYPE_APPLICATION_STARTING) {
+ // Can be an IME target only if:
+ // 1. FLAG_NOT_FOCUSABLE is not set
+ // 2. FLAG_ALT_FOCUSABLE_IM is not set
+ // 3. not a starting window.
return false;
}
@@ -2329,43 +2368,12 @@
final Region region = inputWindowHandle.touchableRegion;
setTouchableRegionCropIfNeeded(inputWindowHandle);
- if (modal && mActivityRecord != null) {
- // Limit the outer touch to the activity stack region.
+ if (modal) {
flags |= FLAG_NOT_TOUCH_MODAL;
- // If the inner bounds of letterbox is available, then it will be used as the touchable
- // region so it won't cover the touchable letterbox and the touch events can slip to
- // activity from letterbox.
- mActivityRecord.getLetterboxInnerBounds(mTmpRect);
- if (mTmpRect.isEmpty()) {
- // If this is a modal window we need to dismiss it if it's not full screen and the
- // touch happens outside of the frame that displays the content. This means we need
- // to intercept touches outside of that window. The dim layer user associated with
- // the window (task or stack) will give us the good bounds, as they would be used to
- // display the dim layer.
- final Task task = getTask();
- if (task != null) {
- task.getDimBounds(mTmpRect);
- } else {
- getStack().getDimBounds(mTmpRect);
- }
- }
- if (inFreeformWindowingMode()) {
- // For freeform windows we the touch region to include the whole surface for the
- // shadows.
- final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
- final int delta = WindowManagerService.dipToPixel(
- RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
- mTmpRect.inset(-delta, -delta);
- }
- region.set(mTmpRect);
- cropRegionToStackBoundsIfNeeded(region);
- subtractTouchExcludeRegionIfNeeded(region);
- } else if (modal && mTapExcludeRegionHolder != null) {
- final Region touchExcludeRegion = Region.obtain();
- amendTapExcludeRegion(touchExcludeRegion);
- if (!touchExcludeRegion.isEmpty()) {
- // Remove touch modal because there are some areas that cannot be touched.
- flags |= FLAG_NOT_TOUCH_MODAL;
+ if (mActivityRecord != null) {
+ // Limit the outer touch to the activity stack region.
+ updateRegionForModalActivityWindow(region);
+ } else {
// Give it a large touchable region at first because it was touch modal. The window
// might be moved on the display, so the touchable region should be large enough to
// ensure it covers the whole display, no matter where it is moved.
@@ -2373,15 +2381,13 @@
final int dw = mTmpRect.width();
final int dh = mTmpRect.height();
region.set(-dw, -dh, dw + dw, dh + dh);
- // Subtract the area that cannot be touched.
- region.op(touchExcludeRegion, Region.Op.DIFFERENCE);
- inputWindowHandle.setTouchableRegionCrop(null);
}
- touchExcludeRegion.recycle();
+ subtractTouchExcludeRegionIfNeeded(region);
} else {
- // Not modal or full screen modal
+ // Not modal
getTouchableRegion(region);
}
+
// Translate to surface based coordinates.
region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
@@ -2397,6 +2403,41 @@
return flags;
}
+ /**
+ * Updates the region for a window in an Activity that was a touch modal. This will limit
+ * the outer touch to the activity stack region.
+ * @param outRegion The region to update.
+ */
+ private void updateRegionForModalActivityWindow(Region outRegion) {
+ // If the inner bounds of letterbox is available, then it will be used as the
+ // touchable region so it won't cover the touchable letterbox and the touch
+ // events can slip to activity from letterbox.
+ mActivityRecord.getLetterboxInnerBounds(mTmpRect);
+ if (mTmpRect.isEmpty()) {
+ // If this is a modal window we need to dismiss it if it's not full screen
+ // and the touch happens outside of the frame that displays the content. This
+ // means we need to intercept touches outside of that window. The dim layer
+ // user associated with the window (task or stack) will give us the good
+ // bounds, as they would be used to display the dim layer.
+ final Task task = getTask();
+ if (task != null) {
+ task.getDimBounds(mTmpRect);
+ } else {
+ getStack().getDimBounds(mTmpRect);
+ }
+ }
+ if (inFreeformWindowingMode()) {
+ // For freeform windows, we need the touch region to include the whole
+ // surface for the shadows.
+ final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
+ final int delta = WindowManagerService.dipToPixel(
+ RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics);
+ mTmpRect.inset(-delta, -delta);
+ }
+ outRegion.set(mTmpRect);
+ cropRegionToStackBoundsIfNeeded(outRegion);
+ }
+
void checkPolicyVisibilityChange() {
if (isLegacyPolicyVisibility() != mLegacyPolicyVisibilityAfterAnim) {
if (DEBUG_VISIBILITY) {
@@ -2625,7 +2666,7 @@
final boolean canReceiveKeys = isVisibleOrAdding()
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
- && (mActivityRecord == null || mActivityRecord.windowsAreFocusable())
+ && (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
&& !cantReceiveTouchInput();
if (!canReceiveKeys) {
return false;
@@ -2652,18 +2693,18 @@
return mActivityRecord.getTask().getTaskStack().shouldIgnoreInput()
|| !mActivityRecord.mVisibleRequested
- || isAnimatingToRecents();
+ || isRecentsAnimationConsumingAppInput();
}
/**
- * Returns {@code true} if the window is animating to home as part of the recents animation.
+ * Returns {@code true} if the window is animating to home as part of the recents animation and
+ * it is consuming input from the app.
*/
- private boolean isAnimatingToRecents() {
+ private boolean isRecentsAnimationConsumingAppInput() {
final RecentsAnimationController recentsAnimationController =
mWmService.getRecentsAnimationController();
return recentsAnimationController != null
- && recentsAnimationController.isAnimatingTask(getTask())
- && !recentsAnimationController.isTargetApp(mActivityRecord);
+ && recentsAnimationController.shouldApplyInputConsumer(mActivityRecord);
}
@Override
@@ -5447,4 +5488,21 @@
getContentInsets(outInsets);
getStableInsets(outStableInsets);
}
+
+ /**
+ * Returns {@code true} if this window is not {@link WindowManager.LayoutParams#TYPE_TOAST}
+ * or {@link WindowManager.LayoutParams#TYPE_APPLICATION_STARTING},
+ * since this window doesn't belong to apps.
+ */
+ boolean isNonToastOrStarting() {
+ return mAttrs.type != TYPE_TOAST && mAttrs.type != TYPE_APPLICATION_STARTING;
+ }
+
+ boolean isNonToastWindowVisibleForUid(int callingUid) {
+ return getOwningUid() == callingUid && isNonToastOrStarting() && isVisibleNow();
+ }
+
+ boolean isNonToastWindowVisibleForPid(int pid) {
+ return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow();
+ }
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fd8094c..fee29db 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -35,6 +35,7 @@
"com_android_server_power_PowerManagerService.cpp",
"com_android_server_security_VerityUtils.cpp",
"com_android_server_SerialService.cpp",
+ "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
"com_android_server_storage_AppFuseBridge.cpp",
"com_android_server_SystemServer.cpp",
"com_android_server_TestNetworkService.cpp",
@@ -51,6 +52,7 @@
"com_android_server_GraphicsStatsService.cpp",
"com_android_server_am_AppCompactor.cpp",
"com_android_server_am_LowMemDetector.cpp",
+ "com_android_server_incremental_IncrementalManagerService.cpp",
"onload.cpp",
":lib_networkStatsFactory_native",
],
@@ -144,6 +146,7 @@
"android.frameworks.schedulerservice@1.0",
"android.frameworks.sensorservice@1.0",
"android.system.suspend@1.0",
+ "service.incremental",
"suspend_control_aidl_interface-cpp",
"vintf-vibrator-cpp",
],
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index dcff5a1..6811e6d 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -410,6 +410,21 @@
return 0;
}
+static void vibratorAlwaysOnEnable(JNIEnv* env, jclass, jlong id, jlong effect, jlong strength) {
+ auto status = halCall(&aidl::IVibrator::alwaysOnEnable, id,
+ static_cast<aidl::Effect>(effect), static_cast<aidl::EffectStrength>(strength));
+ if (!status.isOk()) {
+ ALOGE("vibratortAlwaysOnEnable command failed (%s).", status.toString8().string());
+ }
+}
+
+static void vibratorAlwaysOnDisable(JNIEnv* env, jclass, jlong id) {
+ auto status = halCall(&aidl::IVibrator::alwaysOnDisable, id);
+ if (!status.isOk()) {
+ ALOGE("vibratorAlwaysOnDisable command failed (%s).", status.toString8().string());
+ }
+}
+
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorInit", "()V", (void*)vibratorInit },
@@ -422,6 +437,8 @@
{ "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
{ "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
{ "vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities},
+ { "vibratorAlwaysOnEnable", "(JJJ)V", (void*)vibratorAlwaysOnEnable},
+ { "vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable},
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp b/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp
new file mode 100644
index 0000000..5e255f4
--- /dev/null
+++ b/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "incremental_manager_service-jni"
+
+#include "incremental_service.h"
+#include "jni.h"
+
+#include <memory>
+#include <nativehelper/JNIHelp.h>
+
+
+namespace android {
+
+static jlong nativeStartService(JNIEnv* env, jclass klass, jobject self) {
+ return Incremental_IncrementalService_Start();
+}
+
+static void nativeSystemReady(JNIEnv* env, jclass klass, jlong self) {
+ Incremental_IncrementalService_OnSystemReady(self);
+}
+
+static const JNINativeMethod method_table[] = {
+ {"nativeStartService", "()J", (void*)nativeStartService},
+ {"nativeSystemReady", "(J)V", (void*)nativeSystemReady},
+};
+
+int register_android_server_incremental_IncrementalManagerService(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "com/android/server/incremental/IncrementalManagerService",
+ method_table, std::size(method_table));
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 9344a9b..2e8e5e7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -485,8 +485,8 @@
// Received data: ['inputPort1', '1', 'inputPort2', '2']
// So we unpack accordingly here.
outConfig->portAssociations.clear();
- jobjectArray portAssociations = jobjectArray(env->CallStaticObjectMethod(
- gServiceClassInfo.clazz, gServiceClassInfo.getInputPortAssociations));
+ jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj,
+ gServiceClassInfo.getInputPortAssociations));
if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
jsize length = env->GetArrayLength(portAssociations);
for (jsize i = 0; i < length / 2; i++) {
@@ -1920,7 +1920,7 @@
GET_STATIC_METHOD_ID(gServiceClassInfo.getExcludedDeviceNames, clazz,
"getExcludedDeviceNames", "()[Ljava/lang/String;");
- GET_STATIC_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz,
+ GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz,
"getInputPortAssociations", "()[Ljava/lang/String;");
GET_METHOD_ID(gServiceClassInfo.getKeyRepeatTimeout, clazz,
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 906b568..be11b86 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -24,6 +24,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <linux/fsverity.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
@@ -33,40 +34,6 @@
#include <android-base/unique_fd.h>
-// TODO(112037636): Always include once fsverity.h is upstreamed.
-#if __has_include(<linux/fsverity.h>)
-#include <linux/fsverity.h>
-#else
-
-#include <linux/limits.h>
-#include <linux/ioctl.h>
-#include <linux/types.h>
-
-#define FS_VERITY_HASH_ALG_SHA256 1
-
-struct fsverity_enable_arg {
- __u32 version;
- __u32 hash_algorithm;
- __u32 block_size;
- __u32 salt_size;
- __u64 salt_ptr;
- __u32 sig_size;
- __u32 __reserved1;
- __u64 sig_ptr;
- __u64 __reserved2[11];
-};
-
-struct fsverity_digest {
- __u16 digest_algorithm;
- __u16 digest_size; /* input/output */
- __u8 digest[];
-};
-
-#define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg)
-#define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest)
-
-#endif
-
const int kSha256Bytes = 32;
namespace android {
diff --git a/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
new file mode 100644
index 0000000..774534f
--- /dev/null
+++ b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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 <sstream>
+
+#include "core_jni_helpers.h"
+#include <media/AudioSystem.h>
+
+namespace android {
+
+namespace {
+
+#define PACKAGE "com/android/server/soundtrigger_middleware"
+#define CLASSNAME PACKAGE "/AudioSessionProviderImpl"
+#define SESSION_CLASSNAME PACKAGE "/SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession"
+
+jobject acquireAudioSession(
+ JNIEnv* env,
+ jobject clazz) {
+
+ audio_session_t session;
+ audio_io_handle_t ioHandle;
+ audio_devices_t device;
+
+ status_t status = AudioSystem::acquireSoundTriggerSession(&session,
+ &ioHandle,
+ &device);
+ if (status != 0) {
+ std::ostringstream message;
+ message
+ << "AudioSystem::acquireSoundTriggerSession returned an error code: "
+ << status;
+ env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+ message.str().c_str());
+ return nullptr;
+ }
+
+ jclass cls = FindClassOrDie(env, SESSION_CLASSNAME);
+ jmethodID ctor = GetMethodIDOrDie(env, cls, "<init>", "(III)V");
+ return env->NewObject(cls,
+ ctor,
+ static_cast<int>(session),
+ static_cast<int>(ioHandle),
+ static_cast<int>(device));
+}
+
+void releaseAudioSession(JNIEnv* env, jobject clazz, jint handle) {
+ status_t status =
+ AudioSystem::releaseSoundTriggerSession(static_cast<audio_session_t>(handle));
+
+ if (status != 0) {
+ std::ostringstream message;
+ message
+ << "AudioSystem::releaseAudioSystemSession returned an error code: "
+ << status;
+ env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+ message.str().c_str());
+ }
+}
+
+const JNINativeMethod g_methods[] = {
+ {"acquireSession", "()L" SESSION_CLASSNAME ";",
+ reinterpret_cast<void*>(acquireAudioSession)},
+ {"releaseSession", "(I)V",
+ reinterpret_cast<void*>(releaseAudioSession)},
+};
+
+} // namespace
+
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ JNIEnv* env) {
+ return RegisterMethodsOrDie(env,
+ CLASSNAME,
+ g_methods,
+ NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 692c9d2..c0a6e4e 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,9 @@
int register_android_server_security_VerityUtils(JNIEnv* env);
int register_android_server_am_AppCompactor(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ JNIEnv* env);
+int register_android_server_incremental_IncrementalManagerService(JNIEnv* env);
};
using namespace android;
@@ -105,5 +108,8 @@
register_android_server_security_VerityUtils(env);
register_android_server_am_AppCompactor(env);
register_android_server_am_LowMemDetector(env);
+ register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ env);
+ register_android_server_incremental_IncrementalManagerService(env);
return JNI_VERSION_1_4;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ee0449d..1536816 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -84,6 +84,7 @@
import static android.provider.Telephony.Carriers.DPC_URI;
import static android.provider.Telephony.Carriers.ENFORCE_KEY;
import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
+import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -254,6 +255,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.Preconditions;
@@ -430,6 +432,8 @@
private static final Set<String> GLOBAL_SETTINGS_DEPRECATED;
private static final Set<String> SYSTEM_SETTINGS_WHITELIST;
private static final Set<Integer> DA_DISALLOWED_POLICIES;
+ // A collection of user restrictions that are deprecated and should simply be ignored.
+ private static final Set<String> DEPRECATED_USER_RESTRICTIONS;
private static final String AB_DEVICE_KEY = "ro.build.ab_update";
static {
@@ -471,6 +475,10 @@
DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES);
DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD);
DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+
+ DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet(
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
}
/**
@@ -990,6 +998,7 @@
"cross-profile-calendar-packages";
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";
DeviceAdminInfo info;
@@ -1104,6 +1113,11 @@
// is whitelisted.
List<String> mCrossProfileCalendarPackages = Collections.emptyList();
+ // The whitelist of packages that the admin has enabled to be able to request consent from
+ // the user to communicate cross-profile. By default, no packages are whitelisted, which is
+ // represented as an empty list.
+ List<String> mCrossProfilePackages = Collections.emptyList();
+
ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
info = _info;
isParent = parent;
@@ -1329,6 +1343,7 @@
writePackageListToXml(out, TAG_CROSS_PROFILE_CALENDAR_PACKAGES,
mCrossProfileCalendarPackages);
}
+ writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
}
void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -1560,6 +1575,8 @@
mCrossProfileCalendarPackages = readPackageList(parser, tag);
} else if (TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL.equals(tag)) {
mCrossProfileCalendarPackages = null;
+ } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
+ mCrossProfilePackages = readPackageList(parser, tag);
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1783,6 +1800,8 @@
pw.print("mCrossProfileCalendarPackages=");
pw.println(mCrossProfileCalendarPackages);
}
+ pw.print("mCrossProfilePackages=");
+ pw.println(mCrossProfilePackages);
}
}
@@ -2088,6 +2107,10 @@
Binder.withCleanCallingIdentity(action);
}
+ final <T> T binderWithCleanCallingIdentity(@NonNull ThrowingSupplier<T> action) {
+ return Binder.withCleanCallingIdentity(action);
+ }
+
final int userHandleGetCallingUserId() {
return UserHandle.getUserId(binderGetCallingUid());
}
@@ -2352,12 +2375,7 @@
* @return
*/
DevicePolicyData getUserDataUnchecked(int userHandle) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- return getUserData(userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ return mInjector.binderWithCleanCallingIdentity(() -> getUserData(userHandle));
}
void removeUserData(int userHandle) {
@@ -2389,7 +2407,6 @@
setDeviceOwnerSystemPropertyLocked();
findOwnerComponentIfNecessaryLocked();
migrateUserRestrictionsIfNecessaryLocked();
- maybeSetDefaultDeviceOwnerUserRestrictionsLocked();
// TODO PO may not have a class name either due to b/17652534. Address that too.
@@ -2397,15 +2414,6 @@
}
}
- /** Apply default restrictions that haven't been applied to device owners yet. */
- private void maybeSetDefaultDeviceOwnerUserRestrictionsLocked() {
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- if (deviceOwner != null) {
- maybeSetDefaultRestrictionsForAdminLocked(mOwners.getDeviceOwnerUserId(),
- deviceOwner, UserRestrictionsUtils.getDefaultEnabledForDeviceOwner());
- }
- }
-
/** Apply default restrictions that haven't been applied to profile owners yet. */
private void maybeSetDefaultProfileOwnerUserRestrictions() {
synchronized (getLockObject()) {
@@ -2436,7 +2444,7 @@
Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId) != 0) {
profileOwner.ensureUserRestrictions().putBoolean(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
- saveUserRestrictionsLocked(userId);
+ saveUserRestrictionsLocked(userId, /* parent = */ false);
mInjector.settingsSecurePutIntForUser(
Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId);
}
@@ -2467,7 +2475,7 @@
}
admin.defaultEnabledRestrictionsAlreadySet.addAll(restrictionsToSet);
Slog.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictionsToSet);
- saveUserRestrictionsLocked(userId);
+ saveUserRestrictionsLocked(userId, /* parent = */ false);
}
}
@@ -2684,8 +2692,7 @@
alarmTime = now + alarmInterval;
}
- long token = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
int affectedUserHandle = parent ? getProfileParentId(userHandle) : userHandle;
AlarmManager am = mInjector.getAlarmManager();
PendingIntent pi = PendingIntent.getBroadcastAsUser(context, REQUEST_EXPIRE_PASSWORD,
@@ -2696,9 +2703,7 @@
if (alarmTime != 0) {
am.set(AlarmManager.RTC, alarmTime, pi);
}
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ });
}
ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who, int userHandle) {
@@ -3262,12 +3267,8 @@
private void sendChangedNotification(int userHandle) {
Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- long ident = mInjector.binderClearCallingIdentity();
- try {
- mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle));
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle)));
}
private void loadSettingsLocked(DevicePolicyData policy, int userHandle) {
@@ -3835,8 +3836,7 @@
/* throwForMissingPermission= */ true);
synchronized (getLockObject()) {
checkActiveAdminPrecondition(adminReceiver, info, policy);
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
final ActiveAdmin existingAdmin
= getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (!refreshing && existingAdmin != null) {
@@ -3867,9 +3867,7 @@
saveSettingsLocked(userHandle);
sendAdminCommandLocked(newAdmin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
onEnableData, null);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
}
@@ -4051,8 +4049,7 @@
}
Preconditions.checkNotNull(adminReceiver, "ComponentName is null");
enforceShell("forceRemoveActiveAdmin");
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
synchronized (getLockObject()) {
if (!isAdminTestOnlyLocked(adminReceiver, userHandle)) {
throw new SecurityException("Attempt to remove non-test admin "
@@ -4072,9 +4069,7 @@
// Remove the admin skipping sending the broadcast.
removeAdminArtifacts(adminReceiver, userHandle);
Slog.i(LOG_TAG, "Admin " + adminReceiver + " removed from user " + userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
private void clearDeviceOwnerUserRestrictionLocked(UserHandle userHandle) {
@@ -4149,12 +4144,8 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_DEVICE_ADMINS, null);
}
- long ident = mInjector.binderClearCallingIdentity();
- try {
- removeActiveAdminLocked(adminReceiver, userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ mInjector.binderWithCleanCallingIdentity(() ->
+ removeActiveAdminLocked(adminReceiver, userHandle));
}
}
@@ -4180,8 +4171,7 @@
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
if (passwordPolicy.quality != quality) {
passwordPolicy.quality = quality;
@@ -4191,9 +4181,7 @@
saveSettingsLocked(userId);
}
maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_QUALITY)
@@ -4380,12 +4368,8 @@
}
private boolean isSeparateProfileChallengeEnabled(int userHandle) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- return mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle));
}
@Override
@@ -5122,12 +5106,7 @@
}
private UserInfo getUserInfo(@UserIdInt int userId) {
- final long token = mInjector.binderClearCallingIdentity();
- try {
- return mUserManager.getUserInfo(userId);
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ return mInjector.binderWithCleanCallingIdentity(() -> mUserManager.getUserInfo(userId));
}
private boolean setPasswordPrivileged(@NonNull String password, int flags, int callingUid) {
@@ -5252,12 +5231,7 @@
}
private boolean isLockScreenSecureUnchecked(int userId) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- return mLockPatternUtils.isSecure(userId);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ return mInjector.binderWithCleanCallingIdentity(() -> mLockPatternUtils.isSecure(userId));
}
private void setDoNotAskCredentialsOnBoot() {
@@ -5309,12 +5283,10 @@
updateProfileLockTimeoutLocked(userId);
}
- final long timeMs;
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
// Update the device timeout
final int parentId = getProfileParentId(userId);
- timeMs = getMaximumTimeToLockPolicyFromAdmins(
+ final long timeMs = getMaximumTimeToLockPolicyFromAdmins(
getActiveAdminsForLockscreenPoliciesLocked(parentId, false));
final DevicePolicyData policy = getUserDataUnchecked(parentId);
@@ -5330,9 +5302,7 @@
}
getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
UserHandle.USER_SYSTEM, timeMs);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
private void updateProfileLockTimeoutLocked(@UserIdInt int userId) {
@@ -5350,13 +5320,9 @@
}
policy.mLastMaximumTimeToLock = timeMs;
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
- userId, policy.mLastMaximumTimeToLock);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ mInjector.binderWithCleanCallingIdentity(() ->
+ getPowerManagerInternal().setMaximumScreenOffTimeoutFromDeviceAdmin(
+ userId, policy.mLastMaximumTimeToLock));
}
@Override
@@ -5624,24 +5590,21 @@
}
enforceCanManageCaCerts(admin, callerPackage);
- final String alias;
-
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
- final long id = mInjector.binderClearCallingIdentity();
- try {
- alias = mCertificateMonitor.installCaCert(userHandle, certBuffer);
+ final String alias = mInjector.binderWithCleanCallingIdentity(() -> {
+ String installedAlias = mCertificateMonitor.installCaCert(userHandle, certBuffer);
final boolean isDelegate = (admin == null);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.INSTALL_CA_CERT)
.setAdmin(callerPackage)
.setBoolean(isDelegate)
.write();
- if (alias == null) {
- Log.w(LOG_TAG, "Problem installing cert");
- return false;
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
+ return installedAlias;
+ });
+
+ if (alias == null) {
+ Log.w(LOG_TAG, "Problem installing cert");
+ return false;
}
synchronized (getLockObject()) {
@@ -5659,8 +5622,7 @@
enforceCanManageCaCerts(admin, callerPackage);
final int userId = mInjector.userHandleGetCallingUserId();
- final long id = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases);
final boolean isDelegate = (admin == null);
DevicePolicyEventLogger
@@ -5668,9 +5630,7 @@
.setAdmin(callerPackage)
.setBoolean(isDelegate)
.write();
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
synchronized (getLockObject()) {
if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
@@ -5866,7 +5826,7 @@
idTypeToAttestationFlag.put(ID_TYPE_IMEI, AttestationUtils.ID_TYPE_IMEI);
idTypeToAttestationFlag.put(ID_TYPE_MEID, AttestationUtils.ID_TYPE_MEID);
idTypeToAttestationFlag.put(
- ID_TYPE_INDIVIDUAL_ATTESTATION, AttestationUtils.USE_INDIVIDUAL_ATTESTATION);
+ ID_TYPE_INDIVIDUAL_ATTESTATION, USE_INDIVIDUAL_ATTESTATION);
int numFlagsSet = Integer.bitCount(idAttestationFlags);
// No flags are set - return null to indicate no device ID attestation information should
@@ -5906,6 +5866,7 @@
if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) {
enforceCallerCanRequestDeviceIdAttestation(who, callerPackage, callingUid);
+ enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
} else {
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_CERT_INSTALL);
@@ -6000,6 +5961,17 @@
return false;
}
+ private void enforceIndividualAttestationSupportedIfRequested(int[] attestationUtilsFlags) {
+ for (int attestationFlag : attestationUtilsFlags) {
+ if (attestationFlag == USE_INDIVIDUAL_ATTESTATION
+ && !mInjector.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_DEVICE_UNIQUE_ATTESTATION)) {
+ throw new UnsupportedOperationException("Device Individual attestation is not "
+ + "supported on this device.");
+ }
+ }
+ }
+
@Override
public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
byte[] cert, byte[] chain, boolean isUserSelectable) {
@@ -6074,8 +6046,7 @@
isDelegate = false;
}
- final long id = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -6088,9 +6059,7 @@
.setAdmin(intent.getComponent())
.setBoolean(isDelegate)
.write();
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
private void sendPrivateKeyAliasResponse(final String alias, final IBinder responseBinder) {
@@ -6506,8 +6475,7 @@
enforceProfileOrDeviceOwner(admin);
final int userId = mInjector.userHandleGetCallingUserId();
- final long token = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
if (vpnPackage != null && !isPackageInstalledForUser(vpnPackage, userId)) {
Slog.w(LOG_TAG, "Non-existent VPN package specified: " + vpnPackage);
throw new ServiceSpecificException(
@@ -6535,9 +6503,7 @@
.setBoolean(lockdown)
.setInt(lockdownWhitelist != null ? lockdownWhitelist.size() : 0)
.write();
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ });
return true;
}
@@ -6546,12 +6512,8 @@
enforceProfileOrDeviceOwner(admin);
final int userId = mInjector.userHandleGetCallingUserId();
- final long token = mInjector.binderClearCallingIdentity();
- try {
- return mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId);
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ return mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId));
}
@Override
@@ -6559,12 +6521,8 @@
enforceProfileOrDeviceOwner(admin);
final int userId = mInjector.userHandleGetCallingUserId();
- final long token = mInjector.binderClearCallingIdentity();
- try {
- return mInjector.getConnectivityManager().isVpnLockdownEnabled(userId);
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ return mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.getConnectivityManager().isVpnLockdownEnabled(userId));
}
@Override
@@ -6573,12 +6531,8 @@
enforceProfileOrDeviceOwner(admin);
final int userId = mInjector.userHandleGetCallingUserId();
- final long token = mInjector.binderClearCallingIdentity();
- try {
- return mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId);
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ return mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId));
}
private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc) {
@@ -6675,8 +6629,7 @@
// control over the device, wiping only the work profile. So the user restriction
// on profile removal needs to be removed first.
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
// Clear restriction as user.
mUserManager.setUserRestriction(
UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, false,
@@ -6684,9 +6637,7 @@
// Device-wide policies set by the profile owner need to be cleaned up here.
mLockPatternUtils.setDeviceOwnerInfo(null);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
}
@@ -6708,8 +6659,7 @@
String wipeReasonForUser, int userId) {
wtfIfInLock();
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
// First check whether the admin is allowed to wipe the device/user/profile.
final String restriction;
if (userId == UserHandle.USER_SYSTEM) {
@@ -6743,9 +6693,7 @@
} else {
forceWipeUser(userId, wipeReasonForUser, (flags & WIPE_SILENTLY) != 0);
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
private void sendWipeProfileNotification(String wipeReasonForUser) {
@@ -6920,8 +6868,7 @@
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(userHandle);
if (policy.mFailedPasswordAttempts != 0 || policy.mPasswordOwner >= 0) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
policy.mFailedPasswordAttempts = 0;
policy.mPasswordOwner = -1;
saveSettingsLocked(userHandle);
@@ -6930,9 +6877,7 @@
DeviceAdminReceiver.ACTION_PASSWORD_SUCCEEDED,
DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle);
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
}
@@ -7031,12 +6976,7 @@
// Reset the global proxy accordingly
// Do this using system permissions, as apps cannot write to secure settings
- long origId = mInjector.binderClearCallingIdentity();
- try {
- resetGlobalProxyLocked(policy);
- } finally {
- mInjector.binderRestoreCallingIdentity(origId);
- }
+ mInjector.binderWithCleanCallingIdentity(() -> resetGlobalProxyLocked(policy));
return null;
}
}
@@ -7068,12 +7008,8 @@
@Override
public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) {
enforceDeviceOwner(who);
- long token = mInjector.binderClearCallingIdentity();
- try {
- mInjector.getConnectivityManager().setGlobalProxy(proxyInfo);
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
}
private void resetGlobalProxyLocked(DevicePolicyData policy) {
@@ -7365,12 +7301,9 @@
// TODO: (b/145604635) Add upgrade case
// Turn AUTO_TIME on in settings if it is required
if (required) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, 1 /* AUTO_TIME on */);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME,
+ 1 /* AUTO_TIME on */));
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_AUTO_TIME_REQUIRED)
@@ -7429,7 +7362,7 @@
}
/**
- * Returns whether or auto time is used on the device or not.
+ * Returns whether auto time is used on the device or not.
*/
@Override
public boolean getAutoTime(ComponentName who) {
@@ -7442,6 +7375,42 @@
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
+ /**
+ * Set whether auto time zone is enabled on the device.
+ */
+ @Override
+ public void setAutoTimeZone(ComponentName who, boolean enabled) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ // TODO (b/145286957) Refactor security checks
+ enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
+ .setAdmin(who)
+ .setBoolean(enabled)
+ .write();
+ }
+
+ /**
+ * Returns whether auto time zone is used on the device or not.
+ */
+ @Override
+ public boolean getAutoTimeZone(ComponentName who) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+
+ return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
+ }
+
@Override
public void setForceEphemeralUsers(ComponentName who, boolean forceEphemeralUsers) {
if (!mHasFeature) {
@@ -7465,12 +7434,7 @@
}
}
if (removeAllUsers) {
- long identitity = mInjector.binderClearCallingIdentity();
- try {
- mUserManagerInternal.removeAllUsers();
- } finally {
- mInjector.binderRestoreCallingIdentity(identitity);
- }
+ mInjector.binderWithCleanCallingIdentity(() -> mUserManagerInternal.removeAllUsers());
}
}
@@ -7983,25 +7947,10 @@
updateDeviceOwnerLocked();
setDeviceOwnerSystemPropertyLocked();
- final Set<String> restrictions =
- UserRestrictionsUtils.getDefaultEnabledForDeviceOwner();
- if (!restrictions.isEmpty()) {
- for (String restriction : restrictions) {
- activeAdmin.ensureUserRestrictions().putBoolean(restriction, true);
- }
- activeAdmin.defaultEnabledRestrictionsAlreadySet.addAll(restrictions);
- Slog.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictions);
-
- saveUserRestrictionsLocked(userId);
- }
-
- long ident = mInjector.binderClearCallingIdentity();
- try {
- // TODO Send to system too?
- sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ // TODO Send to system too?
+ mInjector.binderWithCleanCallingIdentity(
+ () -> sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED,
+ userId));
mDeviceAdminServiceController.startServiceForOwner(
admin.getPackageName(), userId, "set-device-owner");
@@ -8182,15 +8131,12 @@
}
final ActiveAdmin admin = getDeviceOwnerAdminLocked();
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
clearDeviceOwnerLocked(admin, deviceOwnerUserId);
removeActiveAdminLocked(deviceOwnerComponent, deviceOwnerUserId);
sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED,
deviceOwnerUserId);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
Slog.i(LOG_TAG, "Device owner removed: " + deviceOwnerComponent);
}
}
@@ -8276,8 +8222,7 @@
mOwners.writeProfileOwner(userHandle);
Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
- final long id = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
if (mUserManager.isManagedProfile(userHandle)) {
maybeSetDefaultRestrictionsForAdminLocked(userHandle, admin,
UserRestrictionsUtils.getDefaultEnabledForManagedProfiles());
@@ -8286,9 +8231,7 @@
}
sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED,
userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
mDeviceAdminServiceController.startServiceForOwner(
who.getPackageName(), userHandle, "set-profile-owner");
return true;
@@ -8326,15 +8269,12 @@
final ActiveAdmin admin =
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
clearProfileOwnerLocked(admin, userId);
removeActiveAdminLocked(who, userId);
sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED,
userId);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
Slog.i(LOG_TAG, "Profile owner " + who + " removed from user " + userId);
}
}
@@ -8543,8 +8483,7 @@
"setProfileEnabled is called when the profile is already enabled");
return;
}
- long id = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setUserEnabled(userId);
UserInfo parent = mUserManager.getProfileParent(userId);
Intent intent = new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED);
@@ -8552,9 +8491,7 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY |
Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, new UserHandle(parent.id));
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
}
@@ -8564,16 +8501,13 @@
enforceProfileOrDeviceOwner(who);
final int userId = UserHandle.getCallingUserId();
- final long id = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setUserName(userId, profileName);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PROFILE_NAME)
.setAdmin(who)
.write();
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
@Override
@@ -8615,8 +8549,7 @@
@GuardedBy("getLockObject()")
ActiveAdmin getProfileOwnerOfOrganizationOwnedDeviceLocked(int userHandle) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
if (userInfo.isManagedProfile()) {
if (getProfileOwner(userInfo.id) != null
@@ -8626,10 +8559,8 @@
}
}
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
- return null;
+ return null;
+ });
}
@Override
@@ -8670,9 +8601,15 @@
// Allow access to the profile owner for the specified user, or delegate cert installer
// But only if this is an organization-owned device.
ComponentName profileOwner = getProfileOwnerAsUser(userId);
- if (profileOwner != null && canProfileOwnerAccessDeviceIds(userId)
+ final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
&& (profileOwner.getPackageName().equals(packageName)
- || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
+ || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
+ if (isCallerProfileOwnerOrDelegate && canProfileOwnerAccessDeviceIds(userId)) {
+ return true;
+ }
+ //TODO(b/130844684): Temporarily allow profile owner on non-organization-owned devices
+ //to read device identifiers.
+ if (isCallerProfileOwnerOrDelegate) {
return true;
}
@@ -8726,8 +8663,7 @@
* Canonical name for a given package.
*/
private String getApplicationLabel(String packageName, int userHandle) {
- long token = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
final Context userContext;
try {
UserHandle handle = new UserHandle(userHandle);
@@ -8742,9 +8678,7 @@
result = appInfo.loadUnsafeLabel(userContext.getPackageManager());
}
return result != null ? result.toString() : null;
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ });
}
/**
@@ -9035,28 +8969,23 @@
}
protected int getProfileParentId(int userHandle) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
UserInfo parentUser = mUserManager.getProfileParent(userHandle);
return parentUser != null ? parentUser.id : userHandle;
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
- private int getCredentialOwner(int userHandle, boolean parent) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ private int getCredentialOwner(final int userHandle, final boolean parent) {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ int effectiveUserHandle = userHandle;
if (parent) {
UserInfo parentProfile = mUserManager.getProfileParent(userHandle);
if (parentProfile != null) {
- userHandle = parentProfile.id;
+ effectiveUserHandle = parentProfile.id;
}
}
- return mUserManager.getCredentialOwnerProfile(userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ return mUserManager.getCredentialOwnerProfile(effectiveUserHandle);
+ });
}
private boolean isManagedProfile(int userHandle) {
@@ -9243,8 +9172,7 @@
DELEGATION_APP_RESTRICTIONS);
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
- final long id = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setApplicationRestrictions(packageName, settings, userHandle);
final boolean isDelegate = (who == null);
DevicePolicyEventLogger
@@ -9253,9 +9181,7 @@
.setBoolean(isDelegate)
.setStrings(packageName)
.write();
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
@Override
@@ -9998,8 +9924,7 @@
enforceDeviceOwner(who);
final int callingUserId = mInjector.userHandleGetCallingUserId();
- final long id = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
String restriction = isManagedProfile(userHandle.getIdentifier())
? UserManager.DISALLOW_REMOVE_MANAGED_PROFILE
: UserManager.DISALLOW_REMOVE_USER;
@@ -10009,9 +9934,7 @@
return false;
}
return mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle.getIdentifier());
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
private boolean isAdminAffectedByRestriction(
@@ -10157,8 +10080,7 @@
Preconditions.checkNotNull(who, "ComponentName is null");
enforceDeviceOwner(who);
- final long id = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
final List<UserInfo> userInfos = mInjector.getUserManager().getUsers(true
/*excludeDying*/);
final List<UserHandle> userHandles = new ArrayList<>();
@@ -10169,9 +10091,7 @@
}
}
return userHandles;
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
@Override
@@ -10180,12 +10100,8 @@
enforceProfileOrDeviceOwner(who);
final int callingUserId = mInjector.userHandleGetCallingUserId();
- final long id = mInjector.binderClearCallingIdentity();
- try {
- return mInjector.getUserManager().isUserEphemeral(callingUserId);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ return mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.getUserManager().isUserEphemeral(callingUserId));
}
@Override
@@ -10195,15 +10111,12 @@
DELEGATION_APP_RESTRICTIONS);
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
- final long id = mInjector.binderClearCallingIdentity();
- try {
- Bundle bundle = mUserManager.getApplicationRestrictions(packageName, userHandle);
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ Bundle bundle = mUserManager.getApplicationRestrictions(packageName, userHandle);
// if no restrictions were saved, mUserManager.getApplicationRestrictions
// returns null, but DPM method should return an empty Bundle as per JavaDoc
return bundle != null ? bundle : Bundle.EMPTY;
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
@Override
@@ -10263,24 +10176,33 @@
}
@Override
- public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
+ public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner,
+ boolean parent) {
Preconditions.checkNotNull(who, "ComponentName is null");
if (!UserRestrictionsUtils.isValidRestriction(key)) {
return;
}
- final int userHandle = mInjector.userHandleGetCallingUserId();
+ int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
final ActiveAdmin activeAdmin =
getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+
if (isDeviceOwner) {
if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
throw new SecurityException("Device owner cannot set user restriction " + key);
}
- } else { // profile owner
- if (!UserRestrictionsUtils.canProfileOwnerChange(key, userHandle)) {
+ if (parent) {
+ throw new IllegalArgumentException(
+ "Cannot use the parent instance in Device Owner mode");
+ }
+ } else {
+ if (!(UserRestrictionsUtils.canProfileOwnerChange(key, userHandle) || (
+ isProfileOwnerOfOrganizationOwnedDevice(activeAdmin) && parent
+ && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
+ key)))) {
throw new SecurityException("Profile owner cannot set user restriction " + key);
}
}
@@ -10292,7 +10214,7 @@
} else {
restrictions.remove(key);
}
- saveUserRestrictionsLocked(userHandle);
+ saveUserRestrictionsLocked(userHandle, parent);
}
final int eventId = enabledFromThisOwner
? DevicePolicyEnums.ADD_USER_RESTRICTION
@@ -10310,9 +10232,9 @@
}
}
- private void saveUserRestrictionsLocked(int userId) {
+ private void saveUserRestrictionsLocked(int userId, boolean parent) {
saveSettingsLocked(userId);
- pushUserRestrictions(userId);
+ pushUserRestrictions(parent ? getProfileParentId(userId) : userId);
sendChangedNotification(userId);
}
@@ -10321,15 +10243,17 @@
final boolean isDeviceOwner = mOwners.isDeviceOwnerUserId(userId);
Bundle userRestrictions = null;
final int restrictionOwnerType;
+ final int originatingUserId;
if (isDeviceOwner) {
final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
if (deviceOwner == null) {
return; // Shouldn't happen.
}
- userRestrictions = deviceOwner.userRestrictions;
- addOrRemoveDisableCameraRestriction(userRestrictions, deviceOwner);
+ userRestrictions = addOrRemoveDisableCameraRestriction(
+ deviceOwner.userRestrictions, deviceOwner);
restrictionOwnerType = UserManagerInternal.OWNER_TYPE_DEVICE_OWNER;
+ originatingUserId = deviceOwner.getUserHandle().getIdentifier();
} else {
final ActiveAdmin profileOwnerOfOrganizationOwnedDevice =
getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
@@ -10344,21 +10268,29 @@
userRestrictions = parent.userRestrictions;
userRestrictions = addOrRemoveDisableCameraRestriction(userRestrictions,
parent);
+ originatingUserId =
+ profileOwnerOfOrganizationOwnedDevice.getUserHandle().getIdentifier();
} else {
final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
if (profileOwner != null) {
userRestrictions = profileOwner.userRestrictions;
restrictionOwnerType = UserManagerInternal.OWNER_TYPE_PROFILE_OWNER;
+ originatingUserId = profileOwner.getUserHandle().getIdentifier();
} else {
restrictionOwnerType = UserManagerInternal.OWNER_TYPE_NO_OWNER;
+ originatingUserId = userId;
}
userRestrictions = addOrRemoveDisableCameraRestriction(
userRestrictions, userId);
}
}
- mUserManagerInternal.setDevicePolicyUserRestrictions(userId, userRestrictions,
- restrictionOwnerType);
+ // Remove deprecated restrictions.
+ for (String deprecatedRestriction: DEPRECATED_USER_RESTRICTIONS) {
+ userRestrictions.remove(deprecatedRestriction);
+ }
+ mUserManagerInternal.setDevicePolicyUserRestrictions(originatingUserId,
+ userRestrictions, restrictionOwnerType);
}
}
@@ -10388,14 +10320,18 @@
}
@Override
- public Bundle getUserRestrictions(ComponentName who) {
+ public Bundle getUserRestrictions(ComponentName who, boolean parent) {
if (!mHasFeature) {
return null;
}
Preconditions.checkNotNull(who, "ComponentName is null");
+
synchronized (getLockObject()) {
final ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+ if (parent) {
+ enforceProfileOwnerOfOrganizationOwnedDevice(activeAdmin);
+ }
return activeAdmin.userRestrictions;
}
}
@@ -10805,8 +10741,7 @@
actualContactId, isContactIdIgnored, actualDirectoryId, originalIntent);
final int callingUserId = UserHandle.getCallingUserId();
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
synchronized (getLockObject()) {
final int managedUserId = getManagedUserId(callingUserId);
if (managedUserId < 0) {
@@ -10822,9 +10757,7 @@
ContactsInternal.startQuickContactWithErrorToastForUser(
mContext, intent, new UserHandle(managedUserId));
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
/**
@@ -10984,8 +10917,7 @@
}
private void maybeClearLockTaskPolicyLocked() {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
for (int i = userInfos.size() - 1; i >= 0; i--) {
int userId = userInfos.get(i).id;
@@ -11006,9 +10938,7 @@
setLockTaskFeaturesLocked(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
}
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
@Override
@@ -11081,12 +11011,8 @@
}
}
- long id = mInjector.binderClearCallingIdentity();
- try {
- mInjector.settingsGlobalPutString(setting, value);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.settingsGlobalPutString(setting, value));
}
}
@@ -11194,7 +11120,7 @@
} else {
try {
setUserRestriction(who, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- (Integer.parseInt(value) == 0) ? true : false);
+ (Integer.parseInt(value) == 0) ? true : false, /* parent */ false);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
.setAdmin(who)
@@ -11206,8 +11132,7 @@
}
return;
}
- long id = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
if (Settings.Secure.DEFAULT_INPUT_METHOD.equals(setting)) {
final String currentValue = mInjector.settingsSecureGetStringForUser(
Settings.Secure.DEFAULT_INPUT_METHOD, callingUserId);
@@ -11223,9 +11148,7 @@
saveSettingsLocked(callingUserId);
}
mInjector.settingsSecurePutStringForUser(setting, value, callingUserId);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
@@ -11239,7 +11162,7 @@
Preconditions.checkNotNull(who, "ComponentName is null");
synchronized (getLockObject()) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on);
+ setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on, /* parent */ false);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_MASTER_VOLUME_MUTED)
.setAdmin(who)
@@ -11267,12 +11190,8 @@
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
int userId = UserHandle.getCallingUserId();
- long id = mInjector.binderClearCallingIdentity();
- try {
- mUserManagerInternal.setUserIcon(userId, icon);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ mInjector.binderWithCleanCallingIdentity(
+ () -> mUserManagerInternal.setUserIcon(userId, icon));
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_USER_ICON)
@@ -11671,15 +11590,12 @@
@Override
public void reportSeparateProfileChallengeChanged(@UserIdInt int userId) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
synchronized (getLockObject()) {
updateMaximumTimeToLockLocked(userId);
updatePasswordQualityCacheForUserGroup(userId);
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SEPARATE_PROFILE_CHALLENGE_CHANGED)
.setBoolean(isSeparateProfileChallengeEnabled(userId))
@@ -11971,8 +11887,7 @@
.putExtra(DeviceAdminReceiver.EXTRA_SYSTEM_UPDATE_RECEIVED_TIME,
info == null ? -1 : info.getReceivedTime());
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
synchronized (getLockObject()) {
// Broadcast to device owner first if there is one.
if (mOwners.hasDeviceOwner()) {
@@ -12002,9 +11917,7 @@
}
}
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
@Override
@@ -12128,8 +12041,7 @@
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, DELEGATION_PERMISSION_GRANT);
}
synchronized (getLockObject()) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
int granted;
if (getTargetSdk(callerPackage, user.getIdentifier())
< android.os.Build.VERSION_CODES.Q) {
@@ -12164,9 +12076,7 @@
? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
: DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
}
@@ -12406,8 +12316,7 @@
// Make sure caller has DO.
enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin);
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
if (macAddresses == null) {
return null;
@@ -12417,9 +12326,7 @@
.setAdmin(admin)
.write();
return macAddresses.length > 0 ? macAddresses[0] : null;
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
/**
@@ -12455,8 +12362,7 @@
Preconditions.checkNotNull(admin);
// Make sure caller has DO.
enforceDeviceOwner(admin);
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
// Make sure there are no ongoing calls on the device.
if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
throw new IllegalStateException("Cannot be called with ongoing call on the device");
@@ -12466,9 +12372,7 @@
.setAdmin(admin)
.write();
mInjector.powerManagerReboot(PowerManager.REBOOT_REQUESTED_BY_DEVICE_OWNER);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
@Override
@@ -12705,17 +12609,14 @@
final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
final int callingUserId = mInjector.userHandleGetCallingUserId();
- final long identity = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
final List<String> excludedPkgs
= removeInvalidPkgsForMeteredDataRestriction(callingUserId, packageNames);
admin.meteredDisabledPackages = packageNames;
pushMeteredDisabledPackagesLocked(callingUserId);
saveSettingsLocked(callingUserId);
return excludedPkgs;
- } finally {
- mInjector.binderRestoreCallingIdentity(identity);
- }
+ });
}
}
@@ -12828,8 +12729,7 @@
who.flattenToString(), userId));
// First, set restriction on removing the profile.
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
// Clear restriction as user.
UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId));
if (!parentUser.isSystem()) {
@@ -12841,9 +12741,7 @@
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
parentUser);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
// markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner
// data, no need to do it manually.
@@ -12961,8 +12859,7 @@
}
private boolean areAllUsersAffiliatedWithDeviceLocked() {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
for (int i = 0; i < userInfos.size(); i++) {
int userId = userInfos.get(i).id;
@@ -12971,11 +12868,8 @@
return false;
}
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
-
- return true;
+ return true;
+ });
}
@Override
@@ -13185,12 +13079,8 @@
private boolean isCurrentUserDemo() {
if (UserManager.isDeviceInDemoMode(mContext)) {
final int userId = mInjector.userHandleGetCallingUserId();
- final long callingIdentity = mInjector.binderClearCallingIdentity();
- try {
- return mUserManager.getUserInfo(userId).isDemo();
- } finally {
- mInjector.binderRestoreCallingIdentity(callingIdentity);
- }
+ return mInjector.binderWithCleanCallingIdentity(
+ () -> mUserManager.getUserInfo(userId).isDemo());
}
return false;
}
@@ -13406,8 +13296,7 @@
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
final int callingUserId = mInjector.userHandleGetCallingUserId();
- final long callingIdentity = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
ArrayList<UserHandle> targetUsers = new ArrayList<>();
if (!isDeviceOwner(admin, callingUserId)) {
// Profile owners can only bind to the device owner.
@@ -13426,9 +13315,7 @@
}
return targetUsers;
- } finally {
- mInjector.binderRestoreCallingIdentity(callingIdentity);
- }
+ });
}
}
@@ -13469,8 +13356,7 @@
}
wtfIfInLock();
- final long token = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
final AccountManager am = AccountManager.get(mContext);
final Account accounts[] = am.getAccountsAsUser(userId);
if (accounts.length == 0) {
@@ -13508,9 +13394,7 @@
Log.e(LOG_TAG, "Found incompatible accounts");
}
return !compatible;
- } finally {
- mInjector.binderRestoreCallingIdentity(token);
- }
+ });
}
private boolean hasAccountFeatures(AccountManager am, Account account, String[] features) {
@@ -13563,8 +13447,7 @@
private void setNetworkLoggingActiveInternal(boolean active) {
synchronized (getLockObject()) {
- final long callingIdentity = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
if (active) {
mNetworkLogger = new NetworkLogger(this, mInjector.getPackageManagerInternal());
if (!mNetworkLogger.startNetworkLogging()) {
@@ -13582,9 +13465,7 @@
mNetworkLogger = null;
mInjector.getNotificationManager().cancel(SystemMessage.NOTE_NETWORK_LOGGING);
}
- } finally {
- mInjector.binderRestoreCallingIdentity(callingIdentity);
- }
+ });
}
}
@@ -13596,12 +13477,8 @@
throw new IllegalStateException("logging is not available");
}
if (mNetworkLogger != null) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- return mNetworkLogger.forceBatchFinalization();
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ return mInjector.binderWithCleanCallingIdentity(
+ () -> mNetworkLogger.forceBatchFinalization());
}
return 0;
}
@@ -13624,15 +13501,12 @@
@GuardedBy("getLockObject()")
private void maybeResumeDeviceWideLoggingLocked() {
if (areAllUsersAffiliatedWithDeviceLocked()) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
mSecurityLogMonitor.resume();
if (mNetworkLogger != null) {
mNetworkLogger.resume();
}
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
}
@@ -13726,8 +13600,9 @@
} else {
deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
}
+ final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
- intent.setPackage("com.android.systemui");
+ intent.setPackage(pm.getSystemUiServiceComponent().getPackageName());
final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
UserHandle.CURRENT);
Notification notification =
@@ -13820,8 +13695,7 @@
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
DevicePolicyData policy = getUserData(userHandle);
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
if (policy.mPasswordTokenHandle != 0) {
mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, userHandle);
}
@@ -13829,9 +13703,7 @@
userHandle, /*EscrowTokenStateChangeCallback*/ null);
saveSettingsLocked(userHandle);
return policy.mPasswordTokenHandle != 0;
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
}
@@ -13846,16 +13718,13 @@
DevicePolicyData policy = getUserData(userHandle);
if (policy.mPasswordTokenHandle != 0) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
boolean result = mLockPatternUtils.removeEscrowToken(
policy.mPasswordTokenHandle, userHandle);
policy.mPasswordTokenHandle = 0;
saveSettingsLocked(userHandle);
return result;
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ });
}
}
return false;
@@ -13872,13 +13741,9 @@
DevicePolicyData policy = getUserData(userHandle);
if (policy.mPasswordTokenHandle != 0) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- return mLockPatternUtils.isEscrowTokenActive(policy.mPasswordTokenHandle,
- userHandle);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ return mInjector.binderWithCleanCallingIdentity(
+ () -> mLockPatternUtils.isEscrowTokenActive(policy.mPasswordTokenHandle,
+ userHandle));
}
}
return false;
@@ -14248,13 +14113,8 @@
enforceDeviceOwner(who);
int operatedId = -1;
- Uri resultUri;
- final long id = mInjector.binderClearCallingIdentity();
- try {
- resultUri = mContext.getContentResolver().insert(DPC_URI, apnSetting.toContentValues());
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ Uri resultUri = mInjector.binderWithCleanCallingIdentity(() ->
+ mContext.getContentResolver().insert(DPC_URI, apnSetting.toContentValues()));
if (resultUri != null) {
try {
operatedId = Integer.parseInt(resultUri.getLastPathSegment());
@@ -14279,14 +14139,10 @@
if (apnId < 0) {
return false;
}
- final long id = mInjector.binderClearCallingIdentity();
- try {
- return mContext.getContentResolver().update(
- Uri.withAppendedPath(DPC_URI, Integer.toString(apnId)),
- apnSetting.toContentValues(), null, null) > 0;
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ mContext.getContentResolver().update(
+ Uri.withAppendedPath(DPC_URI, Integer.toString(apnId)),
+ apnSetting.toContentValues(), null, null) > 0);
}
@Override
@@ -14304,14 +14160,9 @@
if(apnId < 0) {
return false;
}
- int numDeleted = 0;
- final long id = mInjector.binderClearCallingIdentity();
- try {
- numDeleted = mContext.getContentResolver().delete(
- Uri.withAppendedPath(DPC_URI, Integer.toString(apnId)), null, null);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ int numDeleted = mInjector.binderWithCleanCallingIdentity(
+ () -> mContext.getContentResolver().delete(
+ Uri.withAppendedPath(DPC_URI, Integer.toString(apnId)), null, null));
return numDeleted > 0;
}
@@ -14327,13 +14178,8 @@
}
private List<ApnSetting> getOverrideApnsUnchecked() {
- final Cursor cursor;
- final long id = mInjector.binderClearCallingIdentity();
- try {
- cursor = mContext.getContentResolver().query(DPC_URI, null, null, null, null);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ final Cursor cursor = mInjector.binderWithCleanCallingIdentity(
+ () -> mContext.getContentResolver().query(DPC_URI, null, null, null, null));
if (cursor == null) {
return Collections.emptyList();
@@ -14365,13 +14211,8 @@
private void setOverrideApnsEnabledUnchecked(boolean enabled) {
ContentValues value = new ContentValues();
value.put(ENFORCE_KEY, enabled);
- final long id = mInjector.binderClearCallingIdentity();
- try {
- mContext.getContentResolver().update(
- ENFORCE_MANAGED_URI, value, null, null);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ mInjector.binderWithCleanCallingIdentity(() -> mContext.getContentResolver().update(
+ ENFORCE_MANAGED_URI, value, null, null));
}
@Override
@@ -14382,14 +14223,9 @@
Preconditions.checkNotNull(who, "ComponentName is null in isOverrideApnEnabled");
enforceDeviceOwner(who);
- Cursor enforceCursor;
- final long id = mInjector.binderClearCallingIdentity();
- try {
- enforceCursor = mContext.getContentResolver().query(
- ENFORCE_MANAGED_URI, null, null, null, null);
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity(
+ () -> mContext.getContentResolver().query(
+ ENFORCE_MANAGED_URI, null, null, null, null));
if (enforceCursor == null) {
return false;
@@ -14456,13 +14292,10 @@
private void putPrivateDnsSettings(@Nullable String mode, @Nullable String host) {
// Set Private DNS settings using system permissions, as apps cannot write
// to global settings.
- long origId = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
mInjector.settingsGlobalPutString(PRIVATE_DNS_MODE, mode);
mInjector.settingsGlobalPutString(PRIVATE_DNS_SPECIFIER, host);
- } finally {
- mInjector.binderRestoreCallingIdentity(origId);
- }
+ });
}
@Override
@@ -14548,8 +14381,7 @@
.setBoolean(isDeviceAB())
.write();
enforceDeviceOwner(admin);
- final long id = mInjector.binderClearCallingIdentity();
- try {
+ mInjector.binderWithCleanCallingIdentity(() -> {
UpdateInstaller updateInstaller;
if (isDeviceAB()) {
updateInstaller = new AbUpdateInstaller(
@@ -14559,9 +14391,7 @@
mContext, updateFileDescriptor, callback, mInjector, mConstants);
}
updateInstaller.startInstallUpdate();
- } finally {
- mInjector.binderRestoreCallingIdentity(id);
- }
+ });
}
private boolean isDeviceAB() {
@@ -14645,6 +14475,35 @@
}
@Override
+ public void setCrossProfilePackages(ComponentName who, List<String> packageNames) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(packageNames, "Package names is null");
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin =
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ admin.mCrossProfilePackages = packageNames;
+ saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+ }
+ }
+
+ @Override
+ public List<String> getCrossProfilePackages(ComponentName who) {
+ if (!mHasFeature) {
+ return Collections.emptyList();
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ return admin.mCrossProfilePackages;
+ }
+ }
+
+ @Override
public boolean isManagedKiosk() {
if (!mHasFeature) {
return false;
@@ -14733,8 +14592,7 @@
throw new SecurityException("Input package name doesn't align with actual "
+ "calling package.");
}
- final long identity = mInjector.binderClearCallingIdentity();
- try {
+ return mInjector.binderWithCleanCallingIdentity(() -> {
final int workProfileUserId = getManagedUserId(callingUserId);
if (workProfileUserId < 0) {
return false;
@@ -14758,10 +14616,8 @@
Log.e(LOG_TAG, "View event activity not found", e);
return false;
}
- } finally {
- mInjector.binderRestoreCallingIdentity(identity);
- }
- return true;
+ return true;
+ });
}
private boolean isCallingFromPackage(String packageName, int callingUid) {
diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp
new file mode 100644
index 0000000..2661925
--- /dev/null
+++ b/services/incremental/Android.bp
@@ -0,0 +1,110 @@
+// 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.
+
+cc_defaults {
+ name: "service.incremental-proto-defaults",
+
+ cpp_std: "c++2a",
+ proto: {
+ type: "lite",
+ },
+}
+
+cc_defaults {
+ name: "service.incremental-defaults",
+ defaults: ["service.incremental-proto-defaults"],
+ local_include_dirs: ["include/"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ "-Wno-unused-parameter",
+ ],
+
+ static_libs: [
+ "libbase",
+ "libext2_uuid",
+ "libdataloader_aidl-cpp",
+ "libincremental_aidl-cpp",
+ "libincremental_manager_aidl-cpp",
+ "libnativehelper",
+ "libprotobuf-cpp-lite",
+ "service.incremental.proto",
+ "libutils",
+ "libvold_binder",
+ ],
+ shared_libs: [
+ "libandroidfw",
+ "libbinder",
+ "libincfs",
+ "liblog",
+ "libz",
+ "libziparchive",
+ ],
+}
+
+filegroup {
+ name: "service.incremental_srcs",
+ srcs: [
+ "incremental_service.c",
+ "IncrementalService.cpp",
+ "BinderIncrementalService.cpp",
+ "path.cpp",
+ "ServiceWrappers.cpp",
+ ],
+}
+
+cc_library {
+ name: "service.incremental",
+ defaults: [
+ "service.incremental-defaults",
+ "linux_bionic_supported",
+ ],
+
+ export_include_dirs: ["include/",],
+ srcs: [
+ ":service.incremental_srcs",
+ ],
+}
+
+cc_library_headers {
+ name: "service.incremental_headers",
+ export_include_dirs: ["include/",],
+}
+
+cc_library_static {
+ name: "service.incremental.proto",
+ defaults: ["service.incremental-proto-defaults"],
+ proto: {
+ export_proto_headers: true,
+ },
+
+ srcs: [
+ "Metadata.proto",
+ ],
+}
+
+cc_test {
+ name: "service.incremental_test",
+ defaults: ["service.incremental-defaults"],
+ test_suites: ["device-tests"],
+ srcs: [
+ ":service.incremental_srcs",
+ "test/IncrementalServiceTest.cpp",
+ "test/path_test.cpp",
+ ],
+ static_libs: [
+ "libgmock",
+ ]
+}
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
new file mode 100644
index 0000000..bb26c1f
--- /dev/null
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -0,0 +1,230 @@
+/*
+ * 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 "BinderIncrementalService.h"
+
+#include <binder/IResultReceiver.h>
+#include <incfs.h>
+
+#include "ServiceWrappers.h"
+#include "jni.h"
+#include "nativehelper/JNIHelp.h"
+#include "path.h"
+
+using namespace std::literals;
+using namespace android::incremental;
+
+namespace android::os::incremental {
+
+static constexpr auto kAndroidDataEnv = "ANDROID_DATA"sv;
+static constexpr auto kDataDir = "/data"sv;
+static constexpr auto kIncrementalSubDir = "incremental"sv;
+
+static std::string getIncrementalDir() {
+ const char* dataDir = getenv(kAndroidDataEnv.data());
+ if (!dataDir || !*dataDir) {
+ dataDir = kDataDir.data();
+ }
+ return path::normalize(path::join(dataDir, kIncrementalSubDir));
+}
+
+static bool incFsEnabled() {
+ // TODO(b/136132412): use vold to check /sys/fs/incfs/version (per selinux compliance)
+ return incfs::enabled();
+}
+
+static bool incFsVersionValid(const sp<IVold>& vold) {
+ int version = -1;
+ auto status = vold->incFsVersion(&version);
+ if (!status.isOk() || version <= 0) {
+ return false;
+ }
+ return true;
+}
+
+BinderIncrementalService::BinderIncrementalService(const sp<IServiceManager>& sm)
+ : mImpl(RealServiceManager(sm), getIncrementalDir()) {}
+
+BinderIncrementalService* BinderIncrementalService::start() {
+ if (!incFsEnabled()) {
+ return nullptr;
+ }
+
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+ sp<IServiceManager> sm(defaultServiceManager());
+ if (!sm) {
+ return nullptr;
+ }
+
+ sp<IBinder> voldBinder(sm->getService(String16("vold")));
+ if (voldBinder == nullptr) {
+ return nullptr;
+ }
+ sp<IVold> vold = interface_cast<IVold>(voldBinder);
+ if (!incFsVersionValid(vold)) {
+ return nullptr;
+ }
+
+ sp<BinderIncrementalService> self(new BinderIncrementalService(sm));
+ status_t ret = sm->addService(String16{getServiceName()}, self);
+ if (ret != android::OK) {
+ return nullptr;
+ }
+ sp<ProcessState> ps(ProcessState::self());
+ ps->startThreadPool();
+ ps->giveThreadPoolName();
+ return self.get();
+}
+
+status_t BinderIncrementalService::dump(int fd, const Vector<String16>& args) {
+ return OK;
+}
+
+void BinderIncrementalService::onSystemReady() {
+ mImpl.onSystemReady();
+}
+
+static binder::Status ok() {
+ return binder::Status::ok();
+}
+
+binder::Status BinderIncrementalService::openStorage(const std::string& path,
+ int32_t* _aidl_return) {
+ *_aidl_return = mImpl.openStorage(path);
+ return ok();
+}
+
+binder::Status BinderIncrementalService::createStorage(
+ const std::string& path, const DataLoaderParamsParcel& params,
+ int32_t createMode, int32_t* _aidl_return) {
+ *_aidl_return =
+ mImpl.createStorage(path, const_cast<DataLoaderParamsParcel&&>(params),
+ android::incremental::IncrementalService::CreateOptions(
+ createMode));
+ return ok();
+}
+
+binder::Status BinderIncrementalService::createLinkedStorage(const std::string& path,
+ int32_t otherStorageId,
+ int32_t createMode,
+ int32_t* _aidl_return) {
+ *_aidl_return =
+ mImpl.createLinkedStorage(path, otherStorageId,
+ android::incremental::IncrementalService::CreateOptions(
+ createMode));
+ return ok();
+}
+
+binder::Status BinderIncrementalService::makeBindMount(int32_t storageId,
+ const std::string& pathUnderStorage,
+ const std::string& targetFullPath,
+ int32_t bindType, int32_t* _aidl_return) {
+ *_aidl_return = mImpl.bind(storageId, pathUnderStorage, targetFullPath,
+ android::incremental::IncrementalService::BindKind(bindType));
+ return ok();
+}
+
+binder::Status BinderIncrementalService::deleteBindMount(int32_t storageId,
+ const std::string& targetFullPath,
+ int32_t* _aidl_return) {
+ *_aidl_return = mImpl.unbind(storageId, targetFullPath);
+ return ok();
+}
+
+binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) {
+ mImpl.deleteStorage(storageId);
+ return ok();
+}
+
+binder::Status BinderIncrementalService::makeDirectory(int32_t storageId,
+ const std::string& pathUnderStorage,
+ int32_t* _aidl_return) {
+ auto inode = mImpl.makeDir(storageId, pathUnderStorage);
+ *_aidl_return = inode < 0 ? inode : 0;
+ return ok();
+}
+
+binder::Status BinderIncrementalService::makeDirectories(int32_t storageId,
+ const std::string& pathUnderStorage,
+ int32_t* _aidl_return) {
+ auto inode = mImpl.makeDirs(storageId, pathUnderStorage);
+ *_aidl_return = inode < 0 ? inode : 0;
+ return ok();
+}
+
+binder::Status BinderIncrementalService::makeFile(int32_t storageId,
+ const std::string& pathUnderStorage, int64_t size,
+ const std::vector<uint8_t>& metadata,
+ int32_t* _aidl_return) {
+ auto inode = mImpl.makeFile(storageId, pathUnderStorage, size,
+ {(const char*)metadata.data(), metadata.size()}, {});
+ *_aidl_return = inode < 0 ? inode : 0;
+ return ok();
+}
+binder::Status BinderIncrementalService::makeFileFromRange(
+ int32_t storageId, const std::string& pathUnderStorage,
+ const std::string& sourcePathUnderStorage, int64_t start, int64_t end,
+ int32_t* _aidl_return) {
+ // TODO(b/136132412): implement this
+ *_aidl_return = -1;
+ return ok();
+}
+binder::Status BinderIncrementalService::makeLink(int32_t sourceStorageId,
+ const std::string& relativeSourcePath,
+ int32_t destStorageId,
+ const std::string& relativeDestPath,
+ int32_t* _aidl_return) {
+ auto sourceInode = mImpl.nodeFor(sourceStorageId, relativeSourcePath);
+ auto [targetParentInode, name] = mImpl.parentAndNameFor(destStorageId, relativeDestPath);
+ *_aidl_return = mImpl.link(sourceStorageId, sourceInode, targetParentInode, name);
+ return ok();
+}
+binder::Status BinderIncrementalService::unlink(int32_t storageId,
+ const std::string& pathUnderStorage,
+ int32_t* _aidl_return) {
+ auto [parentNode, name] = mImpl.parentAndNameFor(storageId, pathUnderStorage);
+ *_aidl_return = mImpl.unlink(storageId, parentNode, name);
+ return ok();
+}
+binder::Status BinderIncrementalService::isFileRangeLoaded(int32_t storageId,
+ const std::string& relativePath,
+ int64_t start, int64_t end,
+ bool* _aidl_return) {
+ *_aidl_return = false;
+ return ok();
+}
+binder::Status BinderIncrementalService::getFileMetadata(int32_t storageId,
+ const std::string& relativePath,
+ std::vector<uint8_t>* _aidl_return) {
+ auto inode = mImpl.nodeFor(storageId, relativePath);
+ auto metadata = mImpl.getMetadata(storageId, inode);
+ _aidl_return->assign(metadata.begin(), metadata.end());
+ return ok();
+}
+binder::Status BinderIncrementalService::startLoading(int32_t storageId, bool* _aidl_return) {
+ *_aidl_return = mImpl.startLoading(storageId);
+ return ok();
+}
+} // namespace android::os::incremental
+
+jlong Incremental_IncrementalService_Start() {
+ return (jlong)android::os::incremental::BinderIncrementalService::start();
+}
+void Incremental_IncrementalService_OnSystemReady(jlong self) {
+ if (self) {
+ ((android::os::incremental::BinderIncrementalService*)self)->onSystemReady();
+ }
+}
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
new file mode 100644
index 0000000..37c9661d
--- /dev/null
+++ b/services/incremental/BinderIncrementalService.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <binder/BinderService.h>
+#include <binder/IServiceManager.h>
+
+#include "IncrementalService.h"
+#include "android/os/incremental/BnIncrementalManagerNative.h"
+#include "incremental_service.h"
+
+namespace android::os::incremental {
+
+class BinderIncrementalService : public BnIncrementalManagerNative,
+ public BinderService<BinderIncrementalService> {
+public:
+ BinderIncrementalService(const sp<IServiceManager> &sm);
+
+ static BinderIncrementalService *start();
+ static const char16_t *getServiceName() { return u"incremental_service"; }
+ status_t dump(int fd, const Vector<String16> &args) final;
+
+ void onSystemReady();
+ void onInvalidStorage(int mountId);
+
+ binder::Status openStorage(const std::string &path, int32_t *_aidl_return) final;
+ binder::Status createStorage(
+ const std::string &path,
+ const ::android::content::pm::DataLoaderParamsParcel ¶ms,
+ int32_t createMode, int32_t *_aidl_return) final;
+ binder::Status createLinkedStorage(const std::string &path, int32_t otherStorageId,
+ int32_t createMode, int32_t *_aidl_return) final;
+ binder::Status makeBindMount(int32_t storageId, const std::string &pathUnderStorage,
+ const std::string &targetFullPath, int32_t bindType,
+ int32_t *_aidl_return) final;
+ binder::Status deleteBindMount(int32_t storageId, const std::string &targetFullPath,
+ int32_t *_aidl_return) final;
+ binder::Status deleteStorage(int32_t storageId) final;
+ binder::Status makeDirectory(int32_t storageId, const std::string &pathUnderStorage,
+ int32_t *_aidl_return) final;
+ binder::Status makeDirectories(int32_t storageId, const std::string &pathUnderStorage,
+ int32_t *_aidl_return) final;
+ binder::Status makeFile(int32_t storageId, const std::string &pathUnderStorage, int64_t size,
+ const std::vector<uint8_t> &metadata, int32_t *_aidl_return) final;
+ binder::Status makeFileFromRange(int32_t storageId, const std::string &pathUnderStorage,
+ const std::string &sourcePathUnderStorage, int64_t start,
+ int64_t end, int32_t *_aidl_return);
+ binder::Status makeLink(int32_t sourceStorageId, const std::string &relativeSourcePath,
+ int32_t destStorageId, const std::string &relativeDestPath,
+ int32_t *_aidl_return) final;
+ binder::Status unlink(int32_t storageId, const std::string &pathUnderStorage,
+ int32_t *_aidl_return) final;
+ binder::Status isFileRangeLoaded(int32_t storageId, const std::string &relativePath,
+ int64_t start, int64_t end, bool *_aidl_return) final;
+ binder::Status getFileMetadata(int32_t storageId, const std::string &relativePath,
+ std::vector<uint8_t> *_aidl_return) final;
+ binder::Status startLoading(int32_t storageId, bool *_aidl_return) final;
+
+private:
+ android::incremental::IncrementalService mImpl;
+};
+
+} // namespace android::os::incremental
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
new file mode 100644
index 0000000..afce260
--- /dev/null
+++ b/services/incremental/IncrementalService.cpp
@@ -0,0 +1,1045 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "IncrementalService"
+
+#include "IncrementalService.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android/content/pm/IDataLoaderStatusListener.h>
+#include <android/os/IVold.h>
+#include <androidfw/ZipFileRO.h>
+#include <androidfw/ZipUtils.h>
+#include <binder/BinderService.h>
+#include <binder/ParcelFileDescriptor.h>
+#include <binder/Status.h>
+#include <sys/stat.h>
+#include <uuid/uuid.h>
+#include <zlib.h>
+
+#include <iterator>
+#include <span>
+#include <stack>
+#include <thread>
+#include <type_traits>
+
+#include "Metadata.pb.h"
+
+using namespace std::literals;
+using namespace android::content::pm;
+
+namespace android::incremental {
+
+namespace {
+
+using IncrementalFileSystemControlParcel =
+ ::android::os::incremental::IncrementalFileSystemControlParcel;
+
+struct Constants {
+ static constexpr auto backing = "backing_store"sv;
+ static constexpr auto mount = "mount"sv;
+ static constexpr auto image = "incfs.img"sv;
+ static constexpr auto storagePrefix = "st"sv;
+ static constexpr auto mountpointMdPrefix = ".mountpoint."sv;
+ static constexpr auto infoMdName = ".info"sv;
+};
+
+static const Constants& constants() {
+ static Constants c;
+ return c;
+}
+
+template <base::LogSeverity level = base::ERROR>
+bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) {
+ auto cstr = path::c_str(name);
+ if (::mkdir(cstr, mode)) {
+ if (errno != EEXIST) {
+ PLOG(level) << "Can't create directory '" << name << '\'';
+ return false;
+ }
+ struct stat st;
+ if (::stat(cstr, &st) || !S_ISDIR(st.st_mode)) {
+ PLOG(level) << "Path exists but is not a directory: '" << name << '\'';
+ return false;
+ }
+ }
+ return true;
+}
+
+static std::string toMountKey(std::string_view path) {
+ if (path.empty()) {
+ return "@none";
+ }
+ if (path == "/"sv) {
+ return "@root";
+ }
+ if (path::isAbsolute(path)) {
+ path.remove_prefix(1);
+ }
+ std::string res(path);
+ std::replace(res.begin(), res.end(), '/', '_');
+ std::replace(res.begin(), res.end(), '@', '_');
+ return res;
+}
+
+static std::pair<std::string, std::string> makeMountDir(std::string_view incrementalDir,
+ std::string_view path) {
+ auto mountKey = toMountKey(path);
+ const auto prefixSize = mountKey.size();
+ for (int counter = 0; counter < 1000;
+ mountKey.resize(prefixSize), base::StringAppendF(&mountKey, "%d", counter++)) {
+ auto mountRoot = path::join(incrementalDir, mountKey);
+ if (mkdirOrLog(mountRoot, 0770, false)) {
+ return {mountKey, mountRoot};
+ }
+ }
+ return {};
+}
+
+template <class ProtoMessage, class Control>
+static ProtoMessage parseFromIncfs(const IncFsWrapper* incfs, Control&& control,
+ std::string_view path) {
+ struct stat st;
+ if (::stat(path::c_str(path), &st)) {
+ return {};
+ }
+ auto md = incfs->getMetadata(control, st.st_ino);
+ ProtoMessage message;
+ return message.ParseFromArray(md.data(), md.size()) ? message : ProtoMessage{};
+}
+
+static bool isValidMountTarget(std::string_view path) {
+ return path::isAbsolute(path) && path::isEmptyDir(path).value_or(true);
+}
+
+std::string makeBindMdName() {
+ static constexpr auto uuidStringSize = 36;
+
+ uuid_t guid;
+ uuid_generate(guid);
+
+ std::string name;
+ const auto prefixSize = constants().mountpointMdPrefix.size();
+ name.reserve(prefixSize + uuidStringSize);
+
+ name = constants().mountpointMdPrefix;
+ name.resize(prefixSize + uuidStringSize);
+ uuid_unparse(guid, name.data() + prefixSize);
+
+ return name;
+}
+} // namespace
+
+IncrementalService::IncFsMount::~IncFsMount() {
+ incrementalService.mIncrementalManager->destroyDataLoader(mountId);
+ control.reset();
+ LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\'';
+ for (auto&& [target, _] : bindPoints) {
+ LOG(INFO) << "\tbind: " << target;
+ incrementalService.mVold->unmountIncFs(target);
+ }
+ LOG(INFO) << "\troot: " << root;
+ incrementalService.mVold->unmountIncFs(path::join(root, constants().mount));
+ cleanupFilesystem(root);
+}
+
+auto IncrementalService::IncFsMount::makeStorage(StorageId id) -> StorageMap::iterator {
+ metadata::Storage st;
+ st.set_id(id);
+ auto metadata = st.SerializeAsString();
+
+ std::string name;
+ for (int no = nextStorageDirNo.fetch_add(1, std::memory_order_relaxed), i = 0;
+ i < 1024 && no >= 0; no = nextStorageDirNo.fetch_add(1, std::memory_order_relaxed), ++i) {
+ name.clear();
+ base::StringAppendF(&name, "%.*s%d", int(constants().storagePrefix.size()),
+ constants().storagePrefix.data(), no);
+ if (auto node =
+ incrementalService.mIncFs->makeDir(control, name, INCFS_ROOT_INODE, metadata);
+ node >= 0) {
+ std::lock_guard l(lock);
+ return storages.insert_or_assign(id, Storage{std::move(name), node}).first;
+ }
+ }
+ nextStorageDirNo = 0;
+ return storages.end();
+}
+
+void IncrementalService::IncFsMount::cleanupFilesystem(std::string_view root) {
+ ::unlink(path::join(root, constants().backing, constants().image).c_str());
+ ::rmdir(path::join(root, constants().backing).c_str());
+ ::rmdir(path::join(root, constants().mount).c_str());
+ ::rmdir(path::c_str(root));
+}
+
+IncrementalService::IncrementalService(const ServiceManagerWrapper& sm, std::string_view rootDir)
+ : mVold(sm.getVoldService()),
+ mIncrementalManager(sm.getIncrementalManager()),
+ mIncFs(sm.getIncFs()),
+ mIncrementalDir(rootDir) {
+ if (!mVold) {
+ LOG(FATAL) << "Vold service is unavailable";
+ }
+ if (!mIncrementalManager) {
+ LOG(FATAL) << "IncrementalManager service is unavailable";
+ }
+ // TODO(b/136132412): check that root dir should already exist
+ // TODO(b/136132412): enable mount existing dirs after SELinux rules are merged
+ // mountExistingImages();
+}
+
+IncrementalService::~IncrementalService() = default;
+
+std::optional<std::future<void>> IncrementalService::onSystemReady() {
+ std::promise<void> threadFinished;
+ if (mSystemReady.exchange(true)) {
+ return {};
+ }
+
+ std::vector<IfsMountPtr> mounts;
+ {
+ std::lock_guard l(mLock);
+ mounts.reserve(mMounts.size());
+ for (auto&& [id, ifs] : mMounts) {
+ if (ifs->mountId == id) {
+ mounts.push_back(ifs);
+ }
+ }
+ }
+
+ std::thread([this, mounts = std::move(mounts)]() {
+ std::vector<IfsMountPtr> failedLoaderMounts;
+ for (auto&& ifs : mounts) {
+ if (prepareDataLoader(*ifs, nullptr)) {
+ LOG(INFO) << "Successfully started data loader for mount " << ifs->mountId;
+ } else {
+ LOG(WARNING) << "Failed to start data loader for mount " << ifs->mountId;
+ failedLoaderMounts.push_back(std::move(ifs));
+ }
+ }
+
+ while (!failedLoaderMounts.empty()) {
+ LOG(WARNING) << "Deleting failed mount " << failedLoaderMounts.back()->mountId;
+ deleteStorage(*failedLoaderMounts.back());
+ failedLoaderMounts.pop_back();
+ }
+ mPrepareDataLoaders.set_value_at_thread_exit();
+ }).detach();
+ return mPrepareDataLoaders.get_future();
+}
+
+auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator {
+ for (;;) {
+ if (mNextId == kMaxStorageId) {
+ mNextId = 0;
+ }
+ auto id = ++mNextId;
+ auto [it, inserted] = mMounts.try_emplace(id, nullptr);
+ if (inserted) {
+ return it;
+ }
+ }
+}
+
+StorageId IncrementalService::createStorage(std::string_view mountPoint,
+ DataLoaderParamsParcel&& dataLoaderParams,
+ CreateOptions options) {
+ LOG(INFO) << "createStorage: " << mountPoint << " | " << int(options);
+ if (!path::isAbsolute(mountPoint)) {
+ LOG(ERROR) << "path is not absolute: " << mountPoint;
+ return kInvalidStorageId;
+ }
+
+ auto mountNorm = path::normalize(mountPoint);
+ {
+ const auto id = findStorageId(mountNorm);
+ if (id != kInvalidStorageId) {
+ if (options & CreateOptions::OpenExisting) {
+ LOG(INFO) << "Opened existing storage " << id;
+ return id;
+ }
+ LOG(ERROR) << "Directory " << mountPoint << " is already mounted at storage " << id;
+ return kInvalidStorageId;
+ }
+ }
+
+ if (!(options & CreateOptions::CreateNew)) {
+ LOG(ERROR) << "not requirested create new storage, and it doesn't exist: " << mountPoint;
+ return kInvalidStorageId;
+ }
+
+ if (!path::isEmptyDir(mountNorm)) {
+ LOG(ERROR) << "Mounting over existing non-empty directory is not supported: " << mountNorm;
+ return kInvalidStorageId;
+ }
+ auto [mountKey, mountRoot] = makeMountDir(mIncrementalDir, mountNorm);
+ if (mountRoot.empty()) {
+ LOG(ERROR) << "Bad mount point";
+ return kInvalidStorageId;
+ }
+ // Make sure the code removes all crap it may create while still failing.
+ auto firstCleanup = [](const std::string* ptr) { IncFsMount::cleanupFilesystem(*ptr); };
+ auto firstCleanupOnFailure =
+ std::unique_ptr<std::string, decltype(firstCleanup)>(&mountRoot, firstCleanup);
+
+ auto mountTarget = path::join(mountRoot, constants().mount);
+ if (!mkdirOrLog(path::join(mountRoot, constants().backing)) || !mkdirOrLog(mountTarget)) {
+ return kInvalidStorageId;
+ }
+
+ const auto image = path::join(mountRoot, constants().backing, constants().image);
+ IncFsMount::Control control;
+ {
+ std::lock_guard l(mMountOperationLock);
+ IncrementalFileSystemControlParcel controlParcel;
+ auto status = mVold->mountIncFs(image, mountTarget, incfs::truncate, &controlParcel);
+ if (!status.isOk()) {
+ LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8();
+ return kInvalidStorageId;
+ }
+ if (!controlParcel.cmd || !controlParcel.log) {
+ LOG(ERROR) << "Vold::mountIncFs() returned invalid control parcel.";
+ return kInvalidStorageId;
+ }
+ control.cmdFd = controlParcel.cmd->release();
+ control.logFd = controlParcel.log->release();
+ }
+
+ std::unique_lock l(mLock);
+ const auto mountIt = getStorageSlotLocked();
+ const auto mountId = mountIt->first;
+ l.unlock();
+
+ auto ifs =
+ std::make_shared<IncFsMount>(std::move(mountRoot), mountId, std::move(control), *this);
+ // Now it's the |ifs|'s responsibility to clean up after itself, and the only cleanup we need
+ // is the removal of the |ifs|.
+ firstCleanupOnFailure.release();
+
+ auto secondCleanup = [this, &l](auto itPtr) {
+ if (!l.owns_lock()) {
+ l.lock();
+ }
+ mMounts.erase(*itPtr);
+ };
+ auto secondCleanupOnFailure =
+ std::unique_ptr<decltype(mountIt), decltype(secondCleanup)>(&mountIt, secondCleanup);
+
+ const auto storageIt = ifs->makeStorage(ifs->mountId);
+ if (storageIt == ifs->storages.end()) {
+ LOG(ERROR) << "Can't create default storage directory";
+ return kInvalidStorageId;
+ }
+
+ {
+ metadata::Mount m;
+ m.mutable_storage()->set_id(ifs->mountId);
+ m.mutable_loader()->set_type((int)dataLoaderParams.type);
+ m.mutable_loader()->set_package_name(dataLoaderParams.packageName);
+ m.mutable_loader()->set_class_name(dataLoaderParams.className);
+ m.mutable_loader()->set_arguments(dataLoaderParams.arguments);
+ const auto metadata = m.SerializeAsString();
+ m.mutable_loader()->release_arguments();
+ m.mutable_loader()->release_class_name();
+ m.mutable_loader()->release_package_name();
+ if (auto err = mIncFs->makeFile(ifs->control, constants().infoMdName, INCFS_ROOT_INODE, 0,
+ metadata);
+ err < 0) {
+ LOG(ERROR) << "Saving mount metadata failed: " << -err;
+ return kInvalidStorageId;
+ }
+ }
+
+ const auto bk =
+ (options & CreateOptions::PermanentBind) ? BindKind::Permanent : BindKind::Temporary;
+ if (auto err = addBindMount(*ifs, storageIt->first, std::string(storageIt->second.name),
+ std::move(mountNorm), bk, l);
+ err < 0) {
+ LOG(ERROR) << "adding bind mount failed: " << -err;
+ return kInvalidStorageId;
+ }
+
+ // Done here as well, all data structures are in good state.
+ secondCleanupOnFailure.release();
+
+ if (!prepareDataLoader(*ifs, &dataLoaderParams)) {
+ LOG(ERROR) << "prepareDataLoader() failed";
+ deleteStorageLocked(*ifs, std::move(l));
+ return kInvalidStorageId;
+ }
+
+ mountIt->second = std::move(ifs);
+ l.unlock();
+ LOG(INFO) << "created storage " << mountId;
+ return mountId;
+}
+
+StorageId IncrementalService::createLinkedStorage(std::string_view mountPoint,
+ StorageId linkedStorage,
+ IncrementalService::CreateOptions options) {
+ if (!isValidMountTarget(mountPoint)) {
+ LOG(ERROR) << "Mount point is invalid or missing";
+ return kInvalidStorageId;
+ }
+
+ std::unique_lock l(mLock);
+ const auto& ifs = getIfsLocked(linkedStorage);
+ if (!ifs) {
+ LOG(ERROR) << "Ifs unavailable";
+ return kInvalidStorageId;
+ }
+
+ const auto mountIt = getStorageSlotLocked();
+ const auto storageId = mountIt->first;
+ const auto storageIt = ifs->makeStorage(storageId);
+ if (storageIt == ifs->storages.end()) {
+ LOG(ERROR) << "Can't create a new storage";
+ mMounts.erase(mountIt);
+ return kInvalidStorageId;
+ }
+
+ l.unlock();
+
+ const auto bk =
+ (options & CreateOptions::PermanentBind) ? BindKind::Permanent : BindKind::Temporary;
+ if (auto err = addBindMount(*ifs, storageIt->first, std::string(storageIt->second.name),
+ path::normalize(mountPoint), bk, l);
+ err < 0) {
+ LOG(ERROR) << "bindMount failed with error: " << err;
+ return kInvalidStorageId;
+ }
+
+ mountIt->second = ifs;
+ return storageId;
+}
+
+IncrementalService::BindPathMap::const_iterator IncrementalService::findStorageLocked(
+ std::string_view path) const {
+ auto bindPointIt = mBindsByPath.upper_bound(path);
+ if (bindPointIt == mBindsByPath.begin()) {
+ return mBindsByPath.end();
+ }
+ --bindPointIt;
+ if (!path::startsWith(path, bindPointIt->first)) {
+ return mBindsByPath.end();
+ }
+ return bindPointIt;
+}
+
+StorageId IncrementalService::findStorageId(std::string_view path) const {
+ std::lock_guard l(mLock);
+ auto it = findStorageLocked(path);
+ if (it == mBindsByPath.end()) {
+ return kInvalidStorageId;
+ }
+ return it->second->second.storage;
+}
+
+void IncrementalService::deleteStorage(StorageId storageId) {
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ return;
+ }
+ deleteStorage(*ifs);
+}
+
+void IncrementalService::deleteStorage(IncrementalService::IncFsMount& ifs) {
+ std::unique_lock l(ifs.lock);
+ deleteStorageLocked(ifs, std::move(l));
+}
+
+void IncrementalService::deleteStorageLocked(IncrementalService::IncFsMount& ifs,
+ std::unique_lock<std::mutex>&& ifsLock) {
+ const auto storages = std::move(ifs.storages);
+ // Don't move the bind points out: Ifs's dtor will use them to unmount everything.
+ const auto bindPoints = ifs.bindPoints;
+ ifsLock.unlock();
+
+ std::lock_guard l(mLock);
+ for (auto&& [id, _] : storages) {
+ if (id != ifs.mountId) {
+ mMounts.erase(id);
+ }
+ }
+ for (auto&& [path, _] : bindPoints) {
+ mBindsByPath.erase(path);
+ }
+ mMounts.erase(ifs.mountId);
+}
+
+StorageId IncrementalService::openStorage(std::string_view pathInMount) {
+ if (!path::isAbsolute(pathInMount)) {
+ return kInvalidStorageId;
+ }
+
+ return findStorageId(path::normalize(pathInMount));
+}
+
+Inode IncrementalService::nodeFor(StorageId storage, std::string_view subpath) const {
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ return -1;
+ }
+ std::unique_lock l(ifs->lock);
+ auto storageIt = ifs->storages.find(storage);
+ if (storageIt == ifs->storages.end()) {
+ return -1;
+ }
+ if (subpath.empty() || subpath == "."sv) {
+ return storageIt->second.node;
+ }
+ auto path = path::join(ifs->root, constants().mount, storageIt->second.name, subpath);
+ l.unlock();
+ struct stat st;
+ if (::stat(path.c_str(), &st)) {
+ return -1;
+ }
+ return st.st_ino;
+}
+
+std::pair<Inode, std::string_view> IncrementalService::parentAndNameFor(
+ StorageId storage, std::string_view subpath) const {
+ auto name = path::basename(subpath);
+ if (name.empty()) {
+ return {-1, {}};
+ }
+ auto dir = path::dirname(subpath);
+ if (dir.empty() || dir == "/"sv) {
+ return {-1, {}};
+ }
+ auto inode = nodeFor(storage, dir);
+ return {inode, name};
+}
+
+IncrementalService::IfsMountPtr IncrementalService::getIfs(StorageId storage) const {
+ std::lock_guard l(mLock);
+ return getIfsLocked(storage);
+}
+
+const IncrementalService::IfsMountPtr& IncrementalService::getIfsLocked(StorageId storage) const {
+ auto it = mMounts.find(storage);
+ if (it == mMounts.end()) {
+ static const IfsMountPtr kEmpty = {};
+ return kEmpty;
+ }
+ return it->second;
+}
+
+int IncrementalService::bind(StorageId storage, std::string_view sourceSubdir,
+ std::string_view target, BindKind kind) {
+ if (!isValidMountTarget(target)) {
+ return -EINVAL;
+ }
+
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ return -EINVAL;
+ }
+ std::unique_lock l(ifs->lock);
+ const auto storageInfo = ifs->storages.find(storage);
+ if (storageInfo == ifs->storages.end()) {
+ return -EINVAL;
+ }
+ auto source = path::join(storageInfo->second.name, sourceSubdir);
+ l.unlock();
+ std::unique_lock l2(mLock, std::defer_lock);
+ return addBindMount(*ifs, storage, std::move(source), path::normalize(target), kind, l2);
+}
+
+int IncrementalService::unbind(StorageId storage, std::string_view target) {
+ if (!path::isAbsolute(target)) {
+ return -EINVAL;
+ }
+
+ LOG(INFO) << "Removing bind point " << target;
+
+ // Here we should only look up by the exact target, not by a subdirectory of any existing mount,
+ // otherwise there's a chance to unmount something completely unrelated
+ const auto norm = path::normalize(target);
+ std::unique_lock l(mLock);
+ const auto storageIt = mBindsByPath.find(norm);
+ if (storageIt == mBindsByPath.end() || storageIt->second->second.storage != storage) {
+ return -EINVAL;
+ }
+ const auto bindIt = storageIt->second;
+ const auto storageId = bindIt->second.storage;
+ const auto ifs = getIfsLocked(storageId);
+ if (!ifs) {
+ LOG(ERROR) << "Internal error: storageId " << storageId << " for bound path " << target
+ << " is missing";
+ return -EFAULT;
+ }
+ mBindsByPath.erase(storageIt);
+ l.unlock();
+
+ mVold->unmountIncFs(bindIt->first);
+ std::unique_lock l2(ifs->lock);
+ if (ifs->bindPoints.size() <= 1) {
+ ifs->bindPoints.clear();
+ deleteStorageLocked(*ifs, std::move(l2));
+ } else {
+ const std::string savedFile = std::move(bindIt->second.savedFilename);
+ ifs->bindPoints.erase(bindIt);
+ l2.unlock();
+ if (!savedFile.empty()) {
+ mIncFs->unlink(ifs->control, INCFS_ROOT_INODE, savedFile);
+ }
+ }
+ return 0;
+}
+
+Inode IncrementalService::makeFile(StorageId storageId, std::string_view pathUnderStorage,
+ long size, std::string_view metadata,
+ std::string_view signature) {
+ (void)signature;
+ auto [parentInode, name] = parentAndNameFor(storageId, pathUnderStorage);
+ if (parentInode < 0) {
+ return -EINVAL;
+ }
+ if (auto ifs = getIfs(storageId)) {
+ auto inode = mIncFs->makeFile(ifs->control, name, parentInode, size, metadata);
+ if (inode < 0) {
+ return inode;
+ }
+ auto metadataBytes = std::vector<uint8_t>();
+ if (metadata.data() != nullptr && metadata.size() > 0) {
+ metadataBytes.insert(metadataBytes.end(), &metadata.data()[0],
+ &metadata.data()[metadata.size()]);
+ }
+ mIncrementalManager->newFileForDataLoader(ifs->mountId, inode, metadataBytes);
+ return inode;
+ }
+ return -EINVAL;
+}
+
+Inode IncrementalService::makeDir(StorageId storageId, std::string_view pathUnderStorage,
+ std::string_view metadata) {
+ auto [parentInode, name] = parentAndNameFor(storageId, pathUnderStorage);
+ if (parentInode < 0) {
+ return -EINVAL;
+ }
+ if (auto ifs = getIfs(storageId)) {
+ return mIncFs->makeDir(ifs->control, name, parentInode, metadata);
+ }
+ return -EINVAL;
+}
+
+Inode IncrementalService::makeDirs(StorageId storageId, std::string_view pathUnderStorage,
+ std::string_view metadata) {
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ return -EINVAL;
+ }
+ std::string_view parentDir(pathUnderStorage);
+ auto p = parentAndNameFor(storageId, pathUnderStorage);
+ std::stack<std::string> pathsToCreate;
+ while (p.first < 0) {
+ parentDir = path::dirname(parentDir);
+ pathsToCreate.emplace(parentDir);
+ p = parentAndNameFor(storageId, parentDir);
+ }
+ Inode inode;
+ while (!pathsToCreate.empty()) {
+ p = parentAndNameFor(storageId, pathsToCreate.top());
+ pathsToCreate.pop();
+ inode = mIncFs->makeDir(ifs->control, p.second, p.first, metadata);
+ if (inode < 0) {
+ return inode;
+ }
+ }
+ return mIncFs->makeDir(ifs->control, path::basename(pathUnderStorage), inode, metadata);
+}
+
+int IncrementalService::link(StorageId storage, Inode item, Inode newParent,
+ std::string_view newName) {
+ if (auto ifs = getIfs(storage)) {
+ return mIncFs->link(ifs->control, item, newParent, newName);
+ }
+ return -EINVAL;
+}
+
+int IncrementalService::unlink(StorageId storage, Inode parent, std::string_view name) {
+ if (auto ifs = getIfs(storage)) {
+ return mIncFs->unlink(ifs->control, parent, name);
+ }
+ return -EINVAL;
+}
+
+int IncrementalService::addBindMount(IncFsMount& ifs, StorageId storage, std::string&& sourceSubdir,
+ std::string&& target, BindKind kind,
+ std::unique_lock<std::mutex>& mainLock) {
+ if (!isValidMountTarget(target)) {
+ return -EINVAL;
+ }
+
+ std::string mdFileName;
+ if (kind != BindKind::Temporary) {
+ metadata::BindPoint bp;
+ bp.set_storage_id(storage);
+ bp.set_allocated_dest_path(&target);
+ bp.set_allocated_source_subdir(&sourceSubdir);
+ const auto metadata = bp.SerializeAsString();
+ bp.release_source_subdir();
+ bp.release_dest_path();
+ mdFileName = makeBindMdName();
+ auto node = mIncFs->makeFile(ifs.control, mdFileName, INCFS_ROOT_INODE, 0, metadata);
+ if (node < 0) {
+ return int(node);
+ }
+ }
+
+ return addBindMountWithMd(ifs, storage, std::move(mdFileName), std::move(sourceSubdir),
+ std::move(target), kind, mainLock);
+}
+
+int IncrementalService::addBindMountWithMd(IncrementalService::IncFsMount& ifs, StorageId storage,
+ std::string&& metadataName, std::string&& sourceSubdir,
+ std::string&& target, BindKind kind,
+ std::unique_lock<std::mutex>& mainLock) {
+ LOG(INFO) << "Adding bind mount: " << sourceSubdir << " -> " << target;
+ {
+ auto path = path::join(ifs.root, constants().mount, sourceSubdir);
+ std::lock_guard l(mMountOperationLock);
+ const auto status = mVold->bindMount(path, target);
+ if (!status.isOk()) {
+ LOG(ERROR) << "Calling Vold::bindMount() failed: " << status.toString8();
+ return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC
+ ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode()
+ : status.serviceSpecificErrorCode() == 0
+ ? -EFAULT
+ : status.serviceSpecificErrorCode()
+ : -EIO;
+ }
+ }
+
+ if (!mainLock.owns_lock()) {
+ mainLock.lock();
+ }
+ std::lock_guard l(ifs.lock);
+ const auto [it, _] =
+ ifs.bindPoints.insert_or_assign(target,
+ IncFsMount::Bind{storage, std::move(metadataName),
+ std::move(sourceSubdir), kind});
+ mBindsByPath[std::move(target)] = it;
+ return 0;
+}
+
+RawMetadata IncrementalService::getMetadata(StorageId storage, Inode node) const {
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ return {};
+ }
+ return mIncFs->getMetadata(ifs->control, node);
+}
+
+std::vector<std::string> IncrementalService::listFiles(StorageId storage) const {
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ return {};
+ }
+
+ std::unique_lock l(ifs->lock);
+ auto subdirIt = ifs->storages.find(storage);
+ if (subdirIt == ifs->storages.end()) {
+ return {};
+ }
+ auto dir = path::join(ifs->root, constants().mount, subdirIt->second.name);
+ l.unlock();
+
+ const auto prefixSize = dir.size() + 1;
+ std::vector<std::string> todoDirs{std::move(dir)};
+ std::vector<std::string> result;
+ do {
+ auto currDir = std::move(todoDirs.back());
+ todoDirs.pop_back();
+
+ auto d =
+ std::unique_ptr<DIR, decltype(&::closedir)>(::opendir(currDir.c_str()), ::closedir);
+ while (auto e = ::readdir(d.get())) {
+ if (e->d_type == DT_REG) {
+ result.emplace_back(
+ path::join(std::string_view(currDir).substr(prefixSize), e->d_name));
+ continue;
+ }
+ if (e->d_type == DT_DIR) {
+ if (e->d_name == "."sv || e->d_name == ".."sv) {
+ continue;
+ }
+ todoDirs.emplace_back(path::join(currDir, e->d_name));
+ continue;
+ }
+ }
+ } while (!todoDirs.empty());
+ return result;
+}
+
+bool IncrementalService::startLoading(StorageId storage) const {
+ const auto ifs = getIfs(storage);
+ if (!ifs) {
+ return false;
+ }
+ bool started = false;
+ std::unique_lock l(ifs->lock);
+ if (ifs->dataLoaderStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) {
+ if (ifs->dataLoaderReady.wait_for(l, Seconds(5)) == std::cv_status::timeout) {
+ LOG(ERROR) << "Timeout waiting for data loader to be ready";
+ return false;
+ }
+ }
+ auto status = mIncrementalManager->startDataLoader(ifs->mountId, &started);
+ if (!status.isOk()) {
+ return false;
+ }
+ return started;
+}
+
+void IncrementalService::mountExistingImages() {
+ auto d = std::unique_ptr<DIR, decltype(&::closedir)>(::opendir(mIncrementalDir.c_str()),
+ ::closedir);
+ while (auto e = ::readdir(d.get())) {
+ if (e->d_type != DT_DIR) {
+ continue;
+ }
+ if (e->d_name == "."sv || e->d_name == ".."sv) {
+ continue;
+ }
+ auto root = path::join(mIncrementalDir, e->d_name);
+ if (!mountExistingImage(root, e->d_name)) {
+ IncFsMount::cleanupFilesystem(root);
+ }
+ }
+}
+
+bool IncrementalService::mountExistingImage(std::string_view root, std::string_view key) {
+ LOG(INFO) << "Trying to mount: " << key;
+
+ auto mountTarget = path::join(root, constants().mount);
+ const auto image = path::join(root, constants().backing, constants().image);
+
+ IncFsMount::Control control;
+ IncrementalFileSystemControlParcel controlParcel;
+ auto status = mVold->mountIncFs(image, mountTarget, 0, &controlParcel);
+ if (!status.isOk()) {
+ LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8();
+ return false;
+ }
+ if (controlParcel.cmd) {
+ control.cmdFd = controlParcel.cmd->release();
+ }
+ if (controlParcel.log) {
+ control.logFd = controlParcel.log->release();
+ }
+
+ auto ifs = std::make_shared<IncFsMount>(std::string(root), -1, std::move(control), *this);
+
+ auto m = parseFromIncfs<metadata::Mount>(mIncFs.get(), ifs->control,
+ path::join(mountTarget, constants().infoMdName));
+ if (!m.has_loader() || !m.has_storage()) {
+ LOG(ERROR) << "Bad mount metadata in mount at " << root;
+ return false;
+ }
+
+ ifs->mountId = m.storage().id();
+ mNextId = std::max(mNextId, ifs->mountId + 1);
+
+ std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
+ auto d = std::unique_ptr<DIR, decltype(&::closedir)>(::opendir(path::c_str(mountTarget)),
+ ::closedir);
+ while (auto e = ::readdir(d.get())) {
+ if (e->d_type == DT_REG) {
+ auto name = std::string_view(e->d_name);
+ if (name.starts_with(constants().mountpointMdPrefix)) {
+ bindPoints.emplace_back(name,
+ parseFromIncfs<metadata::BindPoint>(mIncFs.get(),
+ ifs->control,
+ path::join(mountTarget,
+ name)));
+ if (bindPoints.back().second.dest_path().empty() ||
+ bindPoints.back().second.source_subdir().empty()) {
+ bindPoints.pop_back();
+ mIncFs->unlink(ifs->control, INCFS_ROOT_INODE, name);
+ }
+ }
+ } else if (e->d_type == DT_DIR) {
+ if (e->d_name == "."sv || e->d_name == ".."sv) {
+ continue;
+ }
+ auto name = std::string_view(e->d_name);
+ if (name.starts_with(constants().storagePrefix)) {
+ auto md = parseFromIncfs<metadata::Storage>(mIncFs.get(), ifs->control,
+ path::join(mountTarget, name));
+ auto [_, inserted] = mMounts.try_emplace(md.id(), ifs);
+ if (!inserted) {
+ LOG(WARNING) << "Ignoring storage with duplicate id " << md.id()
+ << " for mount " << root;
+ continue;
+ }
+ ifs->storages.insert_or_assign(md.id(),
+ IncFsMount::Storage{std::string(name),
+ Inode(e->d_ino)});
+ mNextId = std::max(mNextId, md.id() + 1);
+ }
+ }
+ }
+
+ if (ifs->storages.empty()) {
+ LOG(WARNING) << "No valid storages in mount " << root;
+ return false;
+ }
+
+ int bindCount = 0;
+ for (auto&& bp : bindPoints) {
+ std::unique_lock l(mLock, std::defer_lock);
+ bindCount += !addBindMountWithMd(*ifs, bp.second.storage_id(), std::move(bp.first),
+ std::move(*bp.second.mutable_source_subdir()),
+ std::move(*bp.second.mutable_dest_path()),
+ BindKind::Permanent, l);
+ }
+
+ if (bindCount == 0) {
+ LOG(WARNING) << "No valid bind points for mount " << root;
+ deleteStorage(*ifs);
+ return false;
+ }
+
+ DataLoaderParamsParcel dlParams;
+ dlParams.type = (DataLoaderType)m.loader().type();
+ dlParams.packageName = std::move(*m.mutable_loader()->mutable_package_name());
+ dlParams.className = std::move(*m.mutable_loader()->mutable_class_name());
+ dlParams.arguments = std::move(*m.mutable_loader()->mutable_arguments());
+ if (!prepareDataLoader(*ifs, &dlParams)) {
+ deleteStorage(*ifs);
+ return false;
+ }
+
+ mMounts[ifs->mountId] = std::move(ifs);
+ return true;
+}
+
+bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs,
+ DataLoaderParamsParcel* params) {
+ if (!mSystemReady.load(std::memory_order_relaxed)) {
+ std::unique_lock l(ifs.lock);
+ if (params) {
+ if (ifs.savedDataLoaderParams) {
+ LOG(WARNING) << "Trying to pass second set of data loader parameters, ignored it";
+ } else {
+ ifs.savedDataLoaderParams = std::move(*params);
+ }
+ } else {
+ if (!ifs.savedDataLoaderParams) {
+ LOG(ERROR) << "Mount " << ifs.mountId
+ << " is broken: no data loader params (system is not ready yet)";
+ return false;
+ }
+ }
+ return true; // eventually...
+ }
+ if (base::GetBoolProperty("incremental.skip_loader", false)) {
+ LOG(INFO) << "Skipped data loader because of incremental.skip_loader property";
+ std::unique_lock l(ifs.lock);
+ ifs.savedDataLoaderParams.reset();
+ return true;
+ }
+
+ std::unique_lock l(ifs.lock);
+ if (ifs.dataLoaderStatus == IDataLoaderStatusListener::DATA_LOADER_CREATED) {
+ LOG(INFO) << "Skipped data loader preparation because it already exists";
+ return true;
+ }
+
+ auto* dlp = params ? params
+ : ifs.savedDataLoaderParams ? &ifs.savedDataLoaderParams.value() : nullptr;
+ if (!dlp) {
+ LOG(ERROR) << "Mount " << ifs.mountId << " is broken: no data loader params";
+ return false;
+ }
+ FileSystemControlParcel fsControlParcel;
+ fsControlParcel.incremental = std::make_unique<IncrementalFileSystemControlParcel>();
+ fsControlParcel.incremental->cmd =
+ std::make_unique<os::ParcelFileDescriptor>(base::unique_fd(::dup(ifs.control.cmdFd)));
+ fsControlParcel.incremental->log =
+ std::make_unique<os::ParcelFileDescriptor>(base::unique_fd(::dup(ifs.control.logFd)));
+ sp<IncrementalDataLoaderListener> listener = new IncrementalDataLoaderListener(*this);
+ bool created = false;
+ auto status = mIncrementalManager->prepareDataLoader(ifs.mountId, fsControlParcel, *dlp,
+ listener, &created);
+ if (!status.isOk() || !created) {
+ LOG(ERROR) << "Failed to create a data loader for mount " << ifs.mountId;
+ return false;
+ }
+ ifs.savedDataLoaderParams.reset();
+ return true;
+}
+
+binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId,
+ int newStatus) {
+ std::unique_lock l(incrementalService.mLock);
+ const auto& ifs = incrementalService.getIfsLocked(mountId);
+ if (!ifs) {
+ LOG(WARNING) << "Received data loader status " << int(newStatus) << " for unknown mount "
+ << mountId;
+ return binder::Status::ok();
+ }
+ ifs->dataLoaderStatus = newStatus;
+ switch (newStatus) {
+ case IDataLoaderStatusListener::DATA_LOADER_NO_CONNECTION: {
+ auto now = Clock::now();
+ if (ifs->connectionLostTime.time_since_epoch().count() == 0) {
+ ifs->connectionLostTime = now;
+ break;
+ }
+ auto duration =
+ std::chrono::duration_cast<Seconds>(now - ifs->connectionLostTime).count();
+ if (duration >= 10) {
+ incrementalService.mIncrementalManager->showHealthBlockedUI(mountId);
+ }
+ break;
+ }
+ case IDataLoaderStatusListener::DATA_LOADER_CONNECTION_OK: {
+ ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STARTED;
+ break;
+ }
+ case IDataLoaderStatusListener::DATA_LOADER_CREATED: {
+ ifs->dataLoaderReady.notify_one();
+ break;
+ }
+ case IDataLoaderStatusListener::DATA_LOADER_DESTROYED: {
+ ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STOPPED;
+ incrementalService.deleteStorageLocked(*ifs, std::move(l));
+ break;
+ }
+ case IDataLoaderStatusListener::DATA_LOADER_STARTED: {
+ break;
+ }
+ case IDataLoaderStatusListener::DATA_LOADER_STOPPED: {
+ break;
+ }
+ default: {
+ LOG(WARNING) << "Unknown data loader status: " << newStatus
+ << " for mount: " << mountId;
+ break;
+ }
+ }
+
+ return binder::Status::ok();
+}
+
+} // namespace android::incremental
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
new file mode 100644
index 0000000..a03ffa0
--- /dev/null
+++ b/services/incremental/IncrementalService.h
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/os/incremental/IIncrementalManager.h>
+#include <android/content/pm/DataLoaderParamsParcel.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
+#include <atomic>
+#include <chrono>
+#include <future>
+#include <limits>
+#include <map>
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "ServiceWrappers.h"
+#include "android/content/pm/BnDataLoaderStatusListener.h"
+#include "incfs.h"
+#include "path.h"
+
+using namespace android::os::incremental;
+
+namespace android::os {
+class IVold;
+}
+
+namespace android::incremental {
+
+using MountId = int;
+using StorageId = int;
+using Inode = incfs::Inode;
+using BlockIndex = incfs::BlockIndex;
+using RawMetadata = incfs::RawMetadata;
+using Clock = std::chrono::steady_clock;
+using TimePoint = std::chrono::time_point<Clock>;
+using Seconds = std::chrono::seconds;
+
+class IncrementalService {
+public:
+ explicit IncrementalService(const ServiceManagerWrapper& sm, std::string_view rootDir);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+ ~IncrementalService();
+#pragma GCC diagnostic pop
+
+ static constexpr StorageId kInvalidStorageId = -1;
+ static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max();
+
+ enum CreateOptions {
+ TemporaryBind = 1,
+ PermanentBind = 2,
+ CreateNew = 4,
+ OpenExisting = 8,
+
+ Default = TemporaryBind | CreateNew
+ };
+
+ enum class BindKind {
+ Temporary = 0,
+ Permanent = 1,
+ };
+
+ std::optional<std::future<void>> onSystemReady();
+
+ StorageId createStorage(std::string_view mountPoint,
+ DataLoaderParamsParcel&& dataLoaderParams,
+ CreateOptions options = CreateOptions::Default);
+ StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage,
+ CreateOptions options = CreateOptions::Default);
+ StorageId openStorage(std::string_view pathInMount);
+
+ Inode nodeFor(StorageId storage, std::string_view subpath) const;
+ std::pair<Inode, std::string_view> parentAndNameFor(StorageId storage,
+ std::string_view subpath) const;
+
+ int bind(StorageId storage, std::string_view subdir, std::string_view target, BindKind kind);
+ int unbind(StorageId storage, std::string_view target);
+ void deleteStorage(StorageId storage);
+
+ Inode makeFile(StorageId storage, std::string_view name, long size, std::string_view metadata,
+ std::string_view signature);
+ Inode makeDir(StorageId storage, std::string_view name, std::string_view metadata = {});
+ Inode makeDirs(StorageId storage, std::string_view name, std::string_view metadata = {});
+
+ int link(StorageId storage, Inode item, Inode newParent, std::string_view newName);
+ int unlink(StorageId storage, Inode parent, std::string_view name);
+
+ bool isRangeLoaded(StorageId storage, Inode file, std::pair<BlockIndex, BlockIndex> range) {
+ return false;
+ }
+
+ RawMetadata getMetadata(StorageId storage, Inode node) const;
+ std::string getSigngatureData(StorageId storage, Inode node) const { return {}; }
+
+ std::vector<std::string> listFiles(StorageId storage) const;
+ bool startLoading(StorageId storage) const;
+
+ class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener {
+ public:
+ IncrementalDataLoaderListener(IncrementalService& incrementalService)
+ : incrementalService(incrementalService) {}
+ // Callbacks interface
+ binder::Status onStatusChanged(MountId mount, int newStatus) override;
+
+ private:
+ IncrementalService& incrementalService;
+ };
+
+private:
+ struct IncFsMount {
+ struct Bind {
+ StorageId storage;
+ std::string savedFilename;
+ std::string sourceDir;
+ BindKind kind;
+ };
+
+ struct Storage {
+ std::string name;
+ Inode node;
+ };
+
+ struct Control {
+ operator IncFsControl() const { return {cmdFd, logFd}; }
+ void reset() {
+ cmdFd.reset();
+ logFd.reset();
+ }
+
+ base::unique_fd cmdFd;
+ base::unique_fd logFd;
+ };
+
+ using BindMap = std::map<std::string, Bind>;
+ using StorageMap = std::unordered_map<StorageId, Storage>;
+
+ mutable std::mutex lock;
+ const std::string root;
+ Control control;
+ /*const*/ MountId mountId;
+ StorageMap storages;
+ BindMap bindPoints;
+ std::optional<DataLoaderParamsParcel> savedDataLoaderParams;
+ std::atomic<int> nextStorageDirNo{0};
+ std::atomic<int> dataLoaderStatus = -1;
+ std::condition_variable dataLoaderReady;
+ TimePoint connectionLostTime = TimePoint();
+ const IncrementalService& incrementalService;
+
+ IncFsMount(std::string root, MountId mountId, Control control,
+ const IncrementalService& incrementalService)
+ : root(std::move(root)),
+ control(std::move(control)),
+ mountId(mountId),
+ incrementalService(incrementalService) {}
+ IncFsMount(IncFsMount&&) = delete;
+ IncFsMount& operator=(IncFsMount&&) = delete;
+ ~IncFsMount();
+
+ StorageMap::iterator makeStorage(StorageId id);
+
+ static void cleanupFilesystem(std::string_view root);
+ };
+
+ using IfsMountPtr = std::shared_ptr<IncFsMount>;
+ using MountMap = std::unordered_map<MountId, IfsMountPtr>;
+ using BindPathMap = std::map<std::string, IncFsMount::BindMap::iterator, path::PathLess>;
+
+ void mountExistingImages();
+ bool mountExistingImage(std::string_view root, std::string_view key);
+
+ IfsMountPtr getIfs(StorageId storage) const;
+ const IfsMountPtr& getIfsLocked(StorageId storage) const;
+ int addBindMount(IncFsMount& ifs, StorageId storage, std::string&& sourceSubdir,
+ std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock);
+
+ int addBindMountWithMd(IncFsMount& ifs, StorageId storage, std::string&& metadataName,
+ std::string&& sourceSubdir, std::string&& target, BindKind kind,
+ std::unique_lock<std::mutex>& mainLock);
+
+ bool prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel* params);
+ BindPathMap::const_iterator findStorageLocked(std::string_view path) const;
+ StorageId findStorageId(std::string_view path) const;
+
+ void deleteStorage(IncFsMount& ifs);
+ void deleteStorageLocked(IncFsMount& ifs, std::unique_lock<std::mutex>&& ifsLock);
+ MountMap::iterator getStorageSlotLocked();
+
+ // Member variables
+ // These are shared pointers for the sake of unit testing
+ std::shared_ptr<VoldServiceWrapper> mVold;
+ std::shared_ptr<IncrementalManagerWrapper> mIncrementalManager;
+ std::shared_ptr<IncFsWrapper> mIncFs;
+ const std::string mIncrementalDir;
+
+ mutable std::mutex mLock;
+ mutable std::mutex mMountOperationLock;
+ MountMap mMounts;
+ BindPathMap mBindsByPath;
+
+ std::atomic_bool mSystemReady = false;
+ StorageId mNextId = 0;
+ std::promise<void> mPrepareDataLoaders;
+};
+
+} // namespace android::incremental
diff --git a/services/incremental/Metadata.proto b/services/incremental/Metadata.proto
new file mode 100644
index 0000000..79f1bf8
--- /dev/null
+++ b/services/incremental/Metadata.proto
@@ -0,0 +1,25 @@
+syntax = "proto3";
+
+package android.incremental.metadata;
+
+message BindPoint {
+ int32 storage_id = 1;
+ string source_subdir = 2;
+ string dest_path = 3;
+}
+
+message DataLoader {
+ string package_name = 1;
+ string class_name = 3;
+ string arguments = 2;
+ int32 type = 4;
+}
+
+message Storage {
+ int32 id = 1;
+}
+
+message Mount {
+ Storage storage = 1;
+ DataLoader loader = 2;
+}
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
new file mode 100644
index 0000000..a79b26a
--- /dev/null
+++ b/services/incremental/ServiceWrappers.cpp
@@ -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.
+ */
+
+#include "ServiceWrappers.h"
+
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+
+#include <string>
+#include <string_view>
+
+using namespace std::literals;
+
+namespace android::os::incremental {
+
+static constexpr auto kVoldServiceName = "vold"sv;
+static constexpr auto kIncrementalManagerName = "incremental"sv;
+
+RealServiceManager::RealServiceManager(const sp<IServiceManager>& serviceManager)
+ : mServiceManager(serviceManager) {}
+
+template <class INTERFACE>
+sp<INTERFACE> RealServiceManager::getRealService(std::string_view serviceName) const {
+ sp<IBinder> binder = mServiceManager->getService(String16(serviceName.data()));
+ if (binder == 0) {
+ return 0;
+ }
+ return interface_cast<INTERFACE>(binder);
+}
+
+std::shared_ptr<VoldServiceWrapper> RealServiceManager::getVoldService() const {
+ sp<os::IVold> vold = RealServiceManager::getRealService<os::IVold>(kVoldServiceName);
+ if (vold != 0) {
+ return std::make_shared<RealVoldService>(vold);
+ }
+ return nullptr;
+}
+
+std::shared_ptr<IncrementalManagerWrapper> RealServiceManager::getIncrementalManager() const {
+ sp<IIncrementalManager> manager =
+ RealServiceManager::getRealService<IIncrementalManager>(kIncrementalManagerName);
+ if (manager != 0) {
+ return std::make_shared<RealIncrementalManager>(manager);
+ }
+ return nullptr;
+}
+
+std::shared_ptr<IncFsWrapper> RealServiceManager::getIncFs() const {
+ return std::make_shared<RealIncFs>();
+}
+
+} // namespace android::os::incremental
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
new file mode 100644
index 0000000..5704582
--- /dev/null
+++ b/services/incremental/ServiceWrappers.h
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/content/pm/DataLoaderParamsParcel.h>
+#include <android/content/pm/FileSystemControlParcel.h>
+#include <android/content/pm/IDataLoaderStatusListener.h>
+#include <android/os/IVold.h>
+#include <android/os/incremental/IIncrementalManager.h>
+#include <binder/IServiceManager.h>
+#include <incfs.h>
+
+#include <string>
+#include <string_view>
+
+using namespace android::incfs;
+using namespace android::content::pm;
+
+namespace android::os::incremental {
+
+// --- Wrapper interfaces ---
+
+class VoldServiceWrapper {
+public:
+ virtual ~VoldServiceWrapper(){};
+ virtual binder::Status mountIncFs(const std::string& imagePath, const std::string& targetDir,
+ int32_t flags,
+ IncrementalFileSystemControlParcel* _aidl_return) const = 0;
+ virtual binder::Status unmountIncFs(const std::string& dir) const = 0;
+ virtual binder::Status bindMount(const std::string& sourceDir,
+ const std::string& targetDir) const = 0;
+};
+
+class IncrementalManagerWrapper {
+public:
+ virtual ~IncrementalManagerWrapper() {}
+ virtual binder::Status prepareDataLoader(
+ int32_t mountId, const FileSystemControlParcel& control,
+ const DataLoaderParamsParcel& params,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) const = 0;
+ virtual binder::Status startDataLoader(int32_t mountId, bool* _aidl_return) const = 0;
+ virtual binder::Status destroyDataLoader(int32_t mountId) const = 0;
+ virtual binder::Status newFileForDataLoader(int32_t mountId, int64_t inode,
+ const ::std::vector<uint8_t>& metadata) const = 0;
+ virtual binder::Status showHealthBlockedUI(int32_t mountId) const = 0;
+};
+
+class IncFsWrapper {
+public:
+ virtual ~IncFsWrapper() {}
+ virtual Inode makeFile(Control control, std::string_view name, Inode parent, Size size,
+ std::string_view metadata) const = 0;
+ virtual Inode makeDir(Control control, std::string_view name, Inode parent,
+ std::string_view metadata, int mode = 0555) const = 0;
+ virtual RawMetadata getMetadata(Control control, Inode inode) const = 0;
+ virtual ErrorCode link(Control control, Inode item, Inode targetParent,
+ std::string_view name) const = 0;
+ virtual ErrorCode unlink(Control control, Inode parent, std::string_view name) const = 0;
+ virtual ErrorCode writeBlocks(Control control, const incfs_new_data_block blocks[],
+ int blocksCount) const = 0;
+};
+
+class ServiceManagerWrapper {
+public:
+ virtual ~ServiceManagerWrapper() {}
+ virtual std::shared_ptr<VoldServiceWrapper> getVoldService() const = 0;
+ virtual std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const = 0;
+ virtual std::shared_ptr<IncFsWrapper> getIncFs() const = 0;
+};
+
+// --- Real stuff ---
+
+class RealVoldService : public VoldServiceWrapper {
+public:
+ RealVoldService(const sp<os::IVold> vold) : mInterface(vold) {}
+ ~RealVoldService() = default;
+ binder::Status mountIncFs(const std::string& imagePath, const std::string& targetDir,
+ int32_t flags,
+ IncrementalFileSystemControlParcel* _aidl_return) const override {
+ return mInterface->mountIncFs(imagePath, targetDir, flags, _aidl_return);
+ }
+ binder::Status unmountIncFs(const std::string& dir) const override {
+ return mInterface->unmountIncFs(dir);
+ }
+ binder::Status bindMount(const std::string& sourceDir,
+ const std::string& targetDir) const override {
+ return mInterface->bindMount(sourceDir, targetDir);
+ }
+
+private:
+ sp<os::IVold> mInterface;
+};
+
+class RealIncrementalManager : public IncrementalManagerWrapper {
+public:
+ RealIncrementalManager(const sp<os::incremental::IIncrementalManager> manager)
+ : mInterface(manager) {}
+ ~RealIncrementalManager() = default;
+ binder::Status prepareDataLoader(
+ int32_t mountId, const FileSystemControlParcel& control,
+ const DataLoaderParamsParcel& params,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) const override {
+ return mInterface->prepareDataLoader(mountId, control, params, listener, _aidl_return);
+ }
+ binder::Status startDataLoader(int32_t mountId, bool* _aidl_return) const override {
+ return mInterface->startDataLoader(mountId, _aidl_return);
+ }
+ binder::Status destroyDataLoader(int32_t mountId) const override {
+ return mInterface->destroyDataLoader(mountId);
+ }
+ binder::Status newFileForDataLoader(int32_t mountId, int64_t inode,
+ const ::std::vector<uint8_t>& metadata) const override {
+ return mInterface->newFileForDataLoader(mountId, inode, metadata);
+ }
+ binder::Status showHealthBlockedUI(int32_t mountId) const override {
+ return mInterface->showHealthBlockedUI(mountId);
+ }
+
+private:
+ sp<os::incremental::IIncrementalManager> mInterface;
+};
+
+class RealServiceManager : public ServiceManagerWrapper {
+public:
+ RealServiceManager(const sp<IServiceManager>& serviceManager);
+ ~RealServiceManager() = default;
+ std::shared_ptr<VoldServiceWrapper> getVoldService() const override;
+ std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const override;
+ std::shared_ptr<IncFsWrapper> getIncFs() const override;
+
+private:
+ template <class INTERFACE>
+ sp<INTERFACE> getRealService(std::string_view serviceName) const;
+ sp<android::IServiceManager> mServiceManager;
+};
+
+class RealIncFs : public IncFsWrapper {
+public:
+ RealIncFs() = default;
+ ~RealIncFs() = default;
+ Inode makeFile(Control control, std::string_view name, Inode parent, Size size,
+ std::string_view metadata) const override {
+ return incfs::makeFile(control, name, parent, size, metadata);
+ }
+ Inode makeDir(Control control, std::string_view name, Inode parent, std::string_view metadata,
+ int mode) const override {
+ return incfs::makeDir(control, name, parent, metadata, mode);
+ }
+ RawMetadata getMetadata(Control control, Inode inode) const override {
+ return incfs::getMetadata(control, inode);
+ }
+ ErrorCode link(Control control, Inode item, Inode targetParent,
+ std::string_view name) const override {
+ return incfs::link(control, item, targetParent, name);
+ }
+ ErrorCode unlink(Control control, Inode parent, std::string_view name) const override {
+ return incfs::unlink(control, parent, name);
+ }
+ ErrorCode writeBlocks(Control control, const incfs_new_data_block blocks[],
+ int blocksCount) const override {
+ return incfs::writeBlocks(control, blocks, blocksCount);
+ }
+};
+
+} // namespace android::os::incremental
diff --git a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl b/services/incremental/include/incremental_service.h
similarity index 63%
copy from core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
copy to services/incremental/include/incremental_service.h
index cd988dc..7109d95 100644
--- a/core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl
+++ b/services/incremental/include/incremental_service.h
@@ -14,16 +14,19 @@
* limitations under the License.
*/
-package android.os.incremental;
+#ifndef ANDROID_INCREMENTAL_SERVICE_H
+#define ANDROID_INCREMENTAL_SERVICE_H
-import android.os.incremental.NamedParcelFileDescriptor;
+#include <sys/cdefs.h>
+#include <jni.h>
-/**
- * Class for holding data loader configuration parameters.
- * @hide
- */
-parcelable IncrementalDataLoaderParamsParcel {
- @utf8InCpp String packageName;
- @utf8InCpp String staticArgs;
- NamedParcelFileDescriptor[] dynamicArgs;
-}
+__BEGIN_DECLS
+
+#define INCREMENTAL_LIBRARY_NAME "service.incremental.so"
+
+jlong Incremental_IncrementalService_Start();
+void Incremental_IncrementalService_OnSystemReady(jlong self);
+
+__END_DECLS
+
+#endif // ANDROID_INCREMENTAL_SERVICE_H
diff --git a/tests/utils/testutils/java/test/package-info.java b/services/incremental/incremental_service.c
similarity index 76%
copy from tests/utils/testutils/java/test/package-info.java
copy to services/incremental/incremental_service.c
index c34d7b2..f6ea59e 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/services/incremental/incremental_service.c
@@ -14,8 +14,4 @@
* limitations under the License.
*/
-/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+#include "incremental_service.h"
diff --git a/services/incremental/path.cpp b/services/incremental/path.cpp
new file mode 100644
index 0000000..c529d61
--- /dev/null
+++ b/services/incremental/path.cpp
@@ -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.
+ */
+
+#include "path.h"
+
+#include <android-base/strings.h>
+#include <android-base/logging.h>
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <memory>
+
+#include <dirent.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace std::literals;
+
+namespace android::incremental::path {
+
+bool PathCharsLess::operator()(char l, char r) const {
+ int ll = l == '/' ? std::numeric_limits<char>::min() - 1 : l;
+ int rr = r == '/' ? std::numeric_limits<char>::min() - 1 : r;
+ return ll < rr;
+}
+
+bool PathLess::operator()(std::string_view l, std::string_view r) const {
+ return std::lexicographical_compare(std::begin(l), std::end(l), std::begin(r), std::end(r),
+ PathCharsLess());
+}
+
+void details::append_next_path(std::string& target, std::string_view path) {
+ if (path.empty()) {
+ return;
+ }
+ if (!target.empty()) {
+ target.push_back('/');
+ }
+ target += path;
+}
+
+bool isAbsolute(std::string_view path) {
+ return !path.empty() && path[0] == '/';
+}
+
+std::string normalize(std::string_view path) {
+ if (path.empty()) {
+ return {};
+ }
+ if (path.starts_with("../"sv)) {
+ return {};
+ }
+
+ std::string result;
+ if (isAbsolute(path)) {
+ path.remove_prefix(1);
+ } else {
+ char buffer[PATH_MAX];
+ if (!::getcwd(buffer, sizeof(buffer))) {
+ return {};
+ }
+ result += buffer;
+ }
+
+ size_t start = 0;
+ size_t end = 0;
+ for (; end != path.npos; start = end + 1) {
+ end = path.find('/', start);
+ // Next component, excluding the separator
+ auto part = path.substr(start, end - start);
+ if (part.empty() || part == "."sv) {
+ continue;
+ }
+ if (part == ".."sv) {
+ if (result.empty()) {
+ return {};
+ }
+ auto lastPos = result.rfind('/');
+ if (lastPos == result.npos) {
+ result.clear();
+ } else {
+ result.resize(lastPos);
+ }
+ continue;
+ }
+ result += '/';
+ result += part;
+ }
+
+ return result;
+}
+
+std::string_view basename(std::string_view path) {
+ if (path.empty()) {
+ return {};
+ }
+ if (path == "/"sv) {
+ return "/"sv;
+ }
+ auto pos = path.rfind('/');
+ while (!path.empty() && pos == path.size() - 1) {
+ path.remove_suffix(1);
+ pos = path.rfind('/');
+ }
+ if (pos == path.npos) {
+ return path.empty() ? "/"sv : path;
+ }
+ return path.substr(pos + 1);
+}
+
+std::string_view dirname(std::string_view path) {
+ if (path.empty()) {
+ return {};
+ }
+ if (path == "/"sv) {
+ return "/"sv;
+ }
+ const auto pos = path.rfind('/');
+ if (pos == 0) {
+ return "/"sv;
+ }
+ if (pos == path.npos) {
+ return "."sv;
+ }
+ return path.substr(0, pos);
+}
+
+details::CStrWrapper::CStrWrapper(std::string_view sv) {
+ if (sv[sv.size()] == '\0') {
+ mCstr = sv.data();
+ } else {
+ mCopy.emplace(sv);
+ mCstr = mCopy->c_str();
+ }
+}
+
+std::optional<bool> isEmptyDir(std::string_view dir) {
+ const auto d = std::unique_ptr<DIR, decltype(&::closedir)>{::opendir(c_str(dir)), ::closedir};
+ if (!d) {
+ if (errno == EPERM || errno == EACCES) {
+ return std::nullopt;
+ }
+ return false;
+ }
+ while (auto entry = ::readdir(d.get())) {
+ if (entry->d_type != DT_DIR) {
+ return false;
+ }
+ if (entry->d_name != "."sv && entry->d_name != ".."sv) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool startsWith(std::string_view path, std::string_view prefix) {
+ if (!base::StartsWith(path, prefix)) {
+ return false;
+ }
+ return path.size() == prefix.size() || path[prefix.size()] == '/';
+}
+
+} // namespace android::incremental::path
diff --git a/services/incremental/path.h b/services/incremental/path.h
new file mode 100644
index 0000000..a1f4b8e
--- /dev/null
+++ b/services/incremental/path.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace android::incremental::path {
+
+namespace details {
+
+class CStrWrapper {
+public:
+ CStrWrapper(std::string_view sv);
+
+ CStrWrapper(const CStrWrapper&) = delete;
+ void operator=(const CStrWrapper&) = delete;
+ CStrWrapper(CStrWrapper&&) = delete;
+ void operator=(CStrWrapper&&) = delete;
+
+ const char* get() const { return mCstr; }
+ operator const char*() const { return get(); }
+
+private:
+ const char* mCstr;
+ std::optional<std::string> mCopy;
+};
+
+void append_next_path(std::string& res, std::string_view c);
+
+} // namespace details
+
+//
+// An std::map<> comparator that makes all nested paths to be ordered before the parents.
+//
+
+struct PathCharsLess {
+ bool operator()(char l, char r) const;
+};
+
+struct PathLess {
+ using is_transparent = void;
+ bool operator()(std::string_view l, std::string_view r) const;
+};
+
+//
+// Returns a zero-terminated version of a passed string view
+// Only makes a copy if it wasn't zero-terminated already
+// Useful for passing string view parameters to system functions.
+//
+inline details::CStrWrapper c_str(std::string_view sv) {
+ return {sv};
+}
+
+bool isAbsolute(std::string_view path);
+std::string normalize(std::string_view path);
+std::string_view dirname(std::string_view path);
+std::string_view basename(std::string_view path);
+std::optional<bool> isEmptyDir(std::string_view dir);
+bool startsWith(std::string_view path, std::string_view prefix);
+
+template <class... Paths>
+std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
+ std::string result;
+ {
+ using std::size;
+ result.reserve(first.size() + second.size() + 1 + (sizeof...(paths) + ... + size(paths)));
+ }
+ result.assign(first);
+ (details::append_next_path(result, second), ..., details::append_next_path(result, paths));
+ return result;
+}
+
+} // namespace android::incremental::path
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
new file mode 100644
index 0000000..ca1e1a9
--- /dev/null
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -0,0 +1,461 @@
+/*
+ * 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 <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <binder/ParcelFileDescriptor.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <future>
+
+#include "IncrementalService.h"
+#include "Metadata.pb.h"
+#include "ServiceWrappers.h"
+
+using namespace testing;
+using namespace android::incremental;
+using namespace std::literals;
+using testing::_;
+using testing::Invoke;
+using testing::NiceMock;
+
+#undef LOG_TAG
+#define LOG_TAG "IncrementalServiceTest"
+
+using namespace android::incfs;
+using namespace android::content::pm;
+
+namespace android::os::incremental {
+
+class MockVoldService : public VoldServiceWrapper {
+public:
+ MOCK_CONST_METHOD4(mountIncFs,
+ binder::Status(const std::string& imagePath, const std::string& targetDir,
+ int32_t flags,
+ IncrementalFileSystemControlParcel* _aidl_return));
+ MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir));
+ MOCK_CONST_METHOD2(bindMount,
+ binder::Status(const std::string& sourceDir, const std::string& argetDir));
+
+ void mountIncFsFails() {
+ ON_CALL(*this, mountIncFs(_, _, _, _))
+ .WillByDefault(
+ Return(binder::Status::fromExceptionCode(1, String8("failed to mount"))));
+ }
+ void mountIncFsInvalidControlParcel() {
+ ON_CALL(*this, mountIncFs(_, _, _, _))
+ .WillByDefault(Invoke(this, &MockVoldService::getInvalidControlParcel));
+ }
+ void mountIncFsSuccess() {
+ ON_CALL(*this, mountIncFs(_, _, _, _))
+ .WillByDefault(Invoke(this, &MockVoldService::incFsSuccess));
+ }
+ void bindMountFails() {
+ ON_CALL(*this, bindMount(_, _))
+ .WillByDefault(Return(
+ binder::Status::fromExceptionCode(1, String8("failed to bind-mount"))));
+ }
+ void bindMountSuccess() {
+ ON_CALL(*this, bindMount(_, _)).WillByDefault(Return(binder::Status::ok()));
+ }
+ binder::Status getInvalidControlParcel(const std::string& imagePath,
+ const std::string& targetDir, int32_t flags,
+ IncrementalFileSystemControlParcel* _aidl_return) {
+ _aidl_return->cmd = nullptr;
+ _aidl_return->log = nullptr;
+ return binder::Status::ok();
+ }
+ binder::Status incFsSuccess(const std::string& imagePath, const std::string& targetDir,
+ int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) {
+ _aidl_return->cmd = std::make_unique<os::ParcelFileDescriptor>(std::move(cmdFd));
+ _aidl_return->log = std::make_unique<os::ParcelFileDescriptor>(std::move(logFd));
+ return binder::Status::ok();
+ }
+
+private:
+ TemporaryFile cmdFile;
+ TemporaryFile logFile;
+ base::unique_fd cmdFd;
+ base::unique_fd logFd;
+};
+
+class MockIncrementalManager : public IncrementalManagerWrapper {
+public:
+ MOCK_CONST_METHOD5(prepareDataLoader,
+ binder::Status(int32_t mountId, const FileSystemControlParcel& control,
+ const DataLoaderParamsParcel& params,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return));
+ MOCK_CONST_METHOD2(startDataLoader, binder::Status(int32_t mountId, bool* _aidl_return));
+ MOCK_CONST_METHOD1(destroyDataLoader, binder::Status(int32_t mountId));
+ MOCK_CONST_METHOD3(newFileForDataLoader,
+ binder::Status(int32_t mountId, int64_t inode,
+ const ::std::vector<uint8_t>& metadata));
+ MOCK_CONST_METHOD1(showHealthBlockedUI, binder::Status(int32_t mountId));
+
+ binder::Status prepareDataLoaderOk(int32_t mountId, const FileSystemControlParcel& control,
+ const DataLoaderParamsParcel& params,
+ const sp<IDataLoaderStatusListener>& listener,
+ bool* _aidl_return) {
+ mId = mountId;
+ mListener = listener;
+ *_aidl_return = true;
+ return binder::Status::ok();
+ }
+
+ binder::Status startDataLoaderOk(int32_t mountId, bool* _aidl_return) {
+ *_aidl_return = true;
+ return binder::Status::ok();
+ }
+
+ void prepareDataLoaderFails() {
+ ON_CALL(*this, prepareDataLoader(_, _, _, _, _))
+ .WillByDefault(Return(
+ (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
+ }
+ void prepareDataLoaderSuccess() {
+ ON_CALL(*this, prepareDataLoader(_, _, _, _, _))
+ .WillByDefault(Invoke(this, &MockIncrementalManager::prepareDataLoaderOk));
+ }
+ void startDataLoaderSuccess() {
+ ON_CALL(*this, startDataLoader(_, _))
+ .WillByDefault(Invoke(this, &MockIncrementalManager::startDataLoaderOk));
+ }
+ void setDataLoaderStatusNotReady() {
+ mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+ }
+ void setDataLoaderStatusReady() {
+ mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED);
+ }
+
+private:
+ int mId;
+ sp<IDataLoaderStatusListener> mListener;
+};
+
+class MockIncFs : public IncFsWrapper {
+public:
+ MOCK_CONST_METHOD5(makeFile,
+ Inode(Control control, std::string_view name, Inode parent, Size size,
+ std::string_view metadata));
+ MOCK_CONST_METHOD5(makeDir,
+ Inode(Control control, std::string_view name, Inode parent,
+ std::string_view metadata, int mode));
+ MOCK_CONST_METHOD2(getMetadata, RawMetadata(Control control, Inode inode));
+ MOCK_CONST_METHOD4(link,
+ ErrorCode(Control control, Inode item, Inode targetParent,
+ std::string_view name));
+ MOCK_CONST_METHOD3(unlink, ErrorCode(Control control, Inode parent, std::string_view name));
+ MOCK_CONST_METHOD3(writeBlocks,
+ ErrorCode(Control control, const incfs_new_data_block blocks[],
+ int blocksCount));
+
+ void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); }
+ void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); }
+ RawMetadata getMountInfoMetadata(Control control, Inode inode) {
+ metadata::Mount m;
+ m.mutable_storage()->set_id(100);
+ m.mutable_loader()->set_package_name("com.test");
+ m.mutable_loader()->set_arguments("com.uri");
+ const auto metadata = m.SerializeAsString();
+ m.mutable_loader()->release_arguments();
+ m.mutable_loader()->release_package_name();
+ return std::vector<char>(metadata.begin(), metadata.end());
+ }
+ RawMetadata getStorageMetadata(Control control, Inode inode) {
+ metadata::Storage st;
+ st.set_id(100);
+ auto metadata = st.SerializeAsString();
+ return std::vector<char>(metadata.begin(), metadata.end());
+ }
+ RawMetadata getBindPointMetadata(Control control, Inode inode) {
+ metadata::BindPoint bp;
+ std::string destPath = "dest";
+ std::string srcPath = "src";
+ bp.set_storage_id(100);
+ bp.set_allocated_dest_path(&destPath);
+ bp.set_allocated_source_subdir(&srcPath);
+ const auto metadata = bp.SerializeAsString();
+ bp.release_source_subdir();
+ bp.release_dest_path();
+ return std::vector<char>(metadata.begin(), metadata.end());
+ }
+};
+
+class MockServiceManager : public ServiceManagerWrapper {
+public:
+ MockServiceManager(std::shared_ptr<MockVoldService> vold,
+ std::shared_ptr<MockIncrementalManager> manager,
+ std::shared_ptr<MockIncFs> incfs)
+ : mVold(vold), mIncrementalManager(manager), mIncFs(incfs) {}
+ std::shared_ptr<VoldServiceWrapper> getVoldService() const override { return mVold; }
+ std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const override {
+ return mIncrementalManager;
+ }
+ std::shared_ptr<IncFsWrapper> getIncFs() const override { return mIncFs; }
+
+private:
+ std::shared_ptr<MockVoldService> mVold;
+ std::shared_ptr<MockIncrementalManager> mIncrementalManager;
+ std::shared_ptr<MockIncFs> mIncFs;
+};
+
+// --- IncrementalServiceTest ---
+
+static Inode inode(std::string_view path) {
+ struct stat st;
+ if (::stat(path::c_str(path), &st)) {
+ return -1;
+ }
+ return st.st_ino;
+}
+
+class IncrementalServiceTest : public testing::Test {
+public:
+ void SetUp() override {
+ mVold = std::make_shared<NiceMock<MockVoldService>>();
+ mIncrementalManager = std::make_shared<NiceMock<MockIncrementalManager>>();
+ mIncFs = std::make_shared<NiceMock<MockIncFs>>();
+ MockServiceManager serviceManager = MockServiceManager(mVold, mIncrementalManager, mIncFs);
+ mIncrementalService = std::make_unique<IncrementalService>(serviceManager, mRootDir.path);
+ mDataLoaderParcel.packageName = "com.test";
+ mDataLoaderParcel.arguments = "uri";
+ mIncrementalService->onSystemReady();
+ }
+
+ void setUpExistingMountDir(const std::string& rootDir) {
+ const auto dir = rootDir + "/dir1";
+ const auto mountDir = dir + "/mount";
+ const auto backingDir = dir + "/backing_store";
+ const auto storageDir = mountDir + "/st0";
+ ASSERT_EQ(0, mkdir(dir.c_str(), 0755));
+ ASSERT_EQ(0, mkdir(mountDir.c_str(), 0755));
+ ASSERT_EQ(0, mkdir(backingDir.c_str(), 0755));
+ ASSERT_EQ(0, mkdir(storageDir.c_str(), 0755));
+ const auto mountInfoFile = rootDir + "/dir1/mount/.info";
+ const auto mountPointsFile = rootDir + "/dir1/mount/.mountpoint.abcd";
+ ASSERT_TRUE(base::WriteStringToFile("info", mountInfoFile));
+ ASSERT_TRUE(base::WriteStringToFile("mounts", mountPointsFile));
+ ASSERT_GE(inode(mountInfoFile), 0);
+ ASSERT_GE(inode(mountPointsFile), 0);
+ ON_CALL(*mIncFs, getMetadata(_, inode(mountInfoFile)))
+ .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getMountInfoMetadata));
+ ON_CALL(*mIncFs, getMetadata(_, inode(mountPointsFile)))
+ .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getBindPointMetadata));
+ ON_CALL(*mIncFs, getMetadata(_, inode(rootDir + "/dir1/mount/st0")))
+ .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getStorageMetadata));
+ }
+
+protected:
+ std::shared_ptr<NiceMock<MockVoldService>> mVold;
+ std::shared_ptr<NiceMock<MockIncFs>> mIncFs;
+ std::shared_ptr<NiceMock<MockIncrementalManager>> mIncrementalManager;
+ std::unique_ptr<IncrementalService> mIncrementalService;
+ TemporaryDir mRootDir;
+ DataLoaderParamsParcel mDataLoaderParcel;
+};
+
+/*
+TEST_F(IncrementalServiceTest, testBootMountExistingImagesSuccess) {
+ TemporaryDir tempDir;
+ setUpExistingMountDir(tempDir.path);
+ mVold->mountIncFsSuccess();
+ mVold->bindMountSuccess();
+ mIncrementalManager->prepareDataLoaderSuccess();
+ ON_CALL(*mIncrementalManager, destroyDataLoader(_)).WillByDefault(Return(binder::Status::ok()));
+
+ EXPECT_CALL(*mVold, mountIncFs(_, _, _, _)).Times(1);
+ EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(1);
+
+ MockServiceManager serviceManager = MockServiceManager(mVold, mIncrementalManager, mIncFs);
+ std::unique_ptr<IncrementalService> incrementalService =
+ std::make_unique<IncrementalService>(serviceManager, tempDir.path);
+ auto finished = incrementalService->onSystemReady();
+ if (finished) {
+ finished->wait();
+ }
+}
+*/
+
+TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) {
+ mVold->mountIncFsFails();
+ EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(0);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
+ mVold->mountIncFsInvalidControlParcel();
+ EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(0);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileFails();
+ EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_));
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountFails();
+ EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_));
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mIncrementalManager->prepareDataLoaderFails();
+ EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mIncrementalManager->prepareDataLoaderSuccess();
+ EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ mIncrementalService->deleteStorage(storageId);
+}
+
+TEST_F(IncrementalServiceTest, testOnStatusNotReady) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mIncrementalManager->prepareDataLoaderSuccess();
+ EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ mIncrementalManager->setDataLoaderStatusNotReady();
+}
+
+TEST_F(IncrementalServiceTest, testStartDataLoaderSuccess) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mIncrementalManager->prepareDataLoaderSuccess();
+ mIncrementalManager->startDataLoaderSuccess();
+ EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+ EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ ASSERT_GE(storageId, 0);
+ mIncrementalManager->setDataLoaderStatusReady();
+ ASSERT_TRUE(mIncrementalService->startLoading(storageId));
+}
+
+TEST_F(IncrementalServiceTest, testMakeDirectory) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mIncrementalManager->prepareDataLoaderSuccess();
+ mIncrementalManager->startDataLoaderSuccess();
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ std::string_view dir_path("test");
+ EXPECT_CALL(*mIncFs, makeDir(_, dir_path, _, _, _));
+ int fileIno = mIncrementalService->makeDir(storageId, dir_path, "");
+ ASSERT_GE(fileIno, 0);
+}
+
+TEST_F(IncrementalServiceTest, testMakeDirectoryNoParent) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mIncrementalManager->prepareDataLoaderSuccess();
+ mIncrementalManager->startDataLoaderSuccess();
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ std::string_view first("first");
+ std::string_view second("second");
+ std::string dir_path = std::string(first) + "/" + std::string(second);
+ EXPECT_CALL(*mIncFs, makeDir(_, first, _, _, _)).Times(0);
+ EXPECT_CALL(*mIncFs, makeDir(_, second, _, _, _)).Times(0);
+ int fileIno = mIncrementalService->makeDir(storageId, dir_path, "");
+ ASSERT_LT(fileIno, 0);
+}
+
+TEST_F(IncrementalServiceTest, testMakeDirectories) {
+ mVold->mountIncFsSuccess();
+ mIncFs->makeFileSuccess();
+ mVold->bindMountSuccess();
+ mIncrementalManager->prepareDataLoaderSuccess();
+ mIncrementalManager->startDataLoaderSuccess();
+ TemporaryDir tempDir;
+ int storageId =
+ mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew);
+ std::string_view first("first");
+ std::string_view second("second");
+ std::string_view third("third");
+ InSequence seq;
+ EXPECT_CALL(*mIncFs, makeDir(_, first, _, _, _));
+ EXPECT_CALL(*mIncFs, makeDir(_, second, _, _, _));
+ EXPECT_CALL(*mIncFs, makeDir(_, third, _, _, _));
+ std::string dir_path =
+ std::string(first) + "/" + std::string(second) + "/" + std::string(third);
+ int fileIno = mIncrementalService->makeDirs(storageId, dir_path, "");
+ ASSERT_GE(fileIno, 0);
+}
+} // namespace android::os::incremental
diff --git a/services/incremental/test/path_test.cpp b/services/incremental/test/path_test.cpp
new file mode 100644
index 0000000..cbe479e1
--- /dev/null
+++ b/services/incremental/test/path_test.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "../path.h"
+
+#include <gtest/gtest.h>
+
+using namespace std::literals;
+
+namespace android::incremental::path {
+
+TEST(Path, Normalize) {
+ EXPECT_STREQ("", normalize("").c_str());
+ EXPECT_STREQ("/data/app/com.snapchat.android-evzhnJDgPOq8VcxwEkSY5g==/base.apk",
+ normalize("/data/app/com.snapchat.android-evzhnJDgPOq8VcxwEkSY5g==/base.apk")
+ .c_str());
+ EXPECT_STREQ("/a/b", normalize("/a/c/../b").c_str());
+}
+
+TEST(Path, Comparator) {
+ EXPECT_TRUE(PathLess()("/a", "/aa"));
+ EXPECT_TRUE(PathLess()("/a/b", "/aa/b"));
+ EXPECT_TRUE(PathLess()("/a", "/a/b"));
+ EXPECT_TRUE(PathLess()("/a/b"sv, "/a\0"sv));
+ EXPECT_TRUE(!PathLess()("/aa/b", "/a/b"));
+ EXPECT_TRUE(!PathLess()("/a/b", "/a/b"));
+ EXPECT_TRUE(!PathLess()("/a/b", "/a"));
+}
+
+} // namespace android::incremental::path
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 21cacd45..cfe1318 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
@@ -30,20 +31,20 @@
import android.app.AppCompatCallbacks;
import android.app.INotificationManager;
import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.content.res.Resources.Theme;
import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.database.sqlite.SQLiteGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.net.ConnectivityModuleConnector;
+import android.net.ITetheringConnector;
import android.net.NetworkStackClient;
-import android.net.TetheringManager;
import android.os.BaseBundle;
import android.os.Binder;
import android.os.Build;
@@ -105,10 +106,12 @@
import com.android.server.gpu.GpuService;
import com.android.server.hdmi.HdmiControlService;
import com.android.server.incident.IncidentCompanionService;
+import com.android.server.incremental.IncrementalManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.inputmethod.InputMethodSystemProperty;
import com.android.server.inputmethod.MultiClientInputMethodManagerService;
+import com.android.server.integrity.AppIntegrityManagerService;
import com.android.server.lights.LightsService;
import com.android.server.media.MediaResourceMonitorService;
import com.android.server.media.MediaRouterService;
@@ -125,6 +128,7 @@
import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.CrossProfileAppsService;
+import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
@@ -142,10 +146,12 @@
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.role.RoleManagerService;
import com.android.server.rollback.RollbackManagerService;
+import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.signedconfig.SignedConfigService;
import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
@@ -206,8 +212,8 @@
"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_SERVICE_LIFECYCLE_CLASS =
- "com.android.server.stats.StatsCompanionService$Lifecycle";
+ private static final String STATS_COMPANION_LIFECYCLE_CLASS =
+ "com.android.server.stats.StatsCompanion$Lifecycle";
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
private static final String MIDI_SERVICE_CLASS =
@@ -320,6 +326,8 @@
private PackageManager mPackageManager;
private ContentResolver mContentResolver;
private EntropyMixer mEntropyMixer;
+ private DataLoaderManagerService mDataLoaderManagerService;
+ private IncrementalManagerService mIncrementalManagerService;
private boolean mOnlyCore;
private boolean mFirstBoot;
@@ -492,6 +500,9 @@
// Initialize the system context.
createSystemContext();
+ // Call per-process mainline module initialization.
+ ActivityThread.initializeMainlineModules();
+
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setStartInfo(mRuntimeRestart,
@@ -663,6 +674,13 @@
AppCompatCallbacks.install(new long[0]);
t.traceEnd();
+ // FileIntegrityService responds to requests from apps and the system. It needs to run after
+ // the source (i.e. keystore) is ready, and before the apps (or the first customer in the
+ // system) run.
+ t.traceBegin("StartFileIntegrityService");
+ mSystemServiceManager.startService(FileIntegrityService.class);
+ t.traceEnd();
+
// Wait for installd to finish starting up so that it has a chance to
// create critical directories such as /data/user with the appropriate
// permissions. We need this to complete before we initialize other services.
@@ -693,6 +711,17 @@
mWindowManagerGlobalLock = atm.getGlobalLock();
t.traceEnd();
+ // Data loader manager service needs to be started before package manager
+ t.traceBegin("StartDataLoaderManagerService");
+ mDataLoaderManagerService = mSystemServiceManager.startService(
+ DataLoaderManagerService.class);
+ t.traceEnd();
+
+ // Incremental service needs to be started before package manager
+ t.traceBegin("StartIncrementalManagerService");
+ mIncrementalManagerService = IncrementalManagerService.start(mSystemContext);
+ t.traceEnd();
+
// Power manager needs to be started early because other services need it.
// Native daemons may be watching for it to be registered so it must be ready
// to handle incoming binder calls immediately (including being able to verify
@@ -1129,6 +1158,10 @@
SignedConfigService.registerUpdateReceiver(mSystemContext);
t.traceEnd();
+ t.traceBegin("AppIntegrityService");
+ mSystemServiceManager.startService(AppIntegrityManagerService.class);
+ t.traceEnd();
+
} catch (Throwable e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service");
@@ -1539,6 +1572,10 @@
}
t.traceEnd();
+ t.traceBegin("StartSoundTriggerMiddlewareService");
+ mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ t.traceEnd();
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BROADCAST_RADIO)) {
t.traceBegin("StartBroadcastRadioService");
mSystemServiceManager.startService(BroadcastRadioService.class);
@@ -1934,8 +1971,8 @@
}
// Statsd helper
- t.traceBegin("StartStatsCompanionService");
- mSystemServiceManager.startService(STATS_COMPANION_SERVICE_LIFECYCLE_CLASS);
+ t.traceBegin("StartStatsCompanion");
+ mSystemServiceManager.startService(STATS_COMPANION_LIFECYCLE_CLASS);
t.traceEnd();
// Incidentd and dumpstated helper
@@ -2045,6 +2082,12 @@
mPackageManagerService.systemReady();
t.traceEnd();
+ if (mIncrementalManagerService != null) {
+ t.traceBegin("MakeIncrementalManagerServiceReady");
+ mIncrementalManagerService.systemReady();
+ t.traceEnd();
+ }
+
t.traceBegin("MakeDisplayManagerServiceReady");
try {
// TODO: use boot phase and communicate these flags some other way
@@ -2233,8 +2276,14 @@
t.traceBegin("StartTethering");
try {
- // Tethering must start after ConnectivityService and NetworkStack.
- TetheringManager.getInstance().start();
+ // TODO: hide implementation details, b/146312721.
+ ConnectivityModuleConnector.getInstance().startModuleService(
+ ITetheringConnector.class.getName(),
+ PERMISSION_MAINLINE_NETWORK_STACK, service -> {
+ ServiceManager.addService(Context.TETHERING_SERVICE, service,
+ false /* allowIsolated */,
+ DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+ });
} catch (Throwable e) {
reportWtf("starting Tethering", e);
}
@@ -2379,9 +2428,9 @@
}
private static void startSystemUi(Context context, WindowManagerService windowManager) {
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
Intent intent = new Intent();
- intent.setComponent(new ComponentName("com.android.systemui",
- "com.android.systemui.SystemUIService"));
+ intent.setComponent(pm.getSystemUiServiceComponent());
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3babb0b..9c7cfc1 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -10,14 +10,12 @@
srcs: [
":net-module-utils-srcs",
":services.net-sources",
- ":tethering-manager",
],
static_libs: [
"dnsresolver_aidl_interface-V2-java",
"netd_aidl_interface-unstable-java",
"netlink-client",
"networkstack-client",
- "tethering-client",
],
}
@@ -25,8 +23,6 @@
name: "services-tethering-shared-srcs",
srcs: [
":framework-annotations",
- "java/android/net/ConnectivityModuleConnector.java",
- "java/android/net/NetworkStackClient.java",
"java/android/net/util/NetdService.java",
"java/android/net/util/NetworkConstants.java",
],
diff --git a/services/net/java/android/net/NetworkMonitorManager.java b/services/net/java/android/net/NetworkMonitorManager.java
index 0f41302..0f66981 100644
--- a/services/net/java/android/net/NetworkMonitorManager.java
+++ b/services/net/java/android/net/NetworkMonitorManager.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.Hide;
import android.annotation.NonNull;
import android.os.Binder;
import android.os.RemoteException;
@@ -33,6 +34,7 @@
* wrapper methods in this class return a boolean that callers can use to determine whether
* RemoteException was thrown.
*/
+@Hide
public class NetworkMonitorManager {
@NonNull private final INetworkMonitor mNetworkMonitor;
diff --git a/services/net/java/android/net/TcpKeepalivePacketData.java b/services/net/java/android/net/TcpKeepalivePacketData.java
index 7f2f499..aad75ae 100644
--- a/services/net/java/android/net/TcpKeepalivePacketData.java
+++ b/services/net/java/android/net/TcpKeepalivePacketData.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.net.SocketKeepalive.InvalidPacketException;
import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
index 4b7ed3c..09e333e 100644
--- a/services/net/java/android/net/ip/IpClientManager.java
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -16,6 +16,7 @@
package android.net.ip;
+import android.annotation.Hide;
import android.annotation.NonNull;
import android.net.NattKeepalivePacketData;
import android.net.ProxyInfo;
@@ -38,6 +39,7 @@
* wrapper methods in this class return a boolean that callers can use to determine whether
* RemoteException was thrown.
*/
+@Hide
public class IpClientManager {
@NonNull private final IIpClient mIpClient;
@NonNull private final String mTag;
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index 1e27007..f0758dd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -44,7 +44,6 @@
import android.app.IActivityManager;
import android.app.IUidObserver;
import android.app.usage.UsageStatsManager;
-import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -82,6 +81,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
@@ -690,11 +690,11 @@
entries.add(new OpEntry(
AppOpsManager.OP_ACCESS_NOTIFICATIONS,
AppOpsManager.MODE_IGNORED,
- new Pair[0]));
+ Collections.emptyMap()));
entries.add(new OpEntry(
AppStateTracker.TARGET_OP,
AppOpsManager.MODE_IGNORED,
- new Pair[0]));
+ Collections.emptyMap()));
ops.add(new PackageOps(PACKAGE_1, UID_1, entries));
@@ -703,7 +703,7 @@
entries.add(new OpEntry(
AppStateTracker.TARGET_OP,
AppOpsManager.MODE_IGNORED,
- new Pair[0]));
+ Collections.emptyMap()));
ops.add(new PackageOps(PACKAGE_2, UID_2, entries));
@@ -712,7 +712,7 @@
entries.add(new OpEntry(
AppStateTracker.TARGET_OP,
AppOpsManager.MODE_ALLOWED,
- new Pair[0]));
+ Collections.emptyMap()));
ops.add(new PackageOps(PACKAGE_1, UID_10_1, entries));
@@ -721,11 +721,11 @@
entries.add(new OpEntry(
AppStateTracker.TARGET_OP,
AppOpsManager.MODE_IGNORED,
- new Pair[0]));
+ Collections.emptyMap()));
entries.add(new OpEntry(
AppOpsManager.OP_ACCESS_NOTIFICATIONS,
AppOpsManager.MODE_IGNORED,
- new Pair[0]));
+ Collections.emptyMap()));
ops.add(new PackageOps(PACKAGE_3, UID_10_3, entries));
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 45de451..9251031 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -61,6 +61,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.app.ActivityManagerInternal;
@@ -70,7 +71,11 @@
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.ConnectivityManager;
@@ -131,6 +136,8 @@
@Mock
private PowerManagerInternal mPowerManagerInternal;
@Mock
+ private Sensor mMotionSensor;
+ @Mock
private SensorManager mSensorManager;
class InjectorForTest extends DeviceIdleController.Injector {
@@ -203,6 +210,11 @@
}
@Override
+ Sensor getMotionSensor() {
+ return mMotionSensor;
+ }
+
+ @Override
PowerManager getPowerManager() {
return mPowerManager;
}
@@ -1787,22 +1799,36 @@
}
@Test
- public void testStationaryDetection_QuickDozeOn() {
+ public void testStationaryDetection_QuickDozeOn_NoMotion() {
+ // Short timeout for testing.
+ mConstants.MOTION_INACTIVE_TIMEOUT = 6000L;
+ doReturn(Sensor.REPORTING_MODE_ONE_SHOT).when(mMotionSensor).getReportingMode();
+ doReturn(true).when(mSensorManager)
+ .requestTriggerSensor(eq(mDeviceIdleController.mMotionListener), eq(mMotionSensor));
setAlarmSoon(false);
enterDeepState(STATE_QUICK_DOZE_DELAY);
mDeviceIdleController.stepIdleStateLocked("testing");
verifyStateConditions(STATE_IDLE);
// Quick doze progression through states, so time should have increased appropriately.
mInjector.nowElapsed += mConstants.QUICK_DOZE_DELAY_TIMEOUT;
- final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> motionAlarmListener = ArgumentCaptor
.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> motionRegistrationAlarmListener =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
doNothing().when(mAlarmManager).set(anyInt(), anyLong(), eq("DeviceIdleController.motion"),
- alarmListener.capture(), any());
+ motionAlarmListener.capture(), any());
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(),
+ eq("DeviceIdleController.motion_registration"),
+ motionRegistrationAlarmListener.capture(), any());
StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+ spyOn(stationaryListener);
+ InOrder inOrder = inOrder(stationaryListener);
stationaryListener.motionExpected = true;
mDeviceIdleController.registerStationaryListener(stationaryListener);
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
assertFalse(stationaryListener.isStationary);
// Go to IDLE_MAINTENANCE
@@ -1814,13 +1840,17 @@
mDeviceIdleController.stepIdleStateLocked("testing");
// Now enough time has passed.
- mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT;
stationaryListener.motionExpected = false;
- alarmListener.getValue().onAlarm();
+ motionAlarmListener.getValue().onAlarm();
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(true));
assertTrue(stationaryListener.isStationary);
stationaryListener.motionExpected = true;
- mDeviceIdleController.mMotionListener.onSensorChanged(null);
+ mDeviceIdleController.mMotionListener.onTrigger(null);
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
assertFalse(stationaryListener.isStationary);
// Since we're in quick doze, the device shouldn't stop idling.
@@ -1829,18 +1859,116 @@
// Go to IDLE_MAINTENANCE
mDeviceIdleController.stepIdleStateLocked("testing");
+ motionRegistrationAlarmListener.getValue().onAlarm();
mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
// Back to IDLE
+ stationaryListener.motionExpected = false;
mDeviceIdleController.stepIdleStateLocked("testing");
+ verify(mSensorManager,
+ timeout(mConstants.MOTION_INACTIVE_TIMEOUT).times(2))
+ .requestTriggerSensor(eq(mDeviceIdleController.mMotionListener), eq(mMotionSensor));
// Now enough time has passed.
- mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
- stationaryListener.motionExpected = false;
- alarmListener.getValue().onAlarm();
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT;
+ motionAlarmListener.getValue().onAlarm();
+ inOrder.verify(stationaryListener,
+ timeout(mConstants.MOTION_INACTIVE_TIMEOUT).times(1))
+ .onDeviceStationaryChanged(eq(true));
assertTrue(stationaryListener.isStationary);
}
+ @Test
+ public void testStationaryDetection_QuickDozeOn_OneShot() {
+ // Short timeout for testing.
+ mConstants.MOTION_INACTIVE_TIMEOUT = 6000L;
+ doReturn(Sensor.REPORTING_MODE_ONE_SHOT).when(mMotionSensor).getReportingMode();
+ setAlarmSoon(false);
+ enterDeepState(STATE_QUICK_DOZE_DELAY);
+ mDeviceIdleController.stepIdleStateLocked("testing");
+ verifyStateConditions(STATE_IDLE);
+ // Quick doze progression through states, so time should have increased appropriately.
+ mInjector.nowElapsed += mConstants.QUICK_DOZE_DELAY_TIMEOUT;
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
+ .forClass(AlarmManager.OnAlarmListener.class);
+ doNothing().when(mAlarmManager)
+ .set(anyInt(), anyLong(), eq("DeviceIdleController.motion"), any(), any());
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(),
+ eq("DeviceIdleController.motion_registration"),
+ alarmListener.capture(), any());
+ ArgumentCaptor<TriggerEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(TriggerEventListener.class);
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+ spyOn(stationaryListener);
+ InOrder inOrder = inOrder(stationaryListener, mSensorManager);
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+ assertFalse(stationaryListener.isStationary);
+ inOrder.verify(mSensorManager)
+ .requestTriggerSensor(listenerCaptor.capture(), eq(mMotionSensor));
+ final TriggerEventListener listener = listenerCaptor.getValue();
+
+ // Trigger motion
+ listener.onTrigger(mock(TriggerEvent.class));
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+
+ // Make sure the listener is re-registered.
+ alarmListener.getValue().onAlarm();
+ inOrder.verify(mSensorManager).requestTriggerSensor(eq(listener), eq(mMotionSensor));
+ }
+
+ @Test
+ public void testStationaryDetection_QuickDozeOn_MultiShot() {
+ // Short timeout for testing.
+ mConstants.MOTION_INACTIVE_TIMEOUT = 6000L;
+ doReturn(Sensor.REPORTING_MODE_CONTINUOUS).when(mMotionSensor).getReportingMode();
+ setAlarmSoon(false);
+ enterDeepState(STATE_QUICK_DOZE_DELAY);
+ mDeviceIdleController.stepIdleStateLocked("testing");
+ verifyStateConditions(STATE_IDLE);
+ // Quick doze progression through states, so time should have increased appropriately.
+ mInjector.nowElapsed += mConstants.QUICK_DOZE_DELAY_TIMEOUT;
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
+ .forClass(AlarmManager.OnAlarmListener.class);
+ doNothing().when(mAlarmManager)
+ .set(anyInt(), anyLong(), eq("DeviceIdleController.motion"), any(), any());
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(),
+ eq("DeviceIdleController.motion_registration"),
+ alarmListener.capture(), any());
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+ spyOn(stationaryListener);
+ InOrder inOrder = inOrder(stationaryListener, mSensorManager);
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+ assertFalse(stationaryListener.isStationary);
+ inOrder.verify(mSensorManager)
+ .registerListener(listenerCaptor.capture(), eq(mMotionSensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL));
+ final SensorEventListener listener = listenerCaptor.getValue();
+
+ // Trigger motion
+ listener.onSensorChanged(mock(SensorEvent.class));
+ inOrder.verify(stationaryListener, timeout(1000L).times(1))
+ .onDeviceStationaryChanged(eq(false));
+
+ // Make sure the listener is re-registered.
+ alarmListener.getValue().onAlarm();
+ inOrder.verify(mSensorManager)
+ .registerListener(eq(listener), eq(mMotionSensor),
+ eq(SensorManager.SENSOR_DELAY_NORMAL));
+ }
+
private void enterDeepState(int state) {
switch (state) {
case STATE_ACTIVE:
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 8e6114a..4635c08 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -142,7 +142,7 @@
sService.mConstants = new ActivityManagerConstants(sContext, sService,
sContext.getMainThreadHandler());
ProcessList pr = new ProcessList();
- pr.init(sService, new ActiveUids(sService, false));
+ pr.init(sService, new ActiveUids(sService, false), null);
setFieldValue(ActivityManagerService.class, sService, "mProcessList",
pr);
setFieldValue(ActivityManagerService.class, sService, "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 fa209a7a..529339e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -20,6 +20,7 @@
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_READ_SMS;
import static android.app.AppOpsManager.OP_WIFI_SCAN;
import static android.app.AppOpsManager.OP_WRITE_SMS;
@@ -465,11 +466,11 @@
assertWithMessage("Unexpected mode").that(mode).isEqualTo(opEntry.getMode());
if (minMillis > 0) {
assertWithMessage("Unexpected timestamp")
- .that(opEntry.getTime()).isAtLeast(minMillis);
+ .that(opEntry.getLastAccessTime(OP_FLAGS_ALL)).isAtLeast(minMillis);
}
if (minRejectMillis > 0) {
- assertWithMessage("Unexpected rejection timestamp")
- .that(opEntry.getRejectTime()).isAtLeast(minRejectMillis);
+ assertWithMessage("Unexpected rejection timestamp").that(
+ opEntry.getLastRejectTime(OP_FLAGS_ALL)).isAtLeast(minRejectMillis);
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 3518dc5..9e1ddb1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -120,11 +120,14 @@
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
// This should be public
- assertDisplay(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, false);
- // This should be private
- assertDisplay(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, true);
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+ PORT_A, false);
// This should be public
- assertDisplay(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(), PORT_C, false);
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+ PORT_B, true);
+ // This should be public
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(),
+ PORT_C, false);
}
/**
@@ -141,12 +144,14 @@
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
// This should be public
- assertDisplay(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, false);
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+ PORT_A, false);
// This should be public
- assertDisplay(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_C, false);
+ assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+ PORT_C, false);
}
- private static void assertDisplay(
+ private static void assertDisplayPrivateFlag(
DisplayDeviceInfo info, int expectedPort, boolean shouldBePrivate) {
final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address;
assertNotNull(address);
@@ -155,6 +160,39 @@
assertEquals(shouldBePrivate, (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0);
}
+ /**
+ * Confirm that external display uses physical density.
+ */
+ @Test
+ public void testDpiValues() throws Exception {
+ // needs default one always
+ setUpDisplay(new DisplayConfig(createDisplayAddress(PORT_A), createDummyDisplayInfo()));
+ setUpDisplay(new DisplayConfig(createDisplayAddress(PORT_B), createDummyDisplayInfo()));
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertDisplayDpi(
+ mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100,
+ 16000);
+ assertDisplayDpi(
+ mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100,
+ 16000);
+ }
+
+ private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
+ float expectedXdpi,
+ float expectedYDpi,
+ int expectedDensityDpi) {
+ final DisplayAddress.Physical physical = (DisplayAddress.Physical) info.address;
+ assertNotNull(physical);
+ assertEquals((byte) expectedPort, physical.getPort());
+ assertEquals(expectedXdpi, info.xDpi, 0.01);
+ assertEquals(expectedYDpi, info.yDpi, 0.01);
+ assertEquals(expectedDensityDpi, info.densityDpi);
+ }
+
private class DisplayConfig {
public final DisplayAddress.Physical address;
public final IBinder displayToken = new Binder();
@@ -179,9 +217,8 @@
doReturn(new int[]{
0
}).when(() -> SurfaceControl.getDisplayColorModes(config.displayToken));
- doReturn(new int[]{
- 0
- }).when(() -> SurfaceControl.getAllowedDisplayConfigs(config.displayToken));
+ doReturn(new SurfaceControl.DesiredDisplayConfigSpecs(0, 60.f, 60.f))
+ .when(() -> SurfaceControl.getDesiredDisplayConfigSpecs(config.displayToken));
}
private void updateAvailableDisplays() {
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index cf7919b..015e574f2 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -28,6 +28,7 @@
"services.net",
"services.usage",
"guava",
+ "androidx.test.core",
"androidx.test.runner",
"androidx.test.rules",
"mockito-target-minus-junit4",
@@ -40,7 +41,6 @@
"platformprotosnano",
"hamcrest-library",
"servicestests-utils",
- "xml-writer-device-lib",
"service-appsearch",
"service-jobscheduler",
],
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk
new file mode 100644
index 0000000..6345c98
--- /dev/null
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk
Binary files differ
diff --git a/apex/sdkext/framework/tests/AndroidManifest.xml b/services/tests/servicestests/res/xml/usertypes_test_eraseArray.xml
similarity index 64%
copy from apex/sdkext/framework/tests/AndroidManifest.xml
copy to services/tests/servicestests/res/xml/usertypes_test_eraseArray.xml
index 831f132..02ac48e 100644
--- a/apex/sdkext/framework/tests/AndroidManifest.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_eraseArray.xml
@@ -13,15 +13,12 @@
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.sdkext.tests">
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.sdkext.tests" />
-
-</manifest>
+<user-types>
+ <profile-type
+ name='android.test'
+ max-allowed-per-parent='2' >
+ <badge-colors>
+ </badge-colors>
+ <default-restrictions />
+ </profile-type>
+</user-types>
\ No newline at end of file
diff --git a/apex/sdkext/framework/tests/AndroidManifest.xml b/services/tests/servicestests/res/xml/usertypes_test_full.xml
similarity index 64%
copy from apex/sdkext/framework/tests/AndroidManifest.xml
copy to services/tests/servicestests/res/xml/usertypes_test_full.xml
index 831f132..a281dca 100644
--- a/apex/sdkext/framework/tests/AndroidManifest.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_full.xml
@@ -13,15 +13,13 @@
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.sdkext.tests">
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.sdkext.tests" />
-
-</manifest>
+<user-types>
+ <full-type
+ name='android.test.1'
+ max-allowed-per-parent='12' >
+ <default-restrictions no_remove_user='true' no_bluetooth='true' />
+ <badge-colors>
+ <item res='@*android:color/profile_badge_1' />
+ </badge-colors>
+ </full-type>
+</user-types>
diff --git a/apex/sdkext/framework/tests/AndroidManifest.xml b/services/tests/servicestests/res/xml/usertypes_test_illegalOemName.xml
similarity index 64%
copy from apex/sdkext/framework/tests/AndroidManifest.xml
copy to services/tests/servicestests/res/xml/usertypes_test_illegalOemName.xml
index 831f132..f91df1f 100644
--- a/apex/sdkext/framework/tests/AndroidManifest.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_illegalOemName.xml
@@ -13,15 +13,7 @@
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.sdkext.tests">
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.sdkext.tests" />
-
-</manifest>
+<user-types>
+ <profile-type name='android.aosp.legal' max-allowed-per-parent='12' />
+ <profile-type name='android.oem.illegal.name' max-allowed-per-parent='14' />
+</user-types>
diff --git a/apex/sdkext/framework/tests/AndroidManifest.xml b/services/tests/servicestests/res/xml/usertypes_test_illegalUserBaseType.xml
similarity index 64%
copy from apex/sdkext/framework/tests/AndroidManifest.xml
copy to services/tests/servicestests/res/xml/usertypes_test_illegalUserBaseType.xml
index 831f132..0785655 100644
--- a/apex/sdkext/framework/tests/AndroidManifest.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_illegalUserBaseType.xml
@@ -13,15 +13,6 @@
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.sdkext.tests">
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.sdkext.tests" />
-
-</manifest>
+<user-types>
+ <profile-type name='android.test' max-allowed-per-parent='12' />
+</user-types>
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
new file mode 100644
index 0000000..b6c8fbd
--- /dev/null
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<user-types>
+ <profile-type
+ name='android.test.2'
+ max-allowed-per-parent='12'
+ icon-badge='@*android:drawable/ic_corp_icon_badge_case'
+ badge-plain='garbage'
+ badge-no-background='@*android:drawable/ic_corp_badge_no_background'
+ >
+ <badge-labels>
+ <item res='@*android:string/managed_profile_label_badge' />
+ <item res='@*android:string/managed_profile_label_badge_2' />
+ </badge-labels>
+ <badge-colors>
+ <item res='@*android:color/profile_badge_1' />
+ <item res='@*android:color/profile_badge_2' />
+ </badge-colors>
+ <default-restrictions no_remove_user='true' no_bluetooth='true' />
+ </profile-type>
+ <profile-type name='custom.test.1' max-allowed-per-parent='14' />
+</user-types>
diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
index 192c50c..e9c5ce7 100644
--- a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 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.
@@ -11,28 +11,48 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.server;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+
import android.content.Context;
import android.location.Country;
import android.location.CountryListener;
import android.location.ICountryListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
-import android.test.AndroidTestCase;
-public class CountryDetectorServiceTest extends AndroidTestCase {
- private class CountryListenerTester extends ICountryListener.Stub {
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CountryDetectorServiceTest {
+
+ private static class CountryListenerTester extends ICountryListener.Stub {
private Country mCountry;
@Override
- public void onCountryDetected(Country country) throws RemoteException {
+ public void onCountryDetected(Country country) {
mCountry = country;
}
- public Country getCountry() {
+ Country getCountry() {
return mCountry;
}
@@ -41,12 +61,11 @@
}
}
- private class CountryDetectorServiceTester extends CountryDetectorService {
-
+ private static class CountryDetectorServiceTester extends CountryDetectorService {
private CountryListener mListener;
- public CountryDetectorServiceTester(Context context) {
- super(context);
+ CountryDetectorServiceTester(Context context, Handler handler) {
+ super(context, handler);
}
@Override
@@ -59,51 +78,77 @@
mListener = listener;
}
- public boolean isListenerSet() {
+ boolean isListenerSet() {
return mListener != null;
}
}
- public void testAddRemoveListener() throws RemoteException {
- CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
- serviceTester.systemRunning();
- waitForSystemReady(serviceTester);
- CountryListenerTester listenerTester = new CountryListenerTester();
- serviceTester.addCountryListener(listenerTester);
- assertTrue(serviceTester.isListenerSet());
- serviceTester.removeCountryListener(listenerTester);
- assertFalse(serviceTester.isListenerSet());
- }
+ @Rule
+ public final Expect expect = Expect.create();
+ @Spy
+ private Context mContext = ApplicationProvider.getApplicationContext();
+ @Spy
+ private Handler mHandler = new Handler(Looper.myLooper());
+ private CountryDetectorServiceTester mCountryDetectorService;
- public void testNotifyListeners() throws RemoteException {
- CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
- CountryListenerTester listenerTesterA = new CountryListenerTester();
- CountryListenerTester listenerTesterB = new CountryListenerTester();
- Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
- serviceTester.systemRunning();
- waitForSystemReady(serviceTester);
- serviceTester.addCountryListener(listenerTesterA);
- serviceTester.addCountryListener(listenerTesterB);
- serviceTester.notifyReceivers(country);
- assertTrue(serviceTester.isListenerSet());
- assertTrue(listenerTesterA.isNotified());
- assertTrue(listenerTesterB.isNotified());
- serviceTester.removeCountryListener(listenerTesterA);
- serviceTester.removeCountryListener(listenerTesterB);
- assertFalse(serviceTester.isListenerSet());
- }
-
- private void waitForSystemReady(CountryDetectorService service) {
- int count = 5;
- while (count-- > 0) {
- try {
- Thread.sleep(500);
- } catch (Exception e) {
- }
- if (service.isSystemReady()) {
- return;
- }
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
}
- throw new RuntimeException("Wait System Ready timeout");
+ }
+
+ @Before
+ public void setUp() {
+ mCountryDetectorService = new CountryDetectorServiceTester(mContext, mHandler);
+
+ // Immediately invoke run on the Runnable posted to the handler
+ doAnswer(invocation -> {
+ Message message = invocation.getArgument(0);
+ message.getCallback().run();
+ return true;
+ }).when(mHandler).sendMessageAtTime(any(Message.class), anyLong());
+ }
+
+ @Test
+ public void countryListener_add_successful() throws RemoteException {
+ CountryListenerTester countryListener = new CountryListenerTester();
+
+ mCountryDetectorService.systemRunning();
+ expect.that(mCountryDetectorService.isListenerSet()).isFalse();
+ mCountryDetectorService.addCountryListener(countryListener);
+
+ expect.that(mCountryDetectorService.isListenerSet()).isTrue();
+ }
+
+ @Test
+ public void countryListener_remove_successful() throws RemoteException {
+ CountryListenerTester countryListener = new CountryListenerTester();
+
+ mCountryDetectorService.systemRunning();
+ mCountryDetectorService.addCountryListener(countryListener);
+ expect.that(mCountryDetectorService.isListenerSet()).isTrue();
+ mCountryDetectorService.removeCountryListener(countryListener);
+
+ expect.that(mCountryDetectorService.isListenerSet()).isFalse();
+ }
+
+ @Test
+ public void countryListener_notify_successful() throws RemoteException {
+ CountryListenerTester countryListenerA = new CountryListenerTester();
+ CountryListenerTester countryListenerB = new CountryListenerTester();
+ Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
+
+ mCountryDetectorService.systemRunning();
+ mCountryDetectorService.addCountryListener(countryListenerA);
+ mCountryDetectorService.addCountryListener(countryListenerB);
+ expect.that(countryListenerA.isNotified()).isFalse();
+ expect.that(countryListenerB.isNotified()).isFalse();
+ mCountryDetectorService.notifyReceivers(country);
+
+ expect.that(countryListenerA.isNotified()).isTrue();
+ expect.that(countryListenerB.isNotified()).isTrue();
+ expect.that(countryListenerA.getCountry().equalsIgnoreSource(country)).isTrue();
+ expect.that(countryListenerB.getCountry().equalsIgnoreSource(country)).isTrue();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/GnssManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/GnssManagerServiceTest.java
index 9692c25..8b5444c 100644
--- a/services/tests/servicestests/src/com/android/server/GnssManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GnssManagerServiceTest.java
@@ -61,6 +61,7 @@
import com.android.server.location.GnssNavigationMessageProvider;
import com.android.server.location.GnssNavigationMessageProvider.GnssNavigationMessageProviderNative;
import com.android.server.location.GnssStatusListenerHelper;
+import com.android.server.location.LocationUsageLogger;
import org.junit.Before;
import org.junit.Test;
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 d70e164..96d9c47 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -73,7 +73,7 @@
@Mock private AccessibilityUserState.ServiceInfoChangeListener mMockListener;
- @Mock private Context mContext;
+ @Mock private Context mMockContext;
private MockContentResolver mMockResolver;
@@ -85,11 +85,11 @@
FakeSettingsProvider.clearSettingsProvider();
mMockResolver = new MockContentResolver();
mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(mContext.getContentResolver()).thenReturn(mMockResolver);
+ when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
when(mMockServiceInfo.getComponentName()).thenReturn(COMPONENT_NAME);
when(mMockConnection.getServiceInfo()).thenReturn(mMockServiceInfo);
- mUserState = new AccessibilityUserState(USER_ID, mContext, mMockListener);
+ mUserState = new AccessibilityUserState(USER_ID, mMockContext, mMockListener);
}
@After
@@ -109,11 +109,11 @@
mUserState.setInteractiveUiTimeoutLocked(30);
mUserState.mEnabledServices.add(COMPONENT_NAME);
mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
+ mUserState.mAccessibilityShortcutKeyTargets.add(COMPONENT_NAME.flattenToString());
+ mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
mUserState.setTouchExplorationEnabledLocked(true);
mUserState.setDisplayMagnificationEnabledLocked(true);
mUserState.setNavBarMagnificationEnabledLocked(true);
- mUserState.setServiceAssignedToAccessibilityButtonLocked(COMPONENT_NAME);
- mUserState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
mUserState.setAutoclickEnabledLocked(true);
mUserState.setUserNonInteractiveUiTimeoutLocked(30);
mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -128,11 +128,11 @@
assertEquals(0, mUserState.getInteractiveUiTimeoutLocked());
assertTrue(mUserState.mEnabledServices.isEmpty());
assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty());
+ assertTrue(mUserState.mAccessibilityShortcutKeyTargets.isEmpty());
+ assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
- assertNull(mUserState.getServiceAssignedToAccessibilityButtonLocked());
- assertFalse(mUserState.isNavBarMagnificationAssignedToAccessibilityButtonLocked());
assertFalse(mUserState.isAutoclickEnabledLocked());
assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
@@ -287,6 +287,19 @@
verify(mMockConnection).notifySoftKeyboardShowModeChangedLocked(eq(SHOW_MODE_HIDDEN));
}
+ @Test
+ public void isShortcutTargetInstalledLocked_returnTrue() {
+ mUserState.mInstalledServices.add(mMockServiceInfo);
+ assertTrue(mUserState.isShortcutTargetInstalledLocked(COMPONENT_NAME.flattenToString()));
+ }
+
+ @Test
+ public void isShortcutTargetInstalledLocked_invalidTarget_returnFalse() {
+ final ComponentName invalidTarget =
+ new ComponentName("com.android.server.accessibility", "InvalidTarget");
+ assertFalse(mUserState.isShortcutTargetInstalledLocked(invalidTarget.flattenToString()));
+ }
+
private int getSecureIntForUser(String key, int userId) {
return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId);
}
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 b5e5deb..67075ed 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -705,6 +705,26 @@
assertTrue(displayList.equals(mExpectedDisplayList));
}
+ @Test
+ public void setAccessibilityWindowIdToSurfaceMetadata()
+ throws RemoteException {
+ final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ true, USER_SYSTEM_ID);
+ int windowId = -1;
+ for (int i = 0; i < mA11yWindowTokens.size(); i++) {
+ if (mA11yWindowTokens.valueAt(i).equals(token)) {
+ windowId = mA11yWindowTokens.keyAt(i);
+ }
+ }
+ assertNotEquals("Returned token is not found in mA11yWindowTokens", -1, windowId);
+ verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
+ token.asBinder(), windowId);
+
+ mA11yWindowManager.removeAccessibilityInteractionConnection(token);
+ verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
+ token.asBinder(), -1);
+ }
+
private void startTrackingPerDisplay(int displayId) throws RemoteException {
ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
// Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
similarity index 83%
rename from services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
index b707912..538e2d5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
@@ -24,22 +24,21 @@
import android.accessibilityservice.AccessibilityService;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
-import android.util.DisplayMetrics;
-import android.view.GestureDetector;
import android.view.MotionEvent;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
/**
- * Tests for AccessibilityGestureDetector
+ * Tests for GestureManifold
*/
-public class AccessibilityGestureDetectorTest {
+public class GestureManifoldTest {
// Constants for testRecognizeGesturePath()
private static final PointF PATH_START = new PointF(300f, 300f);
@@ -47,24 +46,21 @@
private static final long PATH_STEP_MILLISEC = 100;
// Data used by all tests
- private AccessibilityGestureDetector mDetector;
- private AccessibilityGestureDetector.Listener mResultListener;
+ private GestureManifold mManifold;
+ private TouchState mState;
+ private GestureManifold.Listener mResultListener;
@Before
public void setUp() {
- // Construct a mock Context.
- DisplayMetrics displayMetricsMock = mock(DisplayMetrics.class);
- displayMetricsMock.xdpi = 500;
- displayMetricsMock.ydpi = 500;
- Resources mockResources = mock(Resources.class);
- when(mockResources.getDisplayMetrics()).thenReturn(displayMetricsMock);
- Context contextMock = mock(Context.class);
- when(contextMock.getResources()).thenReturn(mockResources);
+ Context context = InstrumentationRegistry.getContext();
+ // Construct a testable GestureManifold.
+ mResultListener = mock(GestureManifold.Listener.class);
+ mState = new TouchState();
+ mManifold = new GestureManifold(context, mResultListener, mState);
+ // Play the role of touch explorer in updating the shared state.
+ when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted());
- // Construct a testable AccessibilityGestureDetector.
- mResultListener = mock(AccessibilityGestureDetector.Listener.class);
- GestureDetector doubleTapDetectorMock = mock(GestureDetector.class);
- mDetector = new AccessibilityGestureDetector(contextMock, mResultListener, doubleTapDetectorMock);
+
}
@@ -141,8 +137,8 @@
// For each path step from start (non-inclusive) to end ... add a motion point.
for (int step = 1; step < numSteps; ++step) {
path.add(new PointF(
- (start.x + (stepX * (float) step)),
- (start.y + (stepY * (float) step))));
+ (start.x + (stepX * (float) step)),
+ (start.y + (stepY * (float) step))));
}
}
@@ -170,12 +166,22 @@
point.x, point.y, 0);
// Send event.
- mDetector.onMotionEvent(event, event, policyFlags);
+ mState.onReceivedMotionEvent(event);
+ mManifold.onMotionEvent(event, event, policyFlags);
eventTimeMs += PATH_STEP_MILLISEC;
+ if (mState.isClear()) {
+ mState.startTouchInteracting();
+ }
}
+ mState.clear();
// Check that correct gesture was recognized.
verify(mResultListener).onGestureCompleted(
argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId));
}
+
+ private boolean onGestureStarted() {
+ mState.startGestureDetecting();
+ return false;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 4b1ec6f..a4ceadb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -74,7 +74,7 @@
private TouchExplorer mTouchExplorer;
private long mLastDownTime = Integer.MIN_VALUE;
- // mock package-private AccessibilityGestureDetector class
+ // mock package-private GestureManifold class
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
@@ -108,7 +108,7 @@
public void setUp() {
Context context = InstrumentationRegistry.getContext();
AccessibilityManagerService ams = new AccessibilityManagerService(context);
- AccessibilityGestureDetector detector = mock(AccessibilityGestureDetector.class);
+ GestureManifold detector = mock(GestureManifold.class);
mCaptor = new EventCaptor();
mTouchExplorer = new TouchExplorer(context, ams, detector);
mTouchExplorer.setNext(mCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 49412bc0c..2ce17a1 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -349,10 +349,21 @@
verifyUidRangesNoOverlap(range, range2);
verifyIsolatedUidAllocator(range2);
- // Free both, then try to allocate the maximum number of UID ranges
+ // Free both
allocator.freeUidRangeLocked(appInfo);
allocator.freeUidRangeLocked(appInfo2);
+ // Verify for a secondary user
+ ApplicationInfo appInfo3 = new ApplicationInfo();
+ appInfo3.processName = "com.android.test.app";
+ appInfo3.uid = 1010000;
+ final IsolatedUidRange range3 = allocator.getOrCreateIsolatedUidRangeLocked(
+ appInfo3.processName, appInfo3.uid);
+ validateAppZygoteIsolatedUidRange(range3);
+ verifyIsolatedUidAllocator(range3);
+
+ allocator.freeUidRangeLocked(appInfo3);
+ // Try to allocate the maximum number of UID ranges
int maxNumUidRanges = (Process.LAST_APP_ZYGOTE_ISOLATED_UID
- Process.FIRST_APP_ZYGOTE_ISOLATED_UID + 1) / Process.NUM_UIDS_PER_APP_ZYGOTE;
for (int i = 0; i < maxNumUidRanges; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index a47a567..e90cb46 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -16,7 +16,11 @@
package com.android.server.attention;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
import static com.android.server.attention.AttentionManagerService.ATTENTION_CACHE_BUFFER_SIZE;
+import static com.android.server.attention.AttentionManagerService.DEFAULT_STALE_AFTER_MILLIS;
+import static com.android.server.attention.AttentionManagerService.KEY_STALE_AFTER_MILLIS;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +39,7 @@
import android.os.IPowerManager;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.provider.DeviceConfig;
import android.service.attention.IAttentionCallback;
import android.service.attention.IAttentionService;
@@ -180,6 +185,45 @@
assertThat(buffer.get(0)).isEqualTo(cache);
}
+ @Test
+ public void testGetStaleAfterMillis_handlesGoodFlagValue() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "123", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(123);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_1() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "-123", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_2() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "15000", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_3() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "abracadabra", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
+ @Test
+ public void testGetStaleAfterMillis_handlesBadFlagValue_4() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_STALE_AFTER_MILLIS, "15_000L", false);
+ assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+ DEFAULT_STALE_AFTER_MILLIS);
+ }
+
private class MockIAttentionService implements IAttentionService {
public void checkAttention(IAttentionCallback callback) throws RemoteException {
callback.onSuccess(0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 106a723..d38c80c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -20,6 +20,7 @@
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -65,8 +66,16 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
+ // Dummy test config
+ final String[] config = {
+ "0:2:15", // ID0:Fingerprint:Strong
+ "1:4:15", // ID1:Iris:Strong
+ "2:8:15", // ID2:Face:Strong
+ };
+
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mInjector.getBiometricService()).thenReturn(mBiometricService);
+ when(mInjector.getConfiguration(any())).thenReturn(config);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
.thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(true);
@@ -76,8 +85,7 @@
// TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
@Test
- public void testAuthenticate_callsBiometricServiceAuthenticate() throws
- Exception {
+ public void testAuthenticate_callsBiometricServiceAuthenticate() throws Exception {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
@@ -104,22 +112,25 @@
}
@Test
- public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws
- Exception {
+ public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws Exception {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
final int userId = 0;
final int expectedResult = BIOMETRIC_SUCCESS;
- when(mBiometricService.canAuthenticate(anyString(), anyInt())).thenReturn(expectedResult);
+ final int authenticators = 0;
+ when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt()))
+ .thenReturn(expectedResult);
- final int result = mAuthService.mImpl.canAuthenticate(TEST_OP_PACKAGE_NAME, userId);
+ final int result = mAuthService.mImpl
+ .canAuthenticate(TEST_OP_PACKAGE_NAME, userId, authenticators);
assertEquals(expectedResult, result);
waitForIdle();
verify(mBiometricService).canAuthenticate(
eq(TEST_OP_PACKAGE_NAME),
- eq(userId));
+ eq(userId),
+ eq(authenticators));
}
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 4ced421..f96d996 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,12 +16,14 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertNotNull;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,12 +36,14 @@
import static org.mockito.Mockito.when;
import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
-import android.hardware.biometrics.Authenticator;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricService;
@@ -81,8 +85,6 @@
private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
- private static final int STRENGTH_STRONG = 1;
-
private BiometricService mBiometricService;
@Mock
@@ -101,6 +103,10 @@
IBiometricAuthenticator mFingerprintAuthenticator;
@Mock
IBiometricAuthenticator mFaceAuthenticator;
+ @Mock
+ ITrustManager mTrustManager;
+ @Mock
+ DevicePolicyManager mDevicePolicyManager;
@Before
public void setUp() {
@@ -108,13 +114,18 @@
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
+ .thenReturn(mDevicePolicyManager);
when(mInjector.getActivityManagerService()).thenReturn(mock(IActivityManager.class));
when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
- when(mInjector.getSettingObserver(any(), any(), any())).thenReturn(
- mock(BiometricService.SettingObserver.class));
+ when(mInjector.getSettingObserver(any(), any(), any()))
+ .thenReturn(mock(BiometricService.SettingObserver.class));
when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
+ when(mInjector.getBiometricStrengthController(any()))
+ .thenReturn(mock(BiometricStrengthController.class));
+ when(mInjector.getTrustManager()).thenReturn(mTrustManager);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -122,6 +133,56 @@
.thenReturn(ERROR_NOT_RECOGNIZED);
when(mResources.getString(R.string.biometric_error_user_canceled))
.thenReturn(ERROR_USER_CANCELED);
+
+ final String[] config = {
+ "0:2:15", // ID0:Fingerprint:Strong
+ "1:8:15", // ID1:Face:Strong
+ "2:4:255", // ID2:Iris:Weak
+ };
+
+ when(mInjector.getConfiguration(any())).thenReturn(config);
+ }
+
+ @Test
+ public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential()
+ throws Exception {
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL),
+ eq(0 /* vendorCode */));
+ }
+
+ @Test
+ public void testAuthenticate_credentialAllowedAndSetup_callsSystemUI() throws Exception {
+ // When no biometrics are enrolled, but credentials are set up, status bar should be
+ // invoked right away with showAuthenticationDialog
+
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL);
+ waitForIdle();
+
+ assertNull(mBiometricService.mPendingAuthSession);
+ // StatusBar showBiometricDialog invoked
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(0),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
}
@Test
@@ -131,7 +192,7 @@
mBiometricService.onStart();
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -145,12 +206,12 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
- BiometricAuthenticator.TYPE_FINGERPRINT, mFingerprintAuthenticator);
-
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
@@ -159,6 +220,54 @@
}
@Test
+ public void testAuthenticate_notStrongEnough_returnsHardwareNotPresent() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ Authenticators.BIOMETRIC_STRONG);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+ eq(0 /* vendorCode */));
+ }
+
+ @Test
+ public void testAuthenticate_picksStrongIfAvailable() throws Exception {
+ // If both strong and weak are available, and the caller requires STRONG, authentication
+ // is able to proceed.
+
+ final int[] modalities = new int[] {
+ BiometricAuthenticator.TYPE_FINGERPRINT,
+ BiometricAuthenticator.TYPE_FACE,
+ };
+
+ final int[] strengths = new int[] {
+ Authenticators.BIOMETRIC_WEAK,
+ Authenticators.BIOMETRIC_STRONG,
+ };
+
+ setupAuthForMultiple(modalities, strengths);
+
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
+ waitForIdle();
+ verify(mReceiver1, never()).onError(
+ anyInt(),
+ anyInt(),
+ anyInt() /* vendorCode */);
+
+ // StatusBar showBiometricDialog invoked with face, which was set up to be STRONG
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(false) /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
+ }
+
+ @Test
public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws
Exception {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
@@ -166,11 +275,12 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
- BiometricAuthenticator.TYPE_FINGERPRINT, mFingerprintAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -181,18 +291,12 @@
@Test
public void testAuthenticateFace_respectsUserSetting()
throws Exception {
- when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
-
- mBiometricService = new BiometricService(mContext, mInjector);
- mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
- BiometricAuthenticator.TYPE_FACE, mFaceAuthenticator);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
// Disabled in user settings receives onError
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_NONE),
@@ -205,7 +309,7 @@
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
.thenReturn(true);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
@@ -225,7 +329,7 @@
when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
.thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
eq(false) /* requireConfirmation */,
@@ -242,11 +346,11 @@
@Test
public void testAuthenticate_happyPathWithoutConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
// Start testing the happy path
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
+ null /* authenticators */);
waitForIdle();
// Creates a pending auth session with the correct initial states
@@ -314,15 +418,16 @@
@Test
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- true /* requireConfirmation */, true /* allowDeviceCredential */);
+ true /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
waitForIdle();
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
- assertEquals(Authenticator.TYPE_CREDENTIAL,
+ assertEquals(Authenticators.DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mBundle
.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -336,9 +441,9 @@
@Test
public void testAuthenticate_happyPathWithConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- true /* requireConfirmation */, false /* allowDeviceCredential */);
+ true /* requireConfirmation */, null /* authenticators */);
// Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not
// sent to KeyStore yet
@@ -362,9 +467,9 @@
@Test
public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onAuthenticationFailed();
waitForIdle();
@@ -381,9 +486,9 @@
@Test
public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onAuthenticationFailed();
waitForIdle();
@@ -398,53 +503,10 @@
}
@Test
- public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws
- Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
- invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
-
- // Create a new pending auth session but don't start it yet. HAL contract is that previous
- // one must get ERROR_CANCELED. Simulate that here by creating the pending auth session,
- // sending ERROR_CANCELED to the current auth session, and then having the second one
- // onReadyForAuthentication.
- invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */,
- false /* allowDeviceCredential */);
- waitForIdle();
-
- assertEquals(mBiometricService.mCurrentAuthSession.mState,
- BiometricService.STATE_AUTH_STARTED);
- mBiometricService.mInternalReceiver.onError(
- getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
- BiometricAuthenticator.TYPE_FINGERPRINT,
- BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
- waitForIdle();
-
- // Auth session doesn't become null until SystemUI responds that the animation is completed
- assertNotNull(mBiometricService.mCurrentAuthSession);
- // ERROR_CANCELED is not sent until SystemUI responded that animation is completed
- verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
- verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
-
- // SystemUI dialog closed
- verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
-
- // After SystemUI notifies that the animation has completed
- mBiometricService.mInternalReceiver
- .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
- waitForIdle();
- verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FINGERPRINT),
- eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
- eq(0 /* vendorCode */));
- assertNull(mBiometricService.mCurrentAuthSession);
- }
-
- @Test
public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -491,9 +553,9 @@
@Test
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -524,9 +586,9 @@
// For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI
// until SystemUI notifies us that the dialog is dismissed at which point the current
// session is done.
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -558,9 +620,10 @@
@Test
public void testErrorFromHal_whilePreparingAuthentication_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, true /* allowDeviceCredential */);
+ false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
waitForIdle();
mBiometricService.mInternalReceiver.onError(
@@ -576,7 +639,7 @@
assertNotNull(mBiometricService.mCurrentAuthSession);
assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mState);
- assertEquals(Authenticator.TYPE_CREDENTIAL,
+ assertEquals(Authenticators.DEVICE_CREDENTIAL,
mBiometricService.mCurrentAuthSession.mBundle.getInt(
BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -591,9 +654,9 @@
@Test
public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
waitForIdle();
mBiometricService.mInternalReceiver.onError(
@@ -609,58 +672,64 @@
}
@Test
- public void testCombineAuthenticatorBundle_keyAllowDeviceCredentialAlwaysRemoved() {
- Bundle bundle;
- int authenticators;
+ public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
+ final boolean allowDeviceCredential = false;
+ final @Authenticators.Types int authenticators =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+ final Bundle bundle = new Bundle();
- // In:
- // KEY_ALLOW_DEVICE_CREDENTIAL = true
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- // Out:
- // KEY_ALLOW_DEVICE_CREDENTIAL = null
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- bundle = new Bundle();
- bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
- authenticators = Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC;
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
Utils.combineAuthenticatorBundles(bundle);
- assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
- assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
- // In:
- // KEY_ALLOW_DEVICE_CREDENTIAL = true
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC
- // Out:
- // KEY_ALLOW_DEVICE_CREDENTIAL = null
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- bundle = new Bundle();
- bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
- authenticators = Authenticator.TYPE_BIOMETRIC;
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andKeyAuthenticators() {
+ final @Authenticators.Types int authenticators =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+ final Bundle bundle = new Bundle();
+
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
Utils.combineAuthenticatorBundles(bundle);
- assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
- assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
- // In:
- // KEY_ALLOW_DEVICE_CREDENTIAL = null
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- // Out:
- // KEY_ALLOW_DEVICE_CREDENTIAL = null
- // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
- bundle = new Bundle();
- authenticators = Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL;
- bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- Utils.combineAuthenticatorBundles(bundle);
assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
- assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andNoKeyAuthenticators() {
+ final boolean allowDeviceCredential = true;
+ final Bundle bundle = new Bundle();
+
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andNoKeyAuthenticators() {
+ final Bundle bundle = new Bundle();
+
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+ Authenticators.BIOMETRIC_WEAK);
}
@Test
public void testErrorFromHal_whileShowingDeviceCredential_doesntNotifySystemUI()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, true /* allowDeviceCredential */);
+ false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
mBiometricService.mInternalReceiver.onDeviceCredentialPressed();
waitForIdle();
@@ -683,9 +752,10 @@
@Test
public void testLockout_whileAuthenticating_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, true /* allowDeviceCredential */);
+ false /* requireConfirmation */,
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
assertEquals(BiometricService.STATE_AUTH_STARTED,
mBiometricService.mCurrentAuthSession.mState);
@@ -707,9 +777,9 @@
@Test
public void testLockout_whenAuthenticating_credentialNotAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
assertEquals(BiometricService.STATE_AUTH_STARTED,
mBiometricService.mCurrentAuthSession.mState);
@@ -732,9 +802,9 @@
@Test
public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver
.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
@@ -755,9 +825,9 @@
@Test
public void testDismissedReasonNegative_whilePaused_doesntInvokeHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -781,9 +851,9 @@
@Test
public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws
Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onError(
getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -806,9 +876,9 @@
@Test
public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- true /* requireConfirmation */, false /* allowDeviceCredential */);
+ true /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onAuthenticationSucceeded(
true /* requireConfirmation */,
@@ -835,9 +905,9 @@
@Test
public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
- false /* requireConfirmation */, false /* allowDeviceCredential */);
+ false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mInternalReceiver.onAcquired(
FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
@@ -851,26 +921,415 @@
BiometricService.STATE_AUTH_STARTED);
}
+ @Test
+ public void testCancel_whenAuthenticating() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, null /* authenticators */);
+
+ mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
+ TEST_PACKAGE_NAME);
+ waitForIdle();
+
+ // Pretend that the HAL has responded to cancel with ERROR_CANCELED
+ mBiometricService.mInternalReceiver.onError(getCookieForCurrentSession(
+ mBiometricService.mCurrentAuthSession), BiometricAuthenticator.TYPE_FINGERPRINT,
+ BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
+ waitForIdle();
+
+ // Hides system dialog and invokes the onError callback
+ verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
+ eq(0 /* vendorCode */));
+ verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+ }
+
+ @Test
+ public void testCanAuthenticate_whenDeviceHasRequestedBiometricStrength() throws Exception {
+ // When only biometric is requested, and sensor is strong enough
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+ }
+
+ @Test
+ public void testCanAuthenticate_whenDeviceDoesNotHaveRequestedBiometricStrength()
+ throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+
+ // When only biometric is requested, and sensor is not strong enough
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested, and sensor is not strong enough
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ }
+
+ @Test
+ public void testCanAuthenticate_onlyCredentialRequested() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ // Credential requested but not set up
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+ invokeCanAuthenticate(mBiometricService, Authenticators.DEVICE_CREDENTIAL));
+
+ // Credential requested and set up
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.DEVICE_CREDENTIAL));
+ }
+
+ @Test
+ public void testCanAuthenticate_whenNoBiometricsEnrolled() throws Exception {
+ // With credential set up, test the following.
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ false /* enrolled */);
+
+ // When only biometric is requested
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ }
+
+ @Test
+ public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+
+ // When only biometric is requested
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ }
+
+ @Test
+ public void testCanAuthenticate_whenNoBiometricSensor() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ // When only biometric is requested
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested, and credential is not set up
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+
+ // When credential and biometric are requested, and credential is set up
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ }
+
+ @Test
+ public void testAuthenticatorActualStrength() {
+ // Tuple of OEM config, updatedStrength, and expectedStrength
+ final int[][] testCases = {
+ // Downgrades to the specified strength
+ {Authenticators.BIOMETRIC_STRONG, Authenticators.BIOMETRIC_WEAK,
+ Authenticators.BIOMETRIC_WEAK},
+
+ // Cannot be upgraded
+ {Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_STRONG,
+ Authenticators.BIOMETRIC_WEAK},
+
+ // Downgrades to convenience
+ {Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_CONVENIENCE,
+ Authenticators.BIOMETRIC_CONVENIENCE},
+
+ // EMPTY_SET does not modify specified strength
+ {Authenticators.BIOMETRIC_WEAK, Authenticators.EMPTY_SET,
+ Authenticators.BIOMETRIC_WEAK},
+ };
+
+ for (int i = 0; i < testCases.length; i++) {
+ final BiometricService.AuthenticatorWrapper authenticator =
+ new BiometricService.AuthenticatorWrapper(0 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT,
+ testCases[i][0],
+ null /* impl */);
+ authenticator.updateStrength(testCases[i][1]);
+ assertEquals(testCases[i][2], authenticator.getActualStrength());
+ }
+ }
+
+ @Test
+ public void testWithDowngradedAuthenticator() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ final int testId = 0;
+
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+ .thenReturn(true);
+ when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ mBiometricService.mImpl.registerAuthenticator(testId /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mFingerprintAuthenticator);
+
+ // Downgrade the authenticator
+ for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) {
+ if (wrapper.id == testId) {
+ wrapper.updateStrength(Authenticators.BIOMETRIC_WEAK);
+ }
+ }
+
+ // STRONG-only auth is not available
+ int authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+ authenticators);
+ waitForIdle();
+ verify(mReceiver1).onError(
+ eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+ eq(0) /* vendorCode */);
+
+ // Request for weak auth works
+ resetReceiver();
+ authenticators = Authenticators.BIOMETRIC_WEAK;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ authenticators);
+ waitForIdle();
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
+
+ // Requesting strong and credential, when credential is setup
+ resetReceiver();
+ authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+ when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ authenticators);
+ waitForIdle();
+ assertTrue(Utils.isDeviceCredentialAllowed(mBiometricService.mCurrentAuthSession.mBundle));
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_NONE /* biometricModality */),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRegistrationWithDuplicateId_throwsIllegalStateException() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ mBiometricService.mImpl.registerAuthenticator(
+ 0 /* id */, 2 /* modality */, 15 /* strength */,
+ mFingerprintAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(
+ 0 /* id */, 2 /* modality */, 15 /* strength */,
+ mFingerprintAuthenticator);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRegistrationWithUnknownId_throwsIllegalStateException() throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ mBiometricService.mImpl.registerAuthenticator(
+ 100 /* id */, 2 /* modality */, 15 /* strength */,
+ mFingerprintAuthenticator);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testRegistrationWithUnsupportedStrength_throwsIllegalStateException()
+ throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ // Only STRONG and WEAK are supported. Let's enforce that CONVENIENCE cannot be
+ // registered. If there is a compelling reason, we can remove this constraint.
+ mBiometricService.mImpl.registerAuthenticator(
+ 0 /* id */, 2 /* modality */,
+ Authenticators.BIOMETRIC_CONVENIENCE /* strength */,
+ mFingerprintAuthenticator);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
+ throws Exception {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ mBiometricService.mImpl.registerAuthenticator(
+ 0 /* id */, 2 /* modality */,
+ Authenticators.BIOMETRIC_STRONG /* strength */,
+ null /* authenticator */);
+ }
+
+ @Test
+ public void testRegistrationHappyPath_isOk() throws Exception {
+ // This is being tested in many of the other cases, but here's the base case.
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ for (String s : mInjector.getConfiguration(null)) {
+ SensorConfig config = new SensorConfig(s);
+ mBiometricService.mImpl.registerAuthenticator(config.mId, config.mModality,
+ config.mStrength, mFingerprintAuthenticator);
+ }
+ }
+
+ @Test
+ public void testWorkAuthentication_fingerprintWorksIfNotDisabledByDevicePolicyManager()
+ throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mDevicePolicyManager
+ .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
+ .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+ invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1);
+ waitForIdle();
+ assertEquals(mBiometricService.mPendingAuthSession.mState,
+ BiometricService.STATE_AUTH_CALLED);
+ startPendingAuthSession(mBiometricService);
+ waitForIdle();
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_STARTED);
+ }
+
+ @Test
+ public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
+ throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ when(mDevicePolicyManager
+ .getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */))
+ .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+ invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1);
+ waitForIdle();
+ assertEquals(mBiometricService.mPendingAuthSession.mState,
+ BiometricService.STATE_AUTH_CALLED);
+ startPendingAuthSession(mBiometricService);
+ waitForIdle();
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_STARTED);
+ }
+
+ @Test
+ public void testWorkAuthentication_fingerprintFailsIfDisabledByDevicePolicyManager()
+ throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mDevicePolicyManager
+ .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+ invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1);
+ waitForIdle();
+ assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL);
+ }
+
+ @Test
+ public void testWorkAuthentication_faceFailsIfDisabledByDevicePolicyManager() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ when(mDevicePolicyManager
+ .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+ invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1);
+ waitForIdle();
+ assertNotNull(mBiometricService.mCurrentAuthSession);
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL);
+ }
+
// Helper methods
- private void setupAuthForOnly(int modality) throws RemoteException {
+ private int invokeCanAuthenticate(BiometricService service, int authenticators)
+ throws Exception {
+ return service.mImpl.canAuthenticate(TEST_PACKAGE_NAME, 0 /* userId */, authenticators);
+ }
+
+ private void setupAuthForOnly(int modality, int strength) throws Exception {
+ setupAuthForOnly(modality, strength, true /* enrolled */);
+ }
+
+ // TODO: Reconcile the registration strength with the injector
+ private void setupAuthForOnly(int modality, int strength, boolean enrolled) throws Exception {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
- if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
- when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+ .thenReturn(enrolled);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG, modality,
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */, modality, strength,
mFingerprintAuthenticator);
- } else if (modality == BiometricAuthenticator.TYPE_FACE) {
- when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ }
+
+ if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG, modality,
+ mBiometricService.mImpl.registerAuthenticator(1 /* id */, modality, strength,
mFaceAuthenticator);
- } else {
- fail("Unknown modality: " + modality);
+ }
+ }
+
+ // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
+ // all tests.
+ private void setupAuthForMultiple(int[] modalities, int[] strengths) throws RemoteException {
+ mBiometricService = new BiometricService(mContext, mInjector);
+ mBiometricService.onStart();
+
+ when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+
+ assertEquals(modalities.length, strengths.length);
+
+ for (int i = 0; i < modalities.length; i++) {
+ final int modality = modalities[i];
+ final int strength = strengths[i];
+
+ if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+ when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+ .thenReturn(true);
+ when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */, modality, strength,
+ mFingerprintAuthenticator);
+ }
+
+ if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ mBiometricService.mImpl.registerAuthenticator(1 /* id */, modality, strength,
+ mFaceAuthenticator);
+ }
}
}
@@ -885,9 +1344,9 @@
private void invokeAuthenticateAndStart(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
- boolean allowDeviceCredential) throws Exception {
+ Integer authenticators) throws Exception {
// Request auth, creates a pending session
- invokeAuthenticate(service, receiver, requireConfirmation, allowDeviceCredential);
+ invokeAuthenticate(service, receiver, requireConfirmation, authenticators);
waitForIdle();
startPendingAuthSession(mBiometricService);
@@ -908,23 +1367,39 @@
private static void invokeAuthenticate(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
- boolean allowDeviceCredential) throws Exception {
+ Integer authenticators) throws Exception {
service.authenticate(
new Binder() /* token */,
0 /* sessionId */,
0 /* userId */,
receiver,
TEST_PACKAGE_NAME /* packageName */,
- createTestBiometricPromptBundle(requireConfirmation, allowDeviceCredential));
+ createTestBiometricPromptBundle(requireConfirmation, authenticators));
}
- private static Bundle createTestBiometricPromptBundle(boolean requireConfirmation,
- boolean allowDeviceCredential) {
+ private static void invokeAuthenticateForWorkApp(IBiometricService.Stub service,
+ IBiometricServiceReceiver receiver) throws Exception {
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean(BiometricPrompt.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, true);
+ bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
+ service.authenticate(
+ new Binder() /* token */,
+ 0 /* sessionId */,
+ 0 /* userId */,
+ receiver,
+ TEST_PACKAGE_NAME /* packageName */,
+ bundle);
+ }
+
+ private static Bundle createTestBiometricPromptBundle(
+ boolean requireConfirmation,
+ Integer authenticators) {
final Bundle bundle = new Bundle();
bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, requireConfirmation);
- if (allowDeviceCredential) {
- bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
+ if (authenticators != null) {
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
}
return bundle;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/OWNERS b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
new file mode 100644
index 0000000..abe39f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.biometrics;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNull;
+
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class UtilsTest {
+
+ @Test
+ public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
+ final boolean allowDeviceCredential = false;
+ final @Authenticators.Types int authenticators =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+ final Bundle bundle = new Bundle();
+
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andKeyAuthenticators() {
+ final @Authenticators.Types int authenticators =
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+ final Bundle bundle = new Bundle();
+
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andNoKeyAuthenticators() {
+ final boolean allowDeviceCredential = true;
+ final Bundle bundle = new Bundle();
+
+ bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+ Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+ }
+
+ @Test
+ public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andNoKeyAuthenticators() {
+ final Bundle bundle = new Bundle();
+
+ Utils.combineAuthenticatorBundles(bundle);
+
+ assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+ assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+ Authenticators.BIOMETRIC_WEAK);
+ }
+
+ @Test
+ public void testIsDeviceCredentialAllowed_withIntegerFlags() {
+ int authenticators = 0;
+ assertFalse(Utils.isDeviceCredentialAllowed(authenticators));
+
+ authenticators |= Authenticators.DEVICE_CREDENTIAL;
+ assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+
+ authenticators |= Authenticators.BIOMETRIC_WEAK;
+ assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+ }
+
+ @Test
+ public void testIsDeviceCredentialAllowed_withBundle() {
+ Bundle bundle = new Bundle();
+ assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+
+ int authenticators = 0;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+
+ authenticators |= Authenticators.DEVICE_CREDENTIAL;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+
+ authenticators |= Authenticators.BIOMETRIC_WEAK;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+ }
+
+ @Test
+ public void testGetBiometricStrength_removeUnrelatedBits() {
+ // BIOMETRIC_MIN_STRENGTH uses all of the allowed bits for biometric strength, so any other
+ // bits aside from these should be clipped off.
+
+ int authenticators = Integer.MAX_VALUE;
+ assertEquals(Authenticators.BIOMETRIC_WEAK,
+ Utils.getPublicBiometricStrength(authenticators));
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertEquals(Authenticators.BIOMETRIC_WEAK, Utils.getPublicBiometricStrength(bundle));
+ }
+
+ @Test
+ public void testIsBiometricAllowed() {
+ // Only the lowest 8 bits (BIOMETRIC_WEAK mask) are allowed to integrate with the
+ // Biometric APIs
+ Bundle bundle = new Bundle();
+ for (int i = 0; i <= 7; i++) {
+ int authenticators = 1 << i;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertTrue(Utils.isBiometricAllowed(bundle));
+ }
+
+ // The rest of the bits are not allowed to integrate with the public APIs
+ for (int i = 8; i < 32; i++) {
+ int authenticators = 1 << i;
+ bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+ assertFalse(Utils.isBiometricAllowed(bundle));
+ }
+ }
+
+ @Test
+ public void testIsValidAuthenticatorConfig() {
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.EMPTY_SET));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_STRONG));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_WEAK));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_STRONG));
+
+ assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+ | Authenticators.BIOMETRIC_WEAK));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE
+ | Authenticators.DEVICE_CREDENTIAL));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MAX_STRENGTH));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MIN_STRENGTH));
+
+ // The rest of the bits are not allowed to integrate with the public APIs
+ for (int i = 8; i < 32; i++) {
+ final int authenticator = 1 << i;
+ if (authenticator == Authenticators.DEVICE_CREDENTIAL) {
+ continue;
+ }
+ assertFalse(Utils.isValidAuthenticatorConfig(1 << i));
+ }
+ }
+
+ @Test
+ public void testIsAtLeastStrength() {
+ int sensorStrength = Authenticators.BIOMETRIC_STRONG;
+ int requestedStrength = Authenticators.BIOMETRIC_WEAK;
+ assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+ requestedStrength = Authenticators.BIOMETRIC_STRONG;
+ assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+ sensorStrength = Authenticators.BIOMETRIC_WEAK;
+ requestedStrength = Authenticators.BIOMETRIC_STRONG;
+ assertFalse(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+ requestedStrength = Authenticators.BIOMETRIC_WEAK;
+ assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+ }
+
+ @Test
+ public void testBiometricConstantsConversion() {
+ final int[][] testCases = {
+ {BiometricConstants.BIOMETRIC_SUCCESS,
+ BiometricManager.BIOMETRIC_SUCCESS},
+ {BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS,
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED},
+ {BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED},
+ {BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE},
+ {BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT,
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE}
+ };
+
+ for (int i = 0; i < testCases.length; i++) {
+ assertEquals(testCases[i][1],
+ Utils.biometricConstantsToBiometricManager(testCases[i][0]));
+ }
+ }
+}
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 7267976..cb99c11 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -22,15 +22,13 @@
import androidx.test.runner.AndroidJUnit4;
-import com.android.compat.annotation.Change;
-import com.android.compat.annotation.XmlWriter;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.UUID;
@RunWith(AndroidJUnit4.class)
@@ -50,18 +48,10 @@
return dir;
}
- private void writeChangesToFile(Change[] changes, File f) {
- XmlWriter writer = new XmlWriter();
- for (Change change: changes) {
- writer.addChange(change);
- }
- try {
- f.createNewFile();
- writer.write(new FileOutputStream(f));
- } catch (IOException e) {
- throw new RuntimeException(
- "Encountered an error while writing compat config file", e);
- }
+ private void writeToFile(File dir, String filename, String content) throws IOException {
+ OutputStream os = new FileOutputStream(new File(dir, filename));
+ os.write(content.getBytes());
+ os.close();
}
@Test
@@ -173,13 +163,15 @@
}
@Test
- public void testReadConfig() {
- Change[] changes = {new Change(1234L, "MY_CHANGE1", false, 2, null), new Change(1235L,
- "MY_CHANGE2", true, null, "description"), new Change(1236L, "MY_CHANGE3", false,
- null, "")};
+ public void testReadConfig() throws IOException {
+ String configXml = "<config>"
+ + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />"
+ + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />"
+ + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />"
+ + "</config>";
File dir = createTempDir();
- writeChangesToFile(changes, new File(dir.getPath() + "/platform_compat_config.xml"));
+ writeToFile(dir, "platform_compat_config.xml", configXml);
CompatConfig pc = new CompatConfig();
pc.initConfigFromLib(dir);
@@ -191,17 +183,18 @@
}
@Test
- public void testReadConfigMultipleFiles() {
- Change[] changes1 = {new Change(1234L, "MY_CHANGE1", false, 2, null)};
- Change[] changes2 = {new Change(1235L, "MY_CHANGE2", true, null, ""), new Change(1236L,
- "MY_CHANGE3", false, null, null)};
+ public void testReadConfigMultipleFiles() throws IOException {
+ String configXml1 = "<config>"
+ + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />"
+ + "</config>";
+ String configXml2 = "<config>"
+ + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />"
+ + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />"
+ + "</config>";
File dir = createTempDir();
- writeChangesToFile(changes1,
- new File(dir.getPath() + "/libcore_platform_compat_config.xml"));
- writeChangesToFile(changes2,
- new File(dir.getPath() + "/frameworks_platform_compat_config.xml"));
-
+ writeToFile(dir, "libcore_platform_compat_config.xml", configXml1);
+ writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2);
CompatConfig pc = new CompatConfig();
pc.initConfigFromLib(dir);
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 43d8f927..4fcfa32 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -100,7 +100,6 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
-import com.android.server.pm.UserRestrictionsUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
@@ -1162,7 +1161,8 @@
MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM), eq(null),
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(),
eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
@@ -1718,28 +1718,6 @@
assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
UserHandle.USER_SYSTEM));
- // Check that the user restrictions that are enabled by default are set. Then unset them.
- final String[] defaultRestrictions = UserRestrictionsUtils
- .getDefaultEnabledForDeviceOwner().toArray(new String[0]);
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions(defaultRestrictions),
- dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- );
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions(defaultRestrictions),
- dpm.getUserRestrictions(admin1)
- );
- verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(defaultRestrictions),
- eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)
- );
- reset(getServices().userManagerInternal);
-
- for (String restriction : defaultRestrictions) {
- dpm.clearUserRestriction(admin1, restriction);
- }
-
assertNoDeviceOwnerRestrictions();
reset(getServices().userManagerInternal);
@@ -1966,7 +1944,6 @@
// TODO Make sure restrictions are written to the file.
}
- // TODO: (b/138709470) test addUserRestriction as PO of an organization-owned device
public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
final int MANAGED_PROFILE_ADMIN_UID =
@@ -1979,22 +1956,32 @@
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);
+
parentDpm.setCameraDisabled(admin1, true);
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM),
+ eq(MANAGED_PROFILE_USER_ID),
MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
reset(getServices().userManagerInternal);
parentDpm.setCameraDisabled(admin1, false);
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM),
+ eq(MANAGED_PROFILE_USER_ID),
MockUtils.checkUserRestrictions(),
eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
reset(getServices().userManagerInternal);
}
- public void testDefaultEnabledUserRestrictions() throws Exception {
+ public void testNoDefaultEnabledUserRestrictions() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2012,29 +1999,6 @@
assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
UserHandle.USER_SYSTEM));
- // Check that the user restrictions that are enabled by default are set. Then unset them.
- String[] defaultRestrictions = UserRestrictionsUtils
- .getDefaultEnabledForDeviceOwner().toArray(new String[0]);
- assertTrue(defaultRestrictions.length > 0);
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions(defaultRestrictions),
- dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- );
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions(defaultRestrictions),
- dpm.getUserRestrictions(admin1)
- );
- verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(defaultRestrictions),
- eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)
- );
- reset(getServices().userManagerInternal);
-
- for (String restriction : defaultRestrictions) {
- dpm.clearUserRestriction(admin1, restriction);
- }
-
assertNoDeviceOwnerRestrictions();
// Initialize DPMS again and check that the user restriction wasn't enabled again.
@@ -2044,47 +2008,6 @@
assertNotNull(dpms.getDeviceOwnerAdminLocked());
assertNoDeviceOwnerRestrictions();
-
- // Add a new restriction to the default set, initialize DPMS, and check that the restriction
- // is set as it wasn't enabled during setDeviceOwner.
- final String newDefaultEnabledRestriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
- assertFalse(UserRestrictionsUtils
- .getDefaultEnabledForDeviceOwner().contains(newDefaultEnabledRestriction));
- UserRestrictionsUtils
- .getDefaultEnabledForDeviceOwner().add(newDefaultEnabledRestriction);
- try {
- reset(getServices().userManagerInternal);
- initializeDpms();
- assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
- assertNotNull(dpms.getDeviceOwnerAdminLocked());
-
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions(newDefaultEnabledRestriction),
- dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
- );
- DpmTestUtils.assertRestrictions(
- DpmTestUtils.newRestrictions(newDefaultEnabledRestriction),
- dpm.getUserRestrictions(admin1)
- );
- verify(getServices().userManagerInternal, atLeast(1)).setDevicePolicyUserRestrictions(
- eq(UserHandle.USER_SYSTEM),
- MockUtils.checkUserRestrictions(newDefaultEnabledRestriction),
- eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)
- );
- reset(getServices().userManagerInternal);
-
- // Remove the restriction.
- dpm.clearUserRestriction(admin1, newDefaultEnabledRestriction);
-
- // Initialize DPMS again. The restriction shouldn't be enabled for a second time.
- initializeDpms();
- assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
- assertNotNull(dpms.getDeviceOwnerAdminLocked());
- assertNoDeviceOwnerRestrictions();
- } finally {
- UserRestrictionsUtils
- .getDefaultEnabledForDeviceOwner().remove(newDefaultEnabledRestriction);
- }
}
private void assertNoDeviceOwnerRestrictions() {
@@ -3673,6 +3596,45 @@
verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME, 0);
}
+ public void testSetAutoTimeZoneModifiesSetting() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ dpm.setAutoTimeZone(admin1, true);
+ verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 1);
+
+ dpm.setAutoTimeZone(admin1, false);
+ verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 0);
+ }
+
+ public void testSetAutoTimeZoneWithPOOnUser0() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupProfileOwnerOnUser0();
+ dpm.setAutoTimeZone(admin1, true);
+ verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 1);
+
+ dpm.setAutoTimeZone(admin1, false);
+ verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 0);
+ }
+
+ public void testSetAutoTimeZoneFailWithPONotOnUser0() throws Exception {
+ setupProfileOwner();
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.setAutoTimeZone(admin1, false));
+ verify(getServices().settings, never()).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE,
+ 0);
+ }
+
+ public void testSetAutoTimeZoneWithPOOfOrganizationOwnedDevice() throws Exception {
+ setupProfileOwner();
+ configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ dpm.setAutoTimeZone(admin1, true);
+ verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 1);
+
+ dpm.setAutoTimeZone(admin1, false);
+ verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 0);
+ }
+
public void testSetTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -5548,6 +5510,38 @@
mServiceContext.binder.restoreCallingIdentity(ident);
}
+ public void testGetCrossProfilePackages_notSet_returnsEmpty() {
+ setAsProfileOwner(admin1);
+ assertTrue(dpm.getCrossProfilePackages(admin1).isEmpty());
+ }
+
+ public void testGetCrossProfilePackages_notSet_dpmsReinitialized_returnsEmpty() {
+ setAsProfileOwner(admin1);
+
+ initializeDpms();
+
+ assertTrue(dpm.getCrossProfilePackages(admin1).isEmpty());
+ }
+
+ public void testGetCrossProfilePackages_whenSet_returnsEqual() {
+ setAsProfileOwner(admin1);
+ Set<String> packages = Collections.singleton("TEST_PACKAGE");
+
+ dpm.setCrossProfilePackages(admin1, packages);
+
+ assertEquals(packages, dpm.getCrossProfilePackages(admin1));
+ }
+
+ public void testGetCrossProfilePackages_whenSet_dpmsReinitialized_returnsEqual() {
+ setAsProfileOwner(admin1);
+ Set<String> packages = Collections.singleton("TEST_PACKAGE");
+
+ dpm.setCrossProfilePackages(admin1, packages);
+ initializeDpms();
+
+ assertEquals(packages, dpm.getCrossProfilePackages(admin1));
+ }
+
// admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
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 7a2350e..919a3f6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -35,6 +35,7 @@
import android.app.timezonedetector.TimeZoneDetector;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -162,6 +163,8 @@
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
+ when(packageManagerInternal.getSystemUiServiceComponent()).thenReturn(
+ new ComponentName("com.android.systemui", ".Service"));
contentResolver = new MockContentResolver();
contentResolver.addProvider("telephony", new MockContentProvider(realContext) {
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
new file mode 100644
index 0000000..f6c4d3a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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(AndroidJUnit4.class)
+public class AutomaticBrightnessControllerTest {
+
+ private static final int BRIGHTNESS_MIN = 1;
+ private static final int BRIGHTNESS_MAX = 255;
+ private static final int LIGHT_SENSOR_RATE = 20;
+ private static final int INITIAL_LIGHT_SENSOR_RATE = 20;
+ private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 0;
+ private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0;
+ private static final int SHORT_TERM_MODEL_TIMEOUT = 0;
+ private static final float DOZE_SCALE_FACTOR = 0.0f;
+ private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
+
+ private Context mContext;
+ @Mock SensorManager mSensorManager;
+ @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
+ @Mock HysteresisLevels mAmbientBrightnessThresholds;
+ @Mock HysteresisLevels mScreenBrightnessThresholds;
+ @Mock PackageManager mPackageManager;
+ @Mock Handler mNoopHandler;
+
+ private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ private AutomaticBrightnessController setupController(Sensor lightSensor) {
+ AutomaticBrightnessController controller = new AutomaticBrightnessController(
+ new AutomaticBrightnessController.Injector() {
+ @Override
+ public Handler getBackgroundThreadHandler() {
+ return mNoopHandler;
+ }
+ },
+ () -> { }, mContext.getMainLooper(), mSensorManager, lightSensor,
+ mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN,
+ BRIGHTNESS_MAX, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, INITIAL_LIGHT_SENSOR_RATE,
+ BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG,
+ RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds,
+ mScreenBrightnessThresholds, SHORT_TERM_MODEL_TIMEOUT, mPackageManager);
+ controller.setLoggingEnabled(true);
+
+ // Configure the brightness controller and grab an instance of the sensor listener,
+ // through which we can deliver fake (for test) sensor values.
+ controller.configure(true /* enable */, null /* configuration */,
+ 0 /* brightness */, false /* userChangedBrightness */, 0 /* adjustment */,
+ false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+
+ return controller;
+ }
+
+ @Test
+ public void testNoHysteresisAtMinBrightness() throws Exception {
+ Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+ AutomaticBrightnessController controller = setupController(lightSensor);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 5 as a brightness value
+ float lux1 = 100.0f;
+ float normalizedBrightness1 = 0.02f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness1);
+
+ // This is the important bit: When the new brightness is set, make sure the new
+ // brightening threshold is beyond the maximum brightness value...so that we can test that
+ // our threshold clamping works.
+ when(mScreenBrightnessThresholds.getBrighteningThreshold(5)).thenReturn(1.0f);
+
+ // Send new sensor value and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
+ assertEquals(5, controller.getAutomaticScreenBrightness());
+
+
+ // Set up system to return 255 as a brightness value
+ float lux2 = 10.0f;
+ float normalizedBrightness2 = 0.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness2);
+
+ // Send new sensor value and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
+ assertEquals(1, controller.getAutomaticScreenBrightness());
+ }
+
+ @Test
+ public void testNoHysteresisAtMaxBrightness() throws Exception {
+ Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+ AutomaticBrightnessController controller = setupController(lightSensor);
+
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 250 as a brightness value
+ float lux1 = 100.0f;
+ float normalizedBrightness1 = 0.98f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
+ .thenReturn(lux1);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux1), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness1);
+
+ // This is the important bit: When the new brightness is set, make sure the new
+ // brightening threshold is beyond the maximum brightness value...so that we can test that
+ // our threshold clamping works.
+ when(mScreenBrightnessThresholds.getBrighteningThreshold(250)).thenReturn(260.0f);
+
+ // Send new sensor value and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
+ assertEquals(250, controller.getAutomaticScreenBrightness());
+
+
+ // Set up system to return 255 as a brightness value
+ float lux2 = 110.0f;
+ float normalizedBrightness2 = 1.0f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux2))
+ .thenReturn(lux2);
+ when(mBrightnessMappingStrategy.getBrightness(anyFloat(), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness2);
+
+ // Send new sensor value and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
+ assertEquals(255, controller.getAutomaticScreenBrightness());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 4742a73..8d5939a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -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 com.android.server.display;
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 269f918..ebca240 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -78,10 +78,9 @@
int displayId = 0;
// With no votes present, DisplayModeDirector should allow any refresh rate.
- assertEquals(new DisplayModeDirector.DesiredDisplayConfigSpecs(/*defaultModeId=*/60,
- new DisplayModeDirector.RefreshRateRange(0f, Float.POSITIVE_INFINITY),
- intRange(60, 90)),
- createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayConfigSpecs(
+ assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/60,
+ new DisplayModeDirector.RefreshRateRange(0f, Float.POSITIVE_INFINITY)),
+ createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
displayId));
int numPriorities =
@@ -105,11 +104,10 @@
priority, DisplayModeDirector.Vote.forRefreshRates(minFps + i, maxFps - i));
director.injectVotesByDisplay(votesByDisplay);
assertEquals(
- new DisplayModeDirector.DesiredDisplayConfigSpecs(
+ new DisplayModeDirector.DesiredDisplayModeSpecs(
/*defaultModeId=*/minFps + i,
- new DisplayModeDirector.RefreshRateRange(minFps + i, maxFps - i),
- intRange(minFps + i, maxFps - i)),
- director.getDesiredDisplayConfigSpecs(displayId));
+ new DisplayModeDirector.RefreshRateRange(minFps + i, maxFps - i)),
+ director.getDesiredDisplayModeSpecs(displayId));
}
}
@@ -128,10 +126,9 @@
votes.put(DisplayModeDirector.Vote.MIN_PRIORITY,
DisplayModeDirector.Vote.forRefreshRates(70, 80));
director.injectVotesByDisplay(votesByDisplay);
- assertEquals(
- new DisplayModeDirector.DesiredDisplayConfigSpecs(/*defaultModeId=*/70,
- new DisplayModeDirector.RefreshRateRange(70, 80), intRange(70, 80)),
- director.getDesiredDisplayConfigSpecs(displayId));
+ assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/70,
+ new DisplayModeDirector.RefreshRateRange(70, 80)),
+ director.getDesiredDisplayModeSpecs(displayId));
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
new file mode 100644
index 0000000..859dfe3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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.display;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.os.SystemClock;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public final class TestUtils {
+
+ public static SensorEvent createSensorEvent(Sensor sensor, int lux) throws Exception {
+ final Constructor<SensorEvent> constructor =
+ SensorEvent.class.getDeclaredConstructor(int.class);
+ constructor.setAccessible(true);
+ final SensorEvent event = constructor.newInstance(1);
+ event.sensor = sensor;
+ event.values[0] = lux;
+ event.timestamp = SystemClock.elapsedRealtimeNanos();
+ return event;
+ }
+
+
+ public static void setSensorType(Sensor sensor, int type, String strType) throws Exception {
+ Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+ setter.setAccessible(true);
+ setter.invoke(sensor, type);
+ if (strType != null) {
+ Field f = sensor.getClass().getDeclaredField("mStringType");
+ f.setAccessible(true);
+ f.set(sensor, strType);
+ }
+ }
+
+ public static Sensor createSensor(int type, String strType) throws Exception {
+ Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+ constr.setAccessible(true);
+ Sensor sensor = constr.newInstance();
+ setSensorType(sensor, type, strType);
+ return sensor;
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index acf2d0e..2565ae3 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -16,31 +16,19 @@
package com.android.server.display.whitebalance;
-import com.android.internal.R;
-import com.android.server.display.utils.AmbientFilter;
-import com.android.server.display.utils.AmbientFilterStubber;
-import com.google.common.collect.ImmutableList;
-
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
-import org.mockito.stubbing.Answer;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Looper;
@@ -48,15 +36,22 @@
import androidx.test.InstrumentationRegistry;
+import com.android.internal.R;
+import com.android.server.display.TestUtils;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterStubber;
+
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
-import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.util.List;
@RunWith(JUnit4.class)
@@ -84,8 +79,8 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mLightSensor = createSensor(Sensor.TYPE_LIGHT, null);
- mAmbientColorSensor = createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
+ mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, null);
+ mAmbientColorSensor = TestUtils.createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
@@ -482,25 +477,6 @@
}
}
- private void setSensorType(Sensor sensor, int type, String strType) throws Exception {
- Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
- setter.setAccessible(true);
- setter.invoke(sensor, type);
- if (strType != null) {
- Field f = sensor.getClass().getDeclaredField("mStringType");
- f.setAccessible(true);
- f.set(sensor, strType);
- }
- }
-
- private Sensor createSensor(int type, String strType) throws Exception {
- Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
- constr.setAccessible(true);
- Sensor sensor = constr.newInstance();
- setSensorType(sensor, type, strType);
- return sensor;
- }
-
private TypedArray createTypedArray() throws Exception {
TypedArray mockArray = mock(TypedArray.class);
return mockArray;
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
index 6ff4f3b..3e3e535 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
@@ -28,15 +28,14 @@
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.hardware.Sensor;
-import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Looper;
-import android.os.SystemClock;
import androidx.test.InstrumentationRegistry;
+import com.android.server.display.TestUtils;
import com.android.server.display.whitebalance.AmbientSensor.AmbientBrightnessSensor;
import com.android.server.display.whitebalance.AmbientSensor.AmbientColorTemperatureSensor;
@@ -50,9 +49,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -73,8 +69,8 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mLightSensor = createSensor(Sensor.TYPE_LIGHT, null);
- mAmbientColorSensor = createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
+ mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, null);
+ mAmbientColorSensor = TestUtils.createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
@@ -96,7 +92,7 @@
// There should be no issues when we callback the listener, even if there is no callback
// set.
SensorEventListener listener = captor.getValue();
- listener.onSensorChanged(createSensorEvent(mLightSensor, 100));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 100));
}
@Test
@@ -122,7 +118,7 @@
verify(mSensorManagerMock).registerListener(captor.capture(), eq(mLightSensor),
anyInt(), eq(mHandler));
SensorEventListener listener = captor.getValue();
- listener.onSensorChanged(createSensorEvent(mLightSensor, luxValue));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, luxValue));
assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
assertEquals(luxValue, luxReturned[0]);
}
@@ -155,39 +151,8 @@
verify(mSensorManagerMock).registerListener(captor.capture(), eq(mAmbientColorSensor),
anyInt(), eq(mHandler));
SensorEventListener listener = captor.getValue();
- listener.onSensorChanged(createSensorEvent(mAmbientColorSensor, colorTempValue));
+ listener.onSensorChanged(TestUtils.createSensorEvent(mAmbientColorSensor, colorTempValue));
assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
assertEquals(colorTempValue, colorTempReturned[0]);
}
-
- private SensorEvent createSensorEvent(Sensor sensor, int lux) throws Exception {
- final Constructor<SensorEvent> constructor =
- SensorEvent.class.getDeclaredConstructor(int.class);
- constructor.setAccessible(true);
- final SensorEvent event = constructor.newInstance(1);
- event.sensor = sensor;
- event.values[0] = lux;
- event.timestamp = SystemClock.elapsedRealtimeNanos();
- return event;
- }
-
-
- private void setSensorType(Sensor sensor, int type, String strType) throws Exception {
- Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
- setter.setAccessible(true);
- setter.invoke(sensor, type);
- if (strType != null) {
- Field f = sensor.getClass().getDeclaredField("mStringType");
- f.setAccessible(true);
- f.set(sensor, strType);
- }
- }
-
- private Sensor createSensor(int type, String strType) throws Exception {
- Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
- constr.setAccessible(true);
- Sensor sensor = constr.newInstance();
- setSensorType(sensor, type, strType);
- return sensor;
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java b/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java
index 636aa37..2bd4a3a 100644
--- a/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/input/ConfigurationProcessorTest.java
@@ -18,11 +18,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
-import android.util.Pair;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -32,7 +30,7 @@
import org.junit.runner.RunWith;
import java.io.InputStream;
-import java.util.List;
+import java.util.Map;
/**
* Build/Install/Run:
@@ -52,7 +50,7 @@
public void testGetInputPortAssociations() {
final int res = com.android.frameworks.servicestests.R.raw.input_port_associations;
InputStream xml = mContext.getResources().openRawResource(res);
- List<Pair<String, String>> associations = null;
+ Map<String, Integer> associations = null;
try {
associations = ConfigurationProcessor.processInputPortAssociations(xml);
} catch (Exception e) {
@@ -60,8 +58,8 @@
}
assertNotNull(associations);
assertEquals(2, associations.size());
- assertTrue(associations.contains(Pair.create("USB1", "0")));
- assertTrue(associations.contains(Pair.create("USB2", "1")));
+ assertEquals(0, associations.get("USB1").intValue());
+ assertEquals(1, associations.get("USB2").intValue());
}
@Test
@@ -69,7 +67,7 @@
final int res =
com.android.frameworks.servicestests.R.raw.input_port_associations_bad_displayport;
InputStream xml = mContext.getResources().openRawResource(res);
- List<Pair<String, String>> associations = null;
+ Map<String, Integer> associations = null;
try {
associations = ConfigurationProcessor.processInputPortAssociations(xml);
} catch (Exception e) {
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
new file mode 100644
index 0000000..a2376a6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -0,0 +1,395 @@
+/*
+ * 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.integrity;
+
+import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
+import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
+import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.Rule;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.integrity.engine.RuleEvaluationEngine;
+import com.android.server.integrity.model.IntegrityCheckResult;
+import com.android.server.testutils.TestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+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 java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Unit test for {@link com.android.server.integrity.AppIntegrityManagerServiceImpl} */
+@RunWith(AndroidJUnit4.class)
+public class AppIntegrityManagerServiceImplTest {
+ private static final String TEST_DIR = "AppIntegrityManagerServiceImplTest";
+
+ private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+ private static final String VERSION = "version";
+ private static final String TEST_FRAMEWORK_PACKAGE = "com.android.frameworks.servicestests";
+
+ private static final String PACKAGE_NAME = "com.test.app";
+ private static final int VERSION_CODE = 100;
+ private static final String INSTALLER = TEST_FRAMEWORK_PACKAGE;
+ // These are obtained by running the test and checking logcat.
+ private static final String APP_CERT =
+ "949ADC6CB92FF09E3784D6E9504F26F9BEAC06E60D881D55A6A81160F9CD6FD1";
+ private static final String INSTALLER_CERT =
+ "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
+ // We use SHA256 for package names longer than 32 characters.
+ private static final String INSTALLER_SHA256 =
+ "786933C28839603EB48C50B2A688DC6BE52C833627CB2731FF8466A2AE9F94CD";
+
+ @org.junit.Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock PackageManagerInternal mPackageManagerInternal;
+ @Mock Context mMockContext;
+ @Mock Resources mMockResources;
+ @Mock RuleEvaluationEngine mRuleEvaluationEngine;
+ @Mock IntegrityFileManager mIntegrityFileManager;
+ @Mock Handler mHandler;
+
+ private PackageManager mSpyPackageManager;
+ private File mTestApk;
+
+ private final Context mRealContext = InstrumentationRegistry.getTargetContext();
+ // under test
+ private AppIntegrityManagerServiceImpl mService;
+
+ @Before
+ public void setup() throws Exception {
+ mTestApk = File.createTempFile("TestApk", /* suffix= */ null);
+ mTestApk.deleteOnExit();
+ try (InputStream inputStream = mRealContext.getAssets().open(TEST_DIR + "/test.apk")) {
+ Files.copy(inputStream, mTestApk.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ mService =
+ new AppIntegrityManagerServiceImpl(
+ mMockContext,
+ mPackageManagerInternal,
+ mRuleEvaluationEngine,
+ mIntegrityFileManager,
+ mHandler);
+
+ mSpyPackageManager = spy(mRealContext.getPackageManager());
+ // setup mocks to prevent NPE
+ when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {});
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mTestApk.delete();
+ }
+
+ // This is not a test of the class, but more of a safeguard that we don't block any install in
+ // the default case. This is needed because we don't have any emergency kill switch to disable
+ // this component.
+ @Test
+ public void default_allow() throws Exception {
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+ mService = AppIntegrityManagerServiceImpl.create(mMockContext);
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mMockContext, times(2))
+ .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+ Intent intent = makeVerificationIntent();
+
+ broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+
+ // Since we are not mocking handler in this case, we must wait.
+ // 2 seconds should be a sensible timeout.
+ Thread.sleep(2000);
+ verify(mPackageManagerInternal)
+ .setIntegrityVerificationResult(
+ 1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+ }
+
+ @Test
+ public void updateRuleSet_notAuthorized() throws Exception {
+ makeUsSystemApp();
+ Rule rule =
+ new Rule(
+ new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
+ Rule.DENY);
+ TestUtils.assertExpectException(
+ SecurityException.class,
+ "Only system packages specified in config_integrityRuleProviderPackages are"
+ + " allowed to call this method.",
+ () ->
+ mService.updateRuleSet(
+ VERSION,
+ new ParceledListSlice<>(Arrays.asList(rule)),
+ /* statusReceiver= */ null));
+ }
+
+ @Test
+ public void updateRuleSet_notSystemApp() throws Exception {
+ whitelistUsAsRuleProvider();
+ Rule rule =
+ new Rule(
+ new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
+ Rule.DENY);
+ TestUtils.assertExpectException(
+ SecurityException.class,
+ "Only system packages specified in config_integrityRuleProviderPackages are"
+ + " allowed to call this method.",
+ () ->
+ mService.updateRuleSet(
+ VERSION,
+ new ParceledListSlice<>(Arrays.asList(rule)),
+ /* statusReceiver= */ null));
+ }
+
+ @Test
+ public void updateRuleSet_authorized() throws Exception {
+ whitelistUsAsRuleProvider();
+ makeUsSystemApp();
+ Rule rule =
+ new Rule(
+ new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
+ Rule.DENY);
+
+ // no SecurityException
+ mService.updateRuleSet(
+ VERSION, new ParceledListSlice<>(Arrays.asList(rule)), mock(IntentSender.class));
+ }
+
+ @Test
+ public void updateRuleSet_correctMethodCall() throws Exception {
+ whitelistUsAsRuleProvider();
+ makeUsSystemApp();
+ IntentSender mockReceiver = mock(IntentSender.class);
+ List<Rule> rules =
+ Arrays.asList(
+ new Rule(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ PACKAGE_NAME,
+ /* isHashedValue= */ false),
+ Rule.DENY));
+
+ mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
+ runJobInHandler();
+
+ verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
+ assertEquals(STATUS_SUCCESS, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
+ }
+
+ @Test
+ public void updateRuleSet_fail() throws Exception {
+ whitelistUsAsRuleProvider();
+ makeUsSystemApp();
+ doThrow(new IOException()).when(mIntegrityFileManager).writeRules(any(), any(), any());
+ IntentSender mockReceiver = mock(IntentSender.class);
+ List<Rule> rules =
+ Arrays.asList(
+ new Rule(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ PACKAGE_NAME,
+ /* isHashedValue= */ false),
+ Rule.DENY));
+
+ mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
+ runJobInHandler();
+
+ verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
+ assertEquals(STATUS_FAILURE, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
+ }
+
+ @Test
+ public void broadcastReceiverRegistration() throws Exception {
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+
+ verify(mMockContext).registerReceiver(any(), intentFilterCaptor.capture(), any(), any());
+ assertEquals(1, intentFilterCaptor.getValue().countActions());
+ assertEquals(
+ Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION,
+ intentFilterCaptor.getValue().getAction(0));
+ assertEquals(1, intentFilterCaptor.getValue().countDataTypes());
+ assertEquals(PACKAGE_MIME_TYPE, intentFilterCaptor.getValue().getDataType(0));
+ }
+
+ @Test
+ public void handleBroadcast_correctArgs() throws Exception {
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mMockContext)
+ .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+ Intent intent = makeVerificationIntent();
+ when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+
+ broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+ runJobInHandler();
+
+ ArgumentCaptor<AppInstallMetadata> metadataCaptor =
+ ArgumentCaptor.forClass(AppInstallMetadata.class);
+ Map<String, String> allowedInstallers = new HashMap<>();
+ ArgumentCaptor<Map<String, String>> allowedInstallersCaptor =
+ ArgumentCaptor.forClass(allowedInstallers.getClass());
+ verify(mRuleEvaluationEngine)
+ .evaluate(metadataCaptor.capture(), allowedInstallersCaptor.capture());
+ AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
+ allowedInstallers = allowedInstallersCaptor.getValue();
+ assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
+ assertEquals(APP_CERT, appInstallMetadata.getAppCertificate());
+ assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
+ assertEquals(INSTALLER_CERT, appInstallMetadata.getInstallerCertificate());
+ assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
+ assertFalse(appInstallMetadata.isPreInstalled());
+ // These are hardcoded in the test apk
+ assertEquals(2, allowedInstallers.size());
+ assertEquals("cert_1", allowedInstallers.get("store_1"));
+ assertEquals("cert_2", allowedInstallers.get("store_2"));
+ }
+
+ @Test
+ public void handleBroadcast_allow() throws Exception {
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mMockContext)
+ .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+ Intent intent = makeVerificationIntent();
+ when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+
+ broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+ runJobInHandler();
+
+ verify(mPackageManagerInternal)
+ .setIntegrityVerificationResult(
+ 1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+ }
+
+ @Test
+ public void handleBroadcast_reject() throws Exception {
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mMockContext)
+ .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+ when(mRuleEvaluationEngine.evaluate(any(), any()))
+ .thenReturn(
+ IntegrityCheckResult.deny(
+ new Rule(
+ new AtomicFormula.BooleanAtomicFormula(
+ AtomicFormula.PRE_INSTALLED, false),
+ Rule.DENY)));
+ Intent intent = makeVerificationIntent();
+
+ broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+ runJobInHandler();
+
+ verify(mPackageManagerInternal)
+ .setIntegrityVerificationResult(
+ 1, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
+ }
+
+ private void whitelistUsAsRuleProvider() {
+ Resources mockResources = mock(Resources.class);
+ when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
+ .thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE});
+ when(mMockContext.getResources()).thenReturn(mockResources);
+ }
+
+ private void runJobInHandler() {
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ // sendMessageAtTime is the first non-final method in the call chain when "post" is invoked.
+ verify(mHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+ messageCaptor.getValue().getCallback().run();
+ }
+
+ private void makeUsSystemApp() throws Exception {
+ PackageInfo packageInfo =
+ mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE, 0);
+ packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+ doReturn(packageInfo)
+ .when(mSpyPackageManager)
+ .getPackageInfo(eq(TEST_FRAMEWORK_PACKAGE), anyInt());
+ }
+
+ private Intent makeVerificationIntent() throws Exception {
+ Intent intent = new Intent();
+ intent.setDataAndType(Uri.fromFile(mTestApk), PACKAGE_MIME_TYPE);
+ intent.setAction(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
+ intent.putExtra(EXTRA_VERIFICATION_ID, 1);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, PACKAGE_NAME);
+ intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, INSTALLER);
+ intent.putExtra(
+ EXTRA_VERIFICATION_INSTALLER_UID,
+ mRealContext.getPackageManager().getPackageUid(INSTALLER, /* flags= */ 0));
+ intent.putExtra(Intent.EXTRA_VERSION_CODE, VERSION_CODE);
+ return intent;
+ }
+}
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 901277d..2304bc6 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
@@ -47,13 +47,18 @@
import org.junit.runners.JUnit4;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Optional;
@RunWith(JUnit4.class)
public class RuleBinarySerializerTest {
+ private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
+ private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
+
private static final String COMPOUND_FORMULA_START_BITS =
getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
private static final String COMPOUND_FORMULA_END_BITS =
@@ -67,6 +72,9 @@
private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
+ private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
+ private static final String INSTALLER_CERTIFICATE =
+ getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS);
private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
@@ -83,17 +91,28 @@
getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
@Test
- public void testBinaryString_serializeEmptyRule() throws Exception {
- Rule rule = null;
+ public void testBinaryString_serializeNullRules() {
RuleSerializer binarySerializer = new RuleBinarySerializer();
assertExpectException(
RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Null rule can not be serialized",
+ /* expectedExceptionMessageRegex= */
+ "Index buckets cannot be created for null rule list.",
() ->
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty()));
+ binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
+ }
+
+ @Test
+ public void testBinaryString_emptyRules() throws Exception {
+ ByteArrayOutputStream expectedArrayOutputStream = new ByteArrayOutputStream();
+ expectedArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ binarySerializer.serialize(
+ Collections.emptyList(), /* formatVersion= */ Optional.empty(), outputStream);
+
+ assertThat(outputStream.toByteArray()).isEqualTo(expectedArrayOutputStream.toByteArray());
}
@Test
@@ -381,7 +400,7 @@
assertExpectException(
RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Invalid formula type",
+ /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
() ->
binarySerializer.serialize(
Collections.singletonList(rule),
@@ -402,6 +421,165 @@
assertThat(actualRules).isEqualTo(expectedRules);
}
+ @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);
+
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ 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());
+
+
+ // 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));
+
+ assertThat(actualRules).isEqualTo(expectedArrayOutputStream.toByteArray());
+ }
+
+ private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ }
+
+ private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ String packageName) {
+ return START_BIT
+ + COMPOUND_FORMULA_START_BITS
+ + AND
+ + ATOMIC_FORMULA_START_BITS
+ + PACKAGE_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(packageName.length(), VALUE_SIZE_BITS)
+ + getValueBits(packageName)
+ + ATOMIC_FORMULA_START_BITS
+ + INSTALLER_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
+ + getValueBits(SAMPLE_INSTALLER_NAME)
+ + COMPOUND_FORMULA_END_BITS
+ + DENY
+ + END_BIT;
+ }
+
+ private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ certificate,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ }
+
+ private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+ String appCertificate) {
+ return START_BIT
+ + COMPOUND_FORMULA_START_BITS
+ + AND
+ + ATOMIC_FORMULA_START_BITS
+ + APP_CERTIFICATE
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(appCertificate.length(), VALUE_SIZE_BITS)
+ + getValueBits(appCertificate)
+ + ATOMIC_FORMULA_START_BITS
+ + INSTALLER_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
+ + getValueBits(SAMPLE_INSTALLER_NAME)
+ + 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
new file mode 100644
index 0000000..94e11c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -0,0 +1,323 @@
+/*
+ * 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.integrity.serializer;
+
+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;
+import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets;
+import static com.android.server.testutils.TestUtils.assertExpectException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Formula;
+import android.content.integrity.Rule;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
+@RunWith(JUnit4.class)
+public class RuleIndexingDetailsIdentifierTest {
+
+ private static final String SAMPLE_APP_CERTIFICATE = "testcert";
+ private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
+ private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert";
+ private static final String SAMPLE_PACKAGE_NAME = "com.test.package";
+
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME =
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ SAMPLE_PACKAGE_NAME,
+ /* isHashedValue= */ false);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE =
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ SAMPLE_APP_CERTIFICATE,
+ /* isHashedValue= */ false);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME =
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE =
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_CERTIFICATE,
+ SAMPLE_INSTALLER_CERTIFICATE,
+ /* isHashedValue= */ false);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE =
+ new AtomicFormula.IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 12);
+ private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED =
+ new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, /* booleanValue= */
+ true);
+
+
+ private static final Rule RULE_WITH_PACKAGE_NAME =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_PACKAGE_NAME,
+ ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
+ Rule.DENY);
+ private static final Rule RULE_WITH_APP_CERTIFICATE =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_APP_CERTIFICATE,
+ ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
+ Rule.DENY);
+ private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_INSTALLER_NAME,
+ ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)),
+ Rule.DENY);
+
+ private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_VERSION_CODE,
+ ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
+ Rule.DENY);
+
+ @Test
+ public void getIndexType_nullRule() {
+ List<Rule> ruleList = null;
+
+ assertExpectException(
+ IllegalArgumentException.class,
+ /* expectedExceptionMessageRegex= */
+ "Index buckets cannot be created for null rule list.",
+ () -> splitRulesIntoIndexBuckets(ruleList));
+ }
+
+ @Test
+ public void getIndexType_invalidFormula() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(new Rule(getInvalidFormula(), Rule.DENY));
+
+ assertExpectException(
+ IllegalArgumentException.class,
+ /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
+ () -> splitRulesIntoIndexBuckets(ruleList));
+ }
+
+ @Test
+ public void getIndexType_ruleContainingPackageNameFormula() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_PACKAGE_NAME);
+
+ Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ // Verify the resulting map content.
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(NOT_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).containsExactly(RULE_WITH_PACKAGE_NAME);
+ }
+
+ @Test
+ public void getIndexType_ruleContainingAppCertificateFormula() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_APP_CERTIFICATE);
+
+ Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(NOT_INDEXED)).isEmpty();
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).containsExactly(RULE_WITH_APP_CERTIFICATE);
+ }
+
+ @Test
+ public void getIndexType_ruleWithUnindexedCompoundFormula() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
+
+ Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
+ assertThat(result.get(NOT_INDEXED)).containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
+ }
+
+ @Test
+ public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() {
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
+
+ Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
+ assertThat(result.get(NOT_INDEXED)).containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
+ }
+
+ @Test
+ public void getIndexType_negatedRuleContainingPackageNameFormula() {
+ Rule negatedRule =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.NOT,
+ Arrays.asList(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ ATOMIC_FORMULA_WITH_PACKAGE_NAME,
+ ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))),
+ Rule.DENY);
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(negatedRule);
+
+ Map<Integer, List<Rule>> result = splitRulesIntoIndexBuckets(ruleList);
+
+ assertThat(result.keySet())
+ .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
+ assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
+ assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
+ assertThat(result.get(NOT_INDEXED)).containsExactly(negatedRule);
+ }
+
+ @Test
+ public void getIndexType_allRulesTogetherInValidOrder() {
+ Rule packageNameRuleA = getRuleWithPackageName("aaa");
+ Rule packageNameRuleB = getRuleWithPackageName("bbb");
+ Rule packageNameRuleC = getRuleWithPackageName("ccc");
+ Rule certificateRule1 = getRuleWithAppCertificate("cert1");
+ Rule certificateRule2 = getRuleWithAppCertificate("cert2");
+ Rule certificateRule3 = getRuleWithAppCertificate("cert3");
+
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(packageNameRuleB);
+ ruleList.add(packageNameRuleC);
+ ruleList.add(packageNameRuleA);
+ ruleList.add(certificateRule3);
+ ruleList.add(certificateRule2);
+ ruleList.add(certificateRule1);
+ ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
+ ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
+
+ Map<Integer, 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).get(0)).isEqualTo(packageNameRuleA);
+ assertThat(result.get(PACKAGE_NAME_INDEXED).get(1)).isEqualTo(packageNameRuleB);
+ assertThat(result.get(PACKAGE_NAME_INDEXED).get(2)).isEqualTo(packageNameRuleC);
+
+ // We check asserts this way to ensure ordering based on app certificate.
+ assertThat(result.get(APP_CERTIFICATE_INDEXED).get(0)).isEqualTo(certificateRule1);
+ assertThat(result.get(APP_CERTIFICATE_INDEXED).get(1)).isEqualTo(certificateRule2);
+ assertThat(result.get(APP_CERTIFICATE_INDEXED).get(2)).isEqualTo(certificateRule3);
+
+ assertThat(result.get(NOT_INDEXED))
+ .containsExactly(
+ RULE_WITH_INSTALLER_RESTRICTIONS,
+ RULE_WITH_NONSTRING_RESTRICTIONS);
+ }
+
+ private Rule getRuleWithPackageName(String packageName) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
+ Rule.DENY);
+ }
+
+ private Rule getRuleWithAppCertificate(String certificate) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ certificate,
+ /* isHashedValue= */ false),
+ ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
+ Rule.DENY);
+ }
+
+ private Formula getInvalidFormula() {
+ return new Formula() {
+ @Override
+ public boolean isSatisfied(AppInstallMetadata appInstallMetadata) {
+ return false;
+ }
+
+ @Override
+ public int getTag() {
+ return 4;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+
+ @NonNull
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ }
+ };
+ }
+}
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 ad74901..0bb2d44 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
@@ -44,48 +44,66 @@
@RunWith(JUnit4.class)
public class RuleXmlSerializerTest {
+ private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
+ private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
+
@Test
- public void testXmlString_serializeEmptyRule() throws Exception {
- Rule rule = null;
+ public void testXmlString_serializeEmptyRuleList() throws Exception {
RuleSerializer xmlSerializer = new RuleXmlSerializer();
String expectedRules = "<RL />";
byte[] actualRules =
xmlSerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
+ Collections.emptyList(), /* formatVersion= */ Optional.empty());
assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8));
}
@Test
- public void testXmlString_serializeMultipleRules_oneEmpty() throws Exception {
- Rule rule1 = null;
- Rule rule2 =
+ public void testXmlString_serializeMultipleRules_indexingOrderPreserved() 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 AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- "com.app.test",
- /* isHashedValue= */ false),
+ 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);
- RuleSerializer xmlSerializer = new RuleXmlSerializer();
- Map<String, String> packageNameAttrs = new LinkedHashMap<>();
- packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME));
- packageNameAttrs.put("V", "com.app.test");
- packageNameAttrs.put("H", "false");
- String expectedRules =
- "<RL>"
- + generateTagWithAttribute(
- /* tag= */ "R",
- Collections.singletonMap("E", String.valueOf(Rule.DENY)),
- /* closed= */ false)
- + generateTagWithAttribute(
- /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
- + "</R>"
- + "</RL>";
+ RuleSerializer xmlSerializer = new RuleXmlSerializer();
byte[] actualRules =
xmlSerializer.serialize(
- Arrays.asList(rule1, rule2), /* formatVersion= */ Optional.empty());
+ Arrays.asList(
+ installerRule,
+ getRuleWithAppCertificateAndSampleInstallerName(appCert1),
+ getRuleWithPackageNameAndSampleInstallerName(packageNameB),
+ getRuleWithAppCertificateAndSampleInstallerName(appCert3),
+ getRuleWithPackageNameAndSampleInstallerName(packageNameC),
+ getRuleWithAppCertificateAndSampleInstallerName(appCert2),
+ getRuleWithPackageNameAndSampleInstallerName(packageNameA)),
+ /* formatVersion= */ Optional.empty());
+
+ String expectedRules = "<RL>"
+ + getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(packageNameA)
+ + getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(packageNameB)
+ + getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(packageNameC)
+ + getSerializedCompoundRuleWithAppCertificateAndSampleInstallerName(appCert1)
+ + getSerializedCompoundRuleWithAppCertificateAndSampleInstallerName(appCert2)
+ + getSerializedCompoundRuleWithAppCertificateAndSampleInstallerName(appCert3)
+ + getSerializedCompoundRuleWithSampleInstallerNameAndCert()
+ + "</RL>";
assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8));
}
@@ -371,7 +389,7 @@
assertExpectException(
RuleSerializeException.class,
- /* expectedExceptionMessageRegex */ "Invalid formula type",
+ /* expectedExceptionMessageRegex */ "Malformed rule identified.",
() ->
xmlSerializer.serialize(
Collections.singletonList(rule),
@@ -393,6 +411,124 @@
return res.toString();
}
+ private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ }
+
+ private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ String packageName) {
+
+ Map<String, String> packageNameAttrs = new LinkedHashMap<>();
+ packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME));
+ packageNameAttrs.put("V", packageName);
+ packageNameAttrs.put("H", "false");
+
+ Map<String, String> installerNameAttrs = new LinkedHashMap<>();
+ installerNameAttrs.put("K", String.valueOf(AtomicFormula.INSTALLER_NAME));
+ installerNameAttrs.put("V", SAMPLE_INSTALLER_NAME);
+ installerNameAttrs.put("H", "false");
+
+ return generateTagWithAttribute(
+ /* tag= */ "R",
+ Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+ /* closed= */ false)
+ + generateTagWithAttribute(
+ /* tag= */ "OF",
+ Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)),
+ /* closed= */ false)
+ + generateTagWithAttribute(
+ /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
+ + generateTagWithAttribute(
+ /* tag= */ "AF", installerNameAttrs, /* closed= */ true)
+ + "</OF>"
+ + "</R>";
+ }
+
+
+ private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ certificate,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ }
+
+ private String getSerializedCompoundRuleWithAppCertificateAndSampleInstallerName(
+ String appCert) {
+
+ Map<String, String> packageNameAttrs = new LinkedHashMap<>();
+ packageNameAttrs.put("K", String.valueOf(AtomicFormula.APP_CERTIFICATE));
+ packageNameAttrs.put("V", appCert);
+ packageNameAttrs.put("H", "false");
+
+ Map<String, String> installerNameAttrs = new LinkedHashMap<>();
+ installerNameAttrs.put("K", String.valueOf(AtomicFormula.INSTALLER_NAME));
+ installerNameAttrs.put("V", SAMPLE_INSTALLER_NAME);
+ installerNameAttrs.put("H", "false");
+
+ return generateTagWithAttribute(
+ /* tag= */ "R",
+ Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+ /* closed= */ false)
+ + generateTagWithAttribute(
+ /* tag= */ "OF",
+ Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)),
+ /* closed= */ false)
+ + generateTagWithAttribute(
+ /* tag= */ "AF", packageNameAttrs, /* closed= */ true)
+ + generateTagWithAttribute(
+ /* tag= */ "AF", installerNameAttrs, /* closed= */ true)
+ + "</OF>"
+ + "</R>";
+ }
+
+ private String getSerializedCompoundRuleWithSampleInstallerNameAndCert() {
+ Map<String, String> installerNameAttrs = new LinkedHashMap<>();
+ installerNameAttrs.put("K", String.valueOf(AtomicFormula.INSTALLER_NAME));
+ installerNameAttrs.put("V", SAMPLE_INSTALLER_NAME);
+ installerNameAttrs.put("H", "false");
+
+ Map<String, String> installerCertAttrs = new LinkedHashMap<>();
+ installerCertAttrs.put("K", String.valueOf(AtomicFormula.INSTALLER_CERTIFICATE));
+ installerCertAttrs.put("V", SAMPLE_INSTALLER_CERT);
+ installerCertAttrs.put("H", "false");
+
+ return generateTagWithAttribute(
+ /* tag= */ "R",
+ Collections.singletonMap("E", String.valueOf(Rule.DENY)),
+ /* closed= */ false)
+ + generateTagWithAttribute(
+ /* tag= */ "OF",
+ Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)),
+ /* closed= */ false)
+ + generateTagWithAttribute(
+ /* tag= */ "AF", installerNameAttrs, /* closed= */ true)
+ + generateTagWithAttribute(
+ /* tag= */ "AF", installerCertAttrs, /* closed= */ true)
+ + "</OF>"
+ + "</R>";
+ }
+
private Formula getInvalidFormula() {
return new Formula() {
@Override
diff --git a/tests/utils/testutils/java/test/util/MockitoUtils.kt b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
similarity index 92%
rename from tests/utils/testutils/java/test/util/MockitoUtils.kt
rename to services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
index 5151abe..0f915db 100644
--- a/tests/utils/testutils/java/test/util/MockitoUtils.kt
+++ b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package test.util
+package com.android.server.om
import org.mockito.Answers
import org.mockito.Mockito
@@ -22,6 +22,8 @@
import org.mockito.stubbing.Answer
import org.mockito.stubbing.Stubber
+// TODO(chiuwinson): Move this entire file to a shared utility module
+// TODO(b/135203078): De-dupe utils added for overlays vs package refactor
object MockitoUtils {
val ANSWER_THROWS = Answer<Any?> {
when (val name = it.method.name) {
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
index f45316f..ef12948 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -24,8 +24,6 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.testng.Assert.assertThrows
-import test.util.mockThrowOnUnmocked
-import test.util.whenever
@RunWith(Parameterized::class)
class OverlayReferenceMapperTests {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index fb9c68a..40ada2a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -30,6 +30,7 @@
import android.apex.ApexInfo;
import android.apex.ApexSessionInfo;
+import android.apex.ApexSessionParams;
import android.apex.IApexService;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -183,19 +184,18 @@
public void testSubmitStagedSession_throwPackageManagerException() throws RemoteException {
doAnswer(invocation -> {
throw new Exception();
- }).when(mApexService).submitStagedSession(anyInt(), any(), any());
+ }).when(mApexService).submitStagedSession(any(), any());
assertThrows(PackageManagerException.class,
- () -> mApexManager.submitStagedSession(TEST_SESSION_ID, TEST_CHILD_SESSION_ID));
+ () -> mApexManager.submitStagedSession(testParamsWithChildren()));
}
@Test
public void testSubmitStagedSession_throwRunTimeException() throws RemoteException {
- doThrow(RemoteException.class).when(mApexService).submitStagedSession(anyInt(), any(),
- any());
+ doThrow(RemoteException.class).when(mApexService).submitStagedSession(any(), any());
assertThrows(RuntimeException.class,
- () -> mApexManager.submitStagedSession(TEST_SESSION_ID, TEST_CHILD_SESSION_ID));
+ () -> mApexManager.submitStagedSession(testParamsWithChildren()));
}
@Test
@@ -272,6 +272,13 @@
return stagedSessionInfo;
}
+ private static ApexSessionParams testParamsWithChildren() {
+ ApexSessionParams params = new ApexSessionParams();
+ params.sessionId = TEST_SESSION_ID;
+ params.childSessionIds = TEST_CHILD_SESSION_ID;
+ return params;
+ }
+
/**
* Copies a specified {@code resourceId} to a temp file. Returns a non-null file if the copy
* succeeded
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 b751308..c478ec4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -173,6 +173,7 @@
/* createdMillis */ 0L,
/* stageDir */ mTmpDir,
/* stageCid */ null,
+ /* files */ null,
/* prepared */ true,
/* committed */ true,
/* sealed */ false, // Setting to true would trigger some PM logic.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
index ebd3633..1fff4f0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -17,8 +17,7 @@
package com.android.server.pm;
import android.content.pm.PackageManager;
-import com.android.server.pm.PackageVerificationState;
-
+import android.content.pm.PackageManagerInternal;
import android.test.AndroidTestCase;
public class PackageVerificationStateTest extends AndroidTestCase {
@@ -29,7 +28,8 @@
private static final int SUFFICIENT_UID_2 = 8938;
public void testPackageVerificationState_OnlyRequiredVerifier_AllowedInstall() {
- PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
assertFalse("Verification should not be marked as complete yet",
state.isVerificationComplete());
@@ -44,7 +44,8 @@
}
public void testPackageVerificationState_OnlyRequiredVerifier_DeniedInstall() {
- PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
assertFalse("Verification should not be marked as complete yet",
state.isVerificationComplete());
@@ -59,7 +60,8 @@
}
public void testPackageVerificationState_RequiredAndOneSufficient_RequiredDeniedInstall() {
- PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
assertFalse("Verification should not be marked as complete yet",
state.isVerificationComplete());
@@ -84,7 +86,8 @@
}
public void testPackageVerificationState_RequiredAndOneSufficient_SufficientDeniedInstall() {
- PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
assertFalse("Verification should not be marked as complete yet",
state.isVerificationComplete());
@@ -109,7 +112,8 @@
}
public void testPackageVerificationState_RequiredAndTwoSufficient_OneSufficientIsEnough() {
- PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
assertFalse("Verification should not be marked as complete yet",
state.isVerificationComplete());
@@ -135,7 +139,8 @@
}
public void testPackageVerificationState_RequiredAndTwoSufficient_SecondSufficientIsEnough() {
- PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
assertFalse("Verification should not be marked as complete yet",
state.isVerificationComplete());
@@ -166,7 +171,8 @@
}
public void testPackageVerificationState_RequiredAndTwoSufficient_RequiredOverrides() {
- PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
assertFalse("Verification should not be marked as complete yet",
state.isVerificationComplete());
@@ -202,4 +208,55 @@
assertTrue("Installation should be marked as allowed still",
state.isInstallAllowed());
}
+
+ public void testAreAllVerificationsComplete_onlyVerificationPasses() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
+ assertFalse(state.areAllVerificationsComplete());
+
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+ assertFalse(state.areAllVerificationsComplete());
+ }
+
+ public void testAreAllVerificationsComplete_onlyIntegrityCheckPasses() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
+ assertFalse(state.areAllVerificationsComplete());
+
+ state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+
+ assertFalse(state.areAllVerificationsComplete());
+ }
+
+ public void testAreAllVerificationsComplete_bothPasses() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
+ assertFalse(state.areAllVerificationsComplete());
+
+ state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue(state.areAllVerificationsComplete());
+ }
+
+ public void testAreAllVerificationsComplete_onlyVerificationFails() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
+ assertFalse(state.areAllVerificationsComplete());
+
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_REJECT);
+
+ assertFalse(state.areAllVerificationsComplete());
+ }
+
+ public void testAreAllVerificationsComplete_onlyIntegrityCheckFails() {
+ PackageVerificationState state = new PackageVerificationState(null);
+ state.setRequiredVerifierUid(REQUIRED_UID);
+ assertFalse(state.areAllVerificationsComplete());
+
+ state.setIntegrityVerificationResult(PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
+
+ assertFalse(state.areAllVerificationsComplete());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 7aadd87..8e74c90 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -25,24 +25,32 @@
import static android.content.pm.UserInfo.FLAG_RESTRICTED;
import static android.content.pm.UserInfo.FLAG_SYSTEM;
+import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
import android.os.UserManager;
+import android.util.ArrayMap;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.frameworks.servicestests.R;
+
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
-import java.util.Arrays;
-
/**
* Tests for {@link UserTypeDetails} and {@link UserTypeFactory}.
*
@@ -52,9 +60,17 @@
@MediumTest
public class UserManagerServiceUserTypeTest {
+ private Resources mResources;
+
+ @Before
+ public void setup() {
+ mResources = InstrumentationRegistry.getTargetContext().getResources();
+ }
+
@Test
public void testUserTypeBuilder_createUserType() {
- UserTypeDetails type = new UserTypeDetails.Builder()
+ final Bundle restrictions = makeRestrictionsBundle("r1", "r2");
+ final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
.setEnabled(true)
.setMaxAllowed(21)
@@ -67,7 +83,7 @@
.setBadgeNoBackground(30)
.setLabel(31)
.setMaxAllowedPerParent(32)
- .setDefaultRestrictions(new ArrayList<>(Arrays.asList("r1", "r2")))
+ .setDefaultRestrictions(restrictions)
.createUserTypeDetails();
assertEquals("a.name", type.getName());
@@ -79,7 +95,8 @@
assertEquals(30, type.getBadgeNoBackground());
assertEquals(31, type.getLabel());
assertEquals(32, type.getMaxAllowedPerParent());
- assertEquals(new ArrayList<>(Arrays.asList("r1", "r2")), type.getDefaultRestrictions());
+ assertTrue(UserRestrictionsUtils.areEqual(restrictions, type.getDefaultRestrictions()));
+ assertNotSame(restrictions, type.getDefaultRestrictions());
assertEquals(23, type.getBadgeLabel(0));
@@ -106,8 +123,8 @@
.createUserTypeDetails();
assertTrue(type.isEnabled());
- assertEquals(UserTypeDetails.UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowed());
- assertEquals(UserTypeDetails.UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowedPerParent());
+ assertEquals(UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowed());
+ assertEquals(UNLIMITED_NUMBER_OF_USERS, type.getMaxAllowedPerParent());
assertEquals(FLAG_FULL, type.getDefaultUserInfoFlags());
assertEquals(Resources.ID_NULL, type.getIconBadge());
assertEquals(Resources.ID_NULL, type.getBadgePlain());
@@ -185,8 +202,173 @@
UserInfo.getDefaultUserType(FLAG_EPHEMERAL));
}
+ /** Tests {@link UserTypeFactory#customizeBuilders} for a reasonable xml file. */
+ @Test
+ public void testUserTypeFactoryCustomize_profile() throws Exception {
+ final String userTypeAosp1 = "android.test.1"; // Profile user that is not customized
+ final String userTypeAosp2 = "android.test.2"; // Profile user that is customized
+ final String userTypeOem1 = "custom.test.1"; // Custom-defined profile
+
+ // Mock some "AOSP defaults".
+ final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
+ final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+ builders.put(userTypeAosp1, new UserTypeDetails.Builder()
+ .setName(userTypeAosp1)
+ .setBaseType(FLAG_PROFILE)
+ .setMaxAllowedPerParent(31)
+ .setDefaultRestrictions(restrictions));
+ builders.put(userTypeAosp2, new UserTypeDetails.Builder()
+ .setName(userTypeAosp1)
+ .setBaseType(FLAG_PROFILE)
+ .setMaxAllowedPerParent(32)
+ .setIconBadge(401)
+ .setBadgeColors(402, 403, 404)
+ .setDefaultRestrictions(restrictions));
+
+ final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_profile);
+ UserTypeFactory.customizeBuilders(builders, parser);
+
+ // userTypeAosp1 should not be modified.
+ UserTypeDetails aospType = builders.get(userTypeAosp1).createUserTypeDetails();
+ assertEquals(31, aospType.getMaxAllowedPerParent());
+ assertEquals(Resources.ID_NULL, aospType.getIconBadge());
+ assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
+
+ // userTypeAosp2 should be modified.
+ aospType = builders.get(userTypeAosp2).createUserTypeDetails();
+ assertEquals(12, aospType.getMaxAllowedPerParent());
+ assertEquals(com.android.internal.R.drawable.ic_corp_icon_badge_case,
+ aospType.getIconBadge());
+ assertEquals(Resources.ID_NULL, aospType.getBadgePlain()); // No resId for 'garbage'
+ assertEquals(com.android.internal.R.drawable.ic_corp_badge_no_background,
+ aospType.getBadgeNoBackground());
+ assertEquals(com.android.internal.R.string.managed_profile_label_badge,
+ aospType.getBadgeLabel(0));
+ assertEquals(com.android.internal.R.string.managed_profile_label_badge_2,
+ aospType.getBadgeLabel(1));
+ assertEquals(com.android.internal.R.string.managed_profile_label_badge_2,
+ aospType.getBadgeLabel(2));
+ assertEquals(com.android.internal.R.string.managed_profile_label_badge_2,
+ aospType.getBadgeLabel(3));
+ assertEquals(com.android.internal.R.color.profile_badge_1,
+ aospType.getBadgeColor(0));
+ assertEquals(com.android.internal.R.color.profile_badge_2,
+ aospType.getBadgeColor(1));
+ assertEquals(com.android.internal.R.color.profile_badge_2,
+ aospType.getBadgeColor(2));
+ assertEquals(com.android.internal.R.color.profile_badge_2,
+ aospType.getBadgeColor(3));
+ assertTrue(UserRestrictionsUtils.areEqual(
+ makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
+ aospType.getDefaultRestrictions()));
+
+ // userTypeOem1 should be created.
+ UserTypeDetails.Builder customType = builders.get(userTypeOem1);
+ assertNotNull(customType);
+ assertEquals(14, customType.createUserTypeDetails().getMaxAllowedPerParent());
+ }
+
+ /** Tests {@link UserTypeFactory#customizeBuilders} for customizing a FULL user. */
+ @Test
+ public void testUserTypeFactoryCustomize_full() throws Exception {
+ final String userTypeFull = "android.test.1";
+
+ // Mock "AOSP default".
+ final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
+ final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+ builders.put(userTypeFull, new UserTypeDetails.Builder()
+ .setName(userTypeFull)
+ .setBaseType(FLAG_FULL)
+ .setDefaultRestrictions(restrictions));
+
+ final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_full);
+ UserTypeFactory.customizeBuilders(builders, parser);
+
+ UserTypeDetails details = builders.get(userTypeFull).createUserTypeDetails();
+ assertEquals(UNLIMITED_NUMBER_OF_USERS, details.getMaxAllowedPerParent());
+ assertTrue(UserRestrictionsUtils.areEqual(
+ makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
+ details.getDefaultRestrictions()));
+ assertEquals(Resources.ID_NULL, details.getBadgeColor(0));
+ }
+
+ /**
+ * Tests {@link UserTypeFactory#customizeBuilders} when custom user type deletes the
+ * badge-colors and restrictions.
+ */
+ @Test
+ public void testUserTypeFactoryCustomize_eraseArray() throws Exception {
+ final String typeName = "android.test";
+
+ final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+ builders.put(typeName, new UserTypeDetails.Builder()
+ .setName(typeName)
+ .setBaseType(FLAG_PROFILE)
+ .setMaxAllowedPerParent(1)
+ .setBadgeColors(501, 502)
+ .setDefaultRestrictions(makeRestrictionsBundle("r1")));
+
+ final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_eraseArray);
+ UserTypeFactory.customizeBuilders(builders, parser);
+
+ UserTypeDetails typeDetails = builders.get(typeName).createUserTypeDetails();
+ assertEquals(2, typeDetails.getMaxAllowedPerParent());
+ assertEquals(Resources.ID_NULL, typeDetails.getBadgeColor(0));
+ assertEquals(Resources.ID_NULL, typeDetails.getBadgeColor(1));
+ assertTrue(typeDetails.getDefaultRestrictions().isEmpty());
+ }
+
+ /** Tests {@link UserTypeFactory#customizeBuilders} when custom user type has illegal name. */
+ @Test
+ public void testUserTypeFactoryCustomize_illegalOemName() throws Exception {
+ final String userTypeAosp = "android.aosp.legal";
+ final String userTypeOem = "android.oem.illegal.name"; // Custom-defined profile
+
+ final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+ builders.put(userTypeAosp, new UserTypeDetails.Builder()
+ .setName(userTypeAosp)
+ .setBaseType(FLAG_PROFILE)
+ .setMaxAllowedPerParent(21));
+
+ final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_illegalOemName);
+
+ // parser is illegal because non-AOSP user types cannot be prefixed with "android.".
+ assertThrows(IllegalArgumentException.class,
+ () -> UserTypeFactory.customizeBuilders(builders, parser));
+ }
+
+ /**
+ * Tests {@link UserTypeFactory#customizeBuilders} when illegally customizing a non-profile as
+ * a profile.
+ */
+ @Test
+ public void testUserTypeFactoryCustomize_illegalUserBaseType() throws Exception {
+ final String userTypeFull = "android.test";
+
+ final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
+ builders.put(userTypeFull, new UserTypeDetails.Builder()
+ .setName(userTypeFull)
+ .setBaseType(FLAG_FULL)
+ .setMaxAllowedPerParent(21));
+
+ XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_illegalUserBaseType);
+
+ // parser is illegal because userTypeFull is FULL but the tag is for profile-type.
+ assertThrows(IllegalArgumentException.class,
+ () -> UserTypeFactory.customizeBuilders(builders, parser));
+ }
+
/** Returns a minimal {@link UserTypeDetails.Builder} that can legitimately be created. */
private UserTypeDetails.Builder getMinimalBuilder() {
return new UserTypeDetails.Builder().setName("name").setBaseType(FLAG_FULL);
}
+
+ /** Creates a Bundle of the given String restrictions, each set to true. */
+ private Bundle makeRestrictionsBundle(String ... restrictions) {
+ final Bundle bundle = new Bundle();
+ for (String restriction : restrictions) {
+ bundle.putBoolean(restriction, true);
+ }
+ return bundle;
+ }
}
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 dee79bb..06b3dc1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -16,6 +16,12 @@
package com.android.server.pm;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
@@ -29,15 +35,21 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
-import com.android.internal.util.ArrayUtils;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.Range;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -45,7 +57,8 @@
import java.util.concurrent.atomic.AtomicInteger;
/** Test {@link UserManager} functionality. */
-public class UserManagerTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public final class UserManagerTest {
// Taken from UserManagerService
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // 30 years
@@ -58,6 +71,8 @@
"com.android.egg"
};
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
private final Object mUserRemoveLock = new Object();
private final Object mUserSwitchLock = new Object();
@@ -65,15 +80,14 @@
private PackageManager mPackageManager;
private List<Integer> usersToRemove;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
- mUserManager = UserManager.get(getContext());
- mPackageManager = getContext().getPackageManager();
+ mUserManager = UserManager.get(mContext);
+ mPackageManager = mContext.getPackageManager();
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
- getContext().registerReceiver(new BroadcastReceiver() {
+ mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
@@ -95,36 +109,35 @@
usersToRemove = new ArrayList<>();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
for (Integer userId : usersToRemove) {
removeUser(userId);
}
- super.tearDown();
}
private void removeExistingUsers() {
+ int currentUser = ActivityManager.getCurrentUser();
List<UserInfo> list = mUserManager.getUsers();
for (UserInfo user : list) {
- // Keep system and primary user.
- // We do not have to keep primary user, but in split system user mode, we need it
- // until http://b/22976637 is fixed. Right now in split system user mode, you need to
- // switch to primary user and run tests under primary user.
- if (user.id != UserHandle.USER_SYSTEM && !user.isPrimary()) {
+ // Keep system and current user
+ if (user.id != UserHandle.USER_SYSTEM && user.id != currentUser) {
removeUser(user.id);
}
}
}
@SmallTest
+ @Test
public void testHasSystemUser() throws Exception {
- assertTrue(findUser(UserHandle.USER_SYSTEM));
+ assertThat(findUser(UserHandle.USER_SYSTEM)).isTrue();
}
@MediumTest
- public void testAddUser() throws Exception {
+ @Test
+ public void testAddGuest() throws Exception {
UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
- assertTrue(userInfo != null);
+ assertThat(userInfo).isNotNull();
List<UserInfo> list = mUserManager.getUsers();
boolean found = false;
@@ -135,35 +148,39 @@
&& !user.isPrimary()) {
found = true;
Bundle restrictions = mUserManager.getUserRestrictions(user.getUserHandle());
- assertTrue("Guest user should have DISALLOW_CONFIG_WIFI=true by default",
- restrictions.getBoolean(UserManager.DISALLOW_CONFIG_WIFI));
+ assertWithMessage("Guest user should have DISALLOW_CONFIG_WIFI=true by default")
+ .that(restrictions.getBoolean(UserManager.DISALLOW_CONFIG_WIFI))
+ .isTrue();
}
}
- assertTrue(found);
+ assertThat(found).isTrue();
}
@MediumTest
+ @Test
public void testAdd2Users() throws Exception {
UserInfo user1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
UserInfo user2 = createUser("User 2", UserInfo.FLAG_ADMIN);
- assertTrue(user1 != null);
- assertTrue(user2 != null);
+ assertThat(user1).isNotNull();
+ assertThat(user2).isNotNull();
- assertTrue(findUser(0));
- assertTrue(findUser(user1.id));
- assertTrue(findUser(user2.id));
+ assertThat(findUser(UserHandle.USER_SYSTEM)).isTrue();
+ assertThat(findUser(user1.id)).isTrue();
+ assertThat(findUser(user2.id)).isTrue();
}
@MediumTest
+ @Test
public void testRemoveUser() throws Exception {
UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
removeUser(userInfo.id);
- assertFalse(findUser(userInfo.id));
+ assertThat(findUser(userInfo.id)).isFalse();
}
@MediumTest
+ @Test
public void testRemoveUserByHandle() {
UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST);
final UserHandle user = userInfo.getUserHandle();
@@ -183,10 +200,11 @@
}
}
- assertFalse(findUser(userInfo.id));
+ assertThat(findUser(userInfo.id)).isFalse();
}
@MediumTest
+ @Test
public void testRemoveUserByHandle_ThrowsException() {
synchronized (mUserRemoveLock) {
try {
@@ -200,6 +218,7 @@
/** Tests creating a FULL user via specifying userType. */
@MediumTest
+ @Test
public void testCreateUserViaTypes() throws Exception {
createUserWithTypeAndCheckFlags(UserManager.USER_TYPE_FULL_GUEST,
UserInfo.FLAG_GUEST | UserInfo.FLAG_FULL);
@@ -213,6 +232,7 @@
/** Tests creating a FULL user via specifying user flags. */
@MediumTest
+ @Test
public void testCreateUserViaFlags() throws Exception {
createUserWithFlagsAndCheckType(UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST,
UserInfo.FLAG_FULL);
@@ -231,10 +251,9 @@
private void createUserWithTypeAndCheckFlags(String userType,
@UserIdInt int requiredFlags) {
final UserInfo userInfo = createUser("Name", userType, 0);
- assertEquals("Wrong user type", userType, userInfo.userType);
- assertEquals(
- "Flags " + userInfo.flags + " did not contain expected " + requiredFlags,
- requiredFlags, userInfo.flags & requiredFlags);
+ assertWithMessage("Wrong user type").that(userInfo.userType).isEqualTo(userType);
+ assertWithMessage("Flags %s did not contain expected %s", userInfo.flags, requiredFlags)
+ .that(userInfo.flags & requiredFlags).isEqualTo(requiredFlags);
removeUser(userInfo.id);
}
@@ -245,145 +264,140 @@
private void createUserWithFlagsAndCheckType(@UserIdInt int flags, String expectedUserType,
@UserIdInt int additionalRequiredFlags) {
final UserInfo userInfo = createUser("Name", flags);
- assertEquals("Wrong user type", expectedUserType, userInfo.userType);
+ assertWithMessage("Wrong user type").that(userInfo.userType).isEqualTo(expectedUserType);
additionalRequiredFlags |= flags;
- assertEquals(
- "Flags " + userInfo.flags + " did not contain expected " + additionalRequiredFlags,
- additionalRequiredFlags, userInfo.flags & additionalRequiredFlags);
+ assertWithMessage("Flags %s did not contain expected %s", userInfo.flags,
+ additionalRequiredFlags).that(userInfo.flags & additionalRequiredFlags)
+ .isEqualTo(additionalRequiredFlags);
removeUser(userInfo.id);
}
@MediumTest
- public void testAddGuest() throws Exception {
+ @Test
+ public void testThereCanBeOnlyOneGuest() throws Exception {
UserInfo userInfo1 = createUser("Guest 1", UserInfo.FLAG_GUEST);
+ assertThat(userInfo1).isNotNull();
UserInfo userInfo2 = createUser("Guest 2", UserInfo.FLAG_GUEST);
- assertNotNull(userInfo1);
- assertNull(userInfo2);
+ assertThat(userInfo2).isNull();
}
@MediumTest
+ @Test
public void testFindExistingGuest_guestExists() throws Exception {
UserInfo userInfo1 = createUser("Guest", UserInfo.FLAG_GUEST);
+ assertThat(userInfo1).isNotNull();
UserInfo foundGuest = mUserManager.findCurrentGuestUser();
- assertNotNull(foundGuest);
+ assertThat(foundGuest).isNotNull();
}
@SmallTest
+ @Test
public void testFindExistingGuest_guestDoesNotExist() throws Exception {
UserInfo foundGuest = mUserManager.findCurrentGuestUser();
- assertNull(foundGuest);
+ assertThat(foundGuest).isNull();
}
@MediumTest
+ @Test
public void testSetUserAdmin() throws Exception {
UserInfo userInfo = createUser("SecondaryUser", /*flags=*/ 0);
+ assertThat(userInfo.isAdmin()).isFalse();
- // Assert user is not admin and has SMS and calls restrictions.
- assertFalse(userInfo.isAdmin());
- assertTrue(mUserManager.hasUserRestriction(UserManager.DISALLOW_SMS,
- userInfo.getUserHandle()));
- assertTrue(mUserManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- userInfo.getUserHandle()));
-
- // Assign admin privileges.
mUserManager.setUserAdmin(userInfo.id);
- // Refresh.
userInfo = mUserManager.getUserInfo(userInfo.id);
-
- // Verify user became admin and SMS and call restrictions are lifted.
- assertTrue(userInfo.isAdmin());
- assertFalse(mUserManager.hasUserRestriction(UserManager.DISALLOW_SMS,
- userInfo.getUserHandle()));
- assertFalse(mUserManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- userInfo.getUserHandle()));
+ assertThat(userInfo.isAdmin()).isTrue();
}
@MediumTest
+ @Test
public void testGetProfileParent() throws Exception {
+ assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
UserInfo userInfo = createProfileForUser("Profile",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
- assertNotNull(userInfo);
- assertNull(mUserManager.getProfileParent(primaryUserId));
+ assertThat(userInfo).isNotNull();
+ assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
- assertNotNull(parentProfileInfo);
- assertEquals(parentProfileInfo.id, primaryUserId);
+ assertThat(parentProfileInfo).isNotNull();
+ assertThat(primaryUserId).isEqualTo(parentProfileInfo.id);
removeUser(userInfo.id);
- assertNull(mUserManager.getProfileParent(primaryUserId));
+ assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
}
/** Test that UserManager returns the correct badge information for a managed profile. */
@MediumTest
+ @Test
public void testProfileTypeInformation() throws Exception {
+ assumeManagedUsersSupported();
final UserTypeDetails userTypeDetails =
UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_MANAGED);
- assertNotNull("No " + UserManager.USER_TYPE_PROFILE_MANAGED + " type on device",
- userTypeDetails);
- assertEquals(UserManager.USER_TYPE_PROFILE_MANAGED, userTypeDetails.getName());
+ assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_MANAGED)
+ .that(userTypeDetails).isNotNull();
+ assertThat(userTypeDetails.getName()).isEqualTo(UserManager.USER_TYPE_PROFILE_MANAGED);
final int primaryUserId = mUserManager.getPrimaryUser().id;
UserInfo userInfo = createProfileForUser("Managed",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
- assertNotNull(userInfo);
+ assertThat(userInfo).isNotNull();
final int userId = userInfo.id;
final UserHandle userHandle = new UserHandle(userId);
- assertEquals(userTypeDetails.hasBadge(),
- mUserManager.hasBadge(userId));
- assertEquals(userTypeDetails.getIconBadge(),
- mUserManager.getUserIconBadgeResId(userId));
- assertEquals(userTypeDetails.getBadgePlain(),
- mUserManager.getUserBadgeResId(userId));
- assertEquals(userTypeDetails.getBadgeNoBackground(),
- mUserManager.getUserBadgeNoBackgroundResId(userId));
- assertEquals(userTypeDetails.isProfile(),
- mUserManager.isProfile(userId));
- assertEquals(userTypeDetails.getName(),
- mUserManager.getUserTypeForUser(userHandle));
+ assertThat(mUserManager.hasBadge(userId)).isEqualTo(userTypeDetails.hasBadge());
+ assertThat(mUserManager.getUserIconBadgeResId(userId))
+ .isEqualTo(userTypeDetails.getIconBadge());
+ assertThat(mUserManager.getUserBadgeResId(userId))
+ .isEqualTo(userTypeDetails.getBadgePlain());
+ assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId))
+ .isEqualTo(userTypeDetails.getBadgeNoBackground());
+ assertThat(mUserManager.isProfile(userId)).isEqualTo(userTypeDetails.isProfile());
+ assertThat(mUserManager.getUserTypeForUser(userHandle))
+ .isEqualTo(userTypeDetails.getName());
final int badgeIndex = userInfo.profileBadge;
- assertEquals(
- Resources.getSystem().getColor(userTypeDetails.getBadgeColor(badgeIndex), null),
- mUserManager.getUserBadgeColor(userId));
- assertEquals(
- Resources.getSystem().getString(userTypeDetails.getBadgeLabel(badgeIndex), "Test"),
- mUserManager.getBadgedLabelForUser("Test", userHandle));
+ assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(
+ Resources.getSystem().getColor(userTypeDetails.getBadgeColor(badgeIndex), null));
+ assertThat(mUserManager.getBadgedLabelForUser("Test", userHandle)).isEqualTo(
+ Resources.getSystem().getString(userTypeDetails.getBadgeLabel(badgeIndex), "Test"));
}
// Make sure only one managed profile can be created
@MediumTest
+ @Test
public void testAddManagedProfile() throws Exception {
+ assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
UserInfo userInfo1 = createProfileForUser("Managed 1",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
UserInfo userInfo2 = createProfileForUser("Managed 2",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
- assertNotNull(userInfo1);
- assertNull(userInfo2);
+ assertThat(userInfo1).isNotNull();
+ assertThat(userInfo2).isNull();
- assertEquals(userInfo1.userType, UserManager.USER_TYPE_PROFILE_MANAGED);
+ assertThat(userInfo1.userType).isEqualTo(UserManager.USER_TYPE_PROFILE_MANAGED);
int requiredFlags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_PROFILE;
- assertEquals("Wrong flags " + userInfo1.flags, requiredFlags,
- userInfo1.flags & requiredFlags);
+ assertWithMessage("Wrong flags %s", userInfo1.flags).that(userInfo1.flags & requiredFlags)
+ .isEqualTo(requiredFlags);
// Verify that current user is not a managed profile
- assertFalse(mUserManager.isManagedProfile());
+ assertThat(mUserManager.isManagedProfile()).isFalse();
}
// Verify that disallowed packages are not installed in the managed profile.
@MediumTest
+ @Test
public void testAddManagedProfile_withDisallowedPackages() throws Exception {
+ assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
UserInfo userInfo1 = createProfileForUser("Managed1",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
// Verify that the packagesToVerify are installed by default.
for (String pkg : PACKAGES) {
- assertTrue("Package should be installed in managed profile: " + pkg,
- isPackageInstalledForUser(pkg, userInfo1.id));
+ assertWithMessage("Package should be installed in managed profile: %s", pkg)
+ .that(isPackageInstalledForUser(pkg, userInfo1.id)).isTrue();
}
removeUser(userInfo1.id);
@@ -391,50 +405,57 @@
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId, PACKAGES);
// Verify that the packagesToVerify are not installed by default.
for (String pkg : PACKAGES) {
- assertFalse("Package should not be installed in managed profile when disallowed: "
- + pkg, isPackageInstalledForUser(pkg, userInfo2.id));
+ assertWithMessage(
+ "Package should not be installed in managed profile when disallowed: %s", pkg)
+ .that(isPackageInstalledForUser(pkg, userInfo2.id)).isFalse();
}
}
// Verify that if any packages are disallowed to install during creation of managed profile can
// still be installed later.
@MediumTest
+ @Test
public void testAddManagedProfile_disallowedPackagesInstalledLater() throws Exception {
+ assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
UserInfo userInfo = createProfileForUser("Managed",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId, PACKAGES);
// Verify that the packagesToVerify are not installed by default.
for (String pkg : PACKAGES) {
- assertFalse("Package should not be installed in managed profile when disallowed: "
- + pkg, isPackageInstalledForUser(pkg, userInfo.id));
+ assertWithMessage("Pkg should not be installed in managed profile when disallowed: %s",
+ pkg).that(isPackageInstalledForUser(pkg, userInfo.id)).isFalse();
}
// Verify that the disallowed packages during profile creation can be installed now.
for (String pkg : PACKAGES) {
- assertEquals("Package could not be installed: " + pkg,
- PackageManager.INSTALL_SUCCEEDED,
- mPackageManager.installExistingPackageAsUser(pkg, userInfo.id));
+ assertWithMessage("Package could not be installed: %s", pkg)
+ .that(mPackageManager.installExistingPackageAsUser(pkg, userInfo.id))
+ .isEqualTo(PackageManager.INSTALL_SUCCEEDED);
}
}
// Make sure createUser would fail if we have DISALLOW_ADD_USER.
@MediumTest
+ @Test
public void testCreateUser_disallowAddUser() throws Exception {
- final int primaryUserId = mUserManager.getPrimaryUser().id;
- final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
- mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle);
+ final int creatorId = isAutomotive() ? ActivityManager.getCurrentUser()
+ : mUserManager.getPrimaryUser().id;
+ final UserHandle creatorHandle = new UserHandle(creatorId);
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, creatorHandle);
try {
- UserInfo userInfo = createUser("SecondaryUser", /*flags=*/ 0);
- assertNull(userInfo);
+ UserInfo createadInfo = createUser("SecondaryUser", /*flags=*/ 0);
+ assertThat(createadInfo).isNull();
} finally {
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false,
- primaryUserHandle);
+ creatorHandle);
}
}
// Make sure createProfile would fail if we have DISALLOW_ADD_MANAGED_PROFILE.
@MediumTest
+ @Test
public void testCreateProfileForUser_disallowAddManagedProfile() throws Exception {
+ assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
@@ -442,7 +463,7 @@
try {
UserInfo userInfo = createProfileForUser("Managed",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
- assertNull(userInfo);
+ assertThat(userInfo).isNull();
} finally {
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false,
primaryUserHandle);
@@ -451,7 +472,9 @@
// Make sure createProfileEvenWhenDisallowedForUser bypass DISALLOW_ADD_MANAGED_PROFILE.
@MediumTest
+ @Test
public void testCreateProfileForUserEvenWhenDisallowed() throws Exception {
+ assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
@@ -459,7 +482,7 @@
try {
UserInfo userInfo = createProfileEvenWhenDisallowedForUser("Managed",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
- assertNotNull(userInfo);
+ assertThat(userInfo).isNotNull();
} finally {
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false,
primaryUserHandle);
@@ -468,14 +491,16 @@
// createProfile succeeds even if DISALLOW_ADD_USER is set
@MediumTest
+ @Test
public void testCreateProfileForUser_disallowAddUser() throws Exception {
+ assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle);
try {
UserInfo userInfo = createProfileForUser("Managed",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
- assertNotNull(userInfo);
+ assertThat(userInfo).isNotNull();
} finally {
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false,
primaryUserHandle);
@@ -483,73 +508,85 @@
}
@MediumTest
+ @Test
public void testAddRestrictedProfile() throws Exception {
- assertFalse("There should be no associated restricted profiles before the test",
- mUserManager.hasRestrictedProfiles());
+ if (isAutomotive()) return;
+ assertWithMessage("There should be no associated restricted profiles before the test")
+ .that(mUserManager.hasRestrictedProfiles()).isFalse();
UserInfo userInfo = createRestrictedProfile("Profile");
- assertNotNull(userInfo);
+ assertThat(userInfo).isNotNull();
Bundle restrictions = mUserManager.getUserRestrictions(UserHandle.of(userInfo.id));
- assertTrue("Restricted profile should have DISALLOW_MODIFY_ACCOUNTS restriction by default",
- restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS));
- assertTrue("Restricted profile should have DISALLOW_SHARE_LOCATION restriction by default",
- restrictions.getBoolean(UserManager.DISALLOW_SHARE_LOCATION));
+ assertWithMessage(
+ "Restricted profile should have DISALLOW_MODIFY_ACCOUNTS restriction by default")
+ .that(restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS))
+ .isTrue();
+ assertWithMessage(
+ "Restricted profile should have DISALLOW_SHARE_LOCATION restriction by default")
+ .that(restrictions.getBoolean(UserManager.DISALLOW_SHARE_LOCATION))
+ .isTrue();
- int locationMode = Settings.Secure.getIntForUser(getContext().getContentResolver(),
+ int locationMode = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.LOCATION_MODE,
Settings.Secure.LOCATION_MODE_HIGH_ACCURACY,
userInfo.id);
- assertEquals("Restricted profile should have setting LOCATION_MODE set to "
- + "LOCATION_MODE_OFF by default", locationMode, Settings.Secure.LOCATION_MODE_OFF);
+ assertWithMessage("Restricted profile should have setting LOCATION_MODE set to "
+ + "LOCATION_MODE_OFF by default").that(locationMode)
+ .isEqualTo(Settings.Secure.LOCATION_MODE_OFF);
- assertTrue("Newly created profile should be associated with the current user",
- mUserManager.hasRestrictedProfiles());
+ assertWithMessage("Newly created profile should be associated with the current user")
+ .that(mUserManager.hasRestrictedProfiles()).isTrue();
}
@MediumTest
+ @Test
public void testGetUserCreationTime() throws Exception {
+ // TODO: should add a regular user instead of a profile, so it can be tested everywhere
+ assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
final long startTime = System.currentTimeMillis();
UserInfo profile = createProfileForUser("Managed 1",
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
final long endTime = System.currentTimeMillis();
- assertNotNull(profile);
+ assertThat(profile).isNotNull();
if (System.currentTimeMillis() > EPOCH_PLUS_30_YEARS) {
- assertTrue("creationTime must be set when the profile is created",
- profile.creationTime >= startTime && profile.creationTime <= endTime);
+ assertWithMessage("creationTime must be set when the profile is created")
+ .that(profile.creationTime).isIn(Range.closed(startTime, endTime));
} else {
- assertTrue("creationTime must be 0 if the time is not > EPOCH_PLUS_30_years",
- profile.creationTime == 0);
+ assertWithMessage("creationTime must be 0 if the time is not > EPOCH_PLUS_30_years")
+ .that(profile.creationTime).isEqualTo(0);
}
- assertEquals(profile.creationTime, mUserManager.getUserCreationTime(
- new UserHandle(profile.id)));
+ assertThat(mUserManager.getUserCreationTime(
+ new UserHandle(profile.id))).isEqualTo(profile.creationTime);
long ownerCreationTime = mUserManager.getUserInfo(primaryUserId).creationTime;
- assertEquals(ownerCreationTime, mUserManager.getUserCreationTime(
- new UserHandle(primaryUserId)));
+ assertThat(mUserManager.getUserCreationTime(
+ new UserHandle(primaryUserId))).isEqualTo(ownerCreationTime);
}
@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) {
- assertTrue("SecurityException should be thrown for nonexistent user, but was: " + e,
- e instanceof SecurityException);
+ assertWithMessage("SecurityException should be thrown for nonexistent user").that(e)
+ .isInstanceOf(SecurityException.class);
}
}
@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) {
- assertTrue("SecurityException should be thrown for other user, but was: " + e,
- e instanceof SecurityException);
+ assertWithMessage("SecurityException should be thrown for other user").that(e)
+ .isInstanceOf(SecurityException.class);
}
}
@@ -565,56 +602,58 @@
}
@MediumTest
+ @Test
public void testSerialNumber() {
UserInfo user1 = createUser("User 1", 0);
int serialNumber1 = user1.serialNumber;
- assertEquals(serialNumber1, mUserManager.getUserSerialNumber(user1.id));
- assertEquals(user1.id, mUserManager.getUserHandle(serialNumber1));
+ assertThat(mUserManager.getUserSerialNumber(user1.id)).isEqualTo(serialNumber1);
+ assertThat(mUserManager.getUserHandle(serialNumber1)).isEqualTo(user1.id);
UserInfo user2 = createUser("User 2", 0);
int serialNumber2 = user2.serialNumber;
- assertFalse(serialNumber1 == serialNumber2);
- assertEquals(serialNumber2, mUserManager.getUserSerialNumber(user2.id));
- assertEquals(user2.id, mUserManager.getUserHandle(serialNumber2));
+ assertThat(serialNumber1 == serialNumber2).isFalse();
+ assertThat(mUserManager.getUserSerialNumber(user2.id)).isEqualTo(serialNumber2);
+ assertThat(mUserManager.getUserHandle(serialNumber2)).isEqualTo(user2.id);
}
@MediumTest
+ @Test
public void testGetSerialNumbersOfUsers() {
UserInfo user1 = createUser("User 1", 0);
UserInfo user2 = createUser("User 2", 0);
long[] serialNumbersOfUsers = mUserManager.getSerialNumbersOfUsers(false);
- String errMsg = "Array " + Arrays.toString(serialNumbersOfUsers) + " should contain ";
- assertTrue(errMsg + user1.serialNumber,
- ArrayUtils.contains(serialNumbersOfUsers, user1.serialNumber));
- assertTrue(errMsg + user2.serialNumber,
- ArrayUtils.contains(serialNumbersOfUsers, user2.serialNumber));
+ assertThat(serialNumbersOfUsers).asList().containsAllOf(
+ (long) user1.serialNumber, (long) user2.serialNumber);
}
@MediumTest
+ @Test
public void testMaxUsers() {
int N = UserManager.getMaxSupportedUsers();
int count = mUserManager.getUsers().size();
// Create as many users as permitted and make sure creation passes
while (count < N) {
UserInfo ui = createUser("User " + count, 0);
- assertNotNull(ui);
+ assertThat(ui).isNotNull();
count++;
}
// Try to create one more user and make sure it fails
UserInfo extra = createUser("One more", 0);
- assertNull(extra);
+ assertThat(extra).isNull();
}
@MediumTest
+ @Test
public void testGetUserCount() {
int count = mUserManager.getUsers().size();
UserInfo user1 = createUser("User 1", 0);
- assertNotNull(user1);
+ assertThat(user1).isNotNull();
UserInfo user2 = createUser("User 2", 0);
- assertNotNull(user2);
- assertEquals(count + 2, mUserManager.getUserCount());
+ assertThat(user2).isNotNull();
+ assertThat(mUserManager.getUserCount()).isEqualTo(count + 2);
}
@MediumTest
+ @Test
public void testRestrictions() {
UserInfo testUser = createUser("User 1", 0);
@@ -625,12 +664,29 @@
Bundle stored = mUserManager.getUserRestrictions(new UserHandle(testUser.id));
// Note this will fail if DO already sets those restrictions.
- assertEquals(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI), false);
- assertEquals(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS), false);
- assertEquals(stored.getBoolean(UserManager.DISALLOW_INSTALL_APPS), true);
+ assertThat(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI)).isFalse();
+ assertThat(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS)).isFalse();
+ assertThat(stored.getBoolean(UserManager.DISALLOW_INSTALL_APPS)).isTrue();
}
@MediumTest
+ @Test
+ public void testDefaultRestrictionsApplied() throws Exception {
+ final UserInfo userInfo = createUser("Useroid", UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_FULL_SECONDARY);
+ final Bundle expectedRestrictions = userTypeDetails.getDefaultRestrictions();
+ // Note this can fail if DO unset those restrictions.
+ for (String restriction : expectedRestrictions.keySet()) {
+ if (expectedRestrictions.getBoolean(restriction)) {
+ assertThat(mUserManager.hasUserRestriction(restriction, UserHandle.of(userInfo.id)))
+ .isTrue();
+ }
+ }
+ }
+
+ @MediumTest
+ @Test
public void testSetDefaultGuestRestrictions() {
final Bundle origGuestRestrictions = mUserManager.getDefaultGuestRestrictions();
Bundle restrictions = new Bundle();
@@ -639,26 +695,28 @@
try {
UserInfo guest = createUser("Guest", UserInfo.FLAG_GUEST);
- assertNotNull(guest);
- assertTrue(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
- guest.getUserHandle()));
+ assertThat(guest).isNotNull();
+ assertThat(mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN,
+ guest.getUserHandle())).isTrue();
} finally {
mUserManager.setDefaultGuestRestrictions(origGuestRestrictions);
}
}
+ @Test
public void testGetUserSwitchability() {
int userSwitchable = mUserManager.getUserSwitchability();
- assertEquals("Expected users to be switchable", UserManager.SWITCHABILITY_STATUS_OK,
- userSwitchable);
+ assertWithMessage("Expected users to be switchable").that(userSwitchable)
+ .isEqualTo(UserManager.SWITCHABILITY_STATUS_OK);
}
@LargeTest
+ @Test
public void testSwitchUser() {
- ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
final int startUser = am.getCurrentUser();
UserInfo user = createUser("User", 0);
- assertNotNull(user);
+ assertThat(user).isNotNull();
// Switch to the user just created.
switchUser(user.id, null, true);
// Switch back to the starting user.
@@ -666,21 +724,23 @@
}
@LargeTest
+ @Test
public void testSwitchUserByHandle() {
- ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
final int startUser = am.getCurrentUser();
UserInfo user = createUser("User", 0);
- assertNotNull(user);
+ assertThat(user).isNotNull();
// Switch to the user just created.
switchUser(-1, user.getUserHandle(), false);
// Switch back to the starting user.
switchUser(-1, UserHandle.of(startUser), false);
}
+ @Test
public void testSwitchUserByHandle_ThrowsException() {
synchronized (mUserSwitchLock) {
try {
- ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
am.switchUser(null);
fail("Expected IllegalArgumentException on passing in a null UserHandle.");
} catch (IllegalArgumentException expected) {
@@ -690,6 +750,7 @@
}
@MediumTest
+ @Test
public void testConcurrentUserCreate() throws Exception {
int userCount = mUserManager.getUserCount();
int maxSupportedUsers = UserManager.getMaxSupportedUsers();
@@ -712,11 +773,12 @@
}
es.shutdown();
es.awaitTermination(20, TimeUnit.SECONDS);
- assertEquals(maxSupportedUsers, mUserManager.getUserCount());
- assertEquals(canBeCreatedCount, created.get());
+ assertThat(mUserManager.getUserCount()).isEqualTo(maxSupportedUsers);
+ assertThat(created.get()).isEqualTo(canBeCreatedCount);
}
@MediumTest
+ @Test
public void testGetUserHandles_createNewUser_shouldFindNewUser() {
UserInfo user = createUser("Guest 1", UserManager.USER_TYPE_FULL_GUEST, /*flags*/ 0);
@@ -728,7 +790,7 @@
}
}
- assertTrue(found);
+ assertThat(found).isTrue();
}
private boolean isPackageInstalledForUser(String packageName, int userId) {
@@ -747,7 +809,7 @@
*/
private void switchUser(int userId, UserHandle user, boolean ignoreHandle) {
synchronized (mUserSwitchLock) {
- ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
if (ignoreHandle) {
am.switchUser(userId);
} else {
@@ -833,4 +895,12 @@
return profile;
}
+ private void assumeManagedUsersSupported() {
+ assumeTrue("device doesn't support managed users",
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS));
+ }
+
+ private boolean isAutomotive() {
+ return mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
index 683278b..3db832b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -26,6 +26,7 @@
import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE;
import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA;
import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM;
import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_LOG;
import static org.junit.Assert.assertEquals;
@@ -262,39 +263,25 @@
// No implicit whitelist, so only install pkg1.
boolean implicit = false;
- boolean isSysUser = false;
assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg1, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg1, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg2, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg2, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg3, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg3, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg4, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg4, pkgBitSetMap, userWhitelist, implicit));
// Use implicit whitelist, so install pkg1 and pkg4
implicit = true;
- isSysUser = false;
assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg1, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg1, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg2, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg2, pkgBitSetMap, userWhitelist, implicit));
assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg3, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg3, pkgBitSetMap, userWhitelist, implicit));
assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg4, pkgBitSetMap, userWhitelist, implicit, isSysUser));
-
- // For user 0 specifically, we always implicitly whitelist.
- implicit = false;
- isSysUser = true;
- assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg1, pkgBitSetMap, userWhitelist, implicit, isSysUser));
- assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg2, pkgBitSetMap, userWhitelist, implicit, isSysUser));
- assertFalse(UserSystemPackageInstaller.shouldInstallPackage(
- pkg3, pkgBitSetMap, userWhitelist, implicit, isSysUser));
- assertTrue(UserSystemPackageInstaller.shouldInstallPackage(
- pkg4, pkgBitSetMap, userWhitelist, implicit, isSysUser));
+ pkg4, pkgBitSetMap, userWhitelist, implicit));
}
/**
@@ -400,30 +387,42 @@
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertFalse(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_LOG);
assertTrue(mUserSystemPackageInstaller.isLogMode());
assertFalse(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertTrue(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertFalse(mUserSystemPackageInstaller.isEnforceMode());
assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
+ assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
+
+ setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST_SYSTEM);
+ assertFalse(mUserSystemPackageInstaller.isLogMode());
+ assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IGNORE_OTA);
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertFalse(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertTrue(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(
@@ -431,6 +430,7 @@
assertTrue(mUserSystemPackageInstaller.isLogMode());
assertTrue(mUserSystemPackageInstaller.isEnforceMode());
assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST
@@ -438,6 +438,7 @@
assertFalse(mUserSystemPackageInstaller.isLogMode());
assertTrue(mUserSystemPackageInstaller.isEnforceMode());
assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+ assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistSystemMode());
assertFalse(mUserSystemPackageInstaller.isIgnoreOtaMode());
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 81fb0ec..13643a0 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -62,6 +62,7 @@
import android.os.PowerSaveState;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.FlakyTest;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.view.Display;
@@ -734,6 +735,7 @@
assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
}
+ @FlakyTest
@Test
public void testInattentiveSleep_goesToSleepWithWakeLock() throws Exception {
final String pkg = mContextSpy.getOpPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
index 89c7dd4..ecdc58e 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
@@ -29,6 +29,7 @@
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.annotations.FlakyTest;
import android.util.ArrayMap;
import androidx.test.InstrumentationRegistry;
@@ -328,6 +329,7 @@
veriryWtf(0);
}
+ @FlakyTest
@Test
public void testAll() throws Exception {
// Run multiple tests on the single target instance.
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
new file mode 100644
index 0000000..5a2ce45
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.audio.common.V2_0.Uuid;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversionUtilTest {
+ private static final String TAG = "ConversionUtilTest";
+
+ @Test
+ public void testUuidRoundTrip() {
+ Uuid hidl = new Uuid();
+ hidl.timeLow = 0xFEDCBA98;
+ hidl.timeMid = (short) 0xEDCB;
+ hidl.versionAndTimeHigh = (short) 0xDCBA;
+ hidl.variantAndClockSeqHigh = (short) 0xCBA9;
+ hidl.node = new byte[] { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
+
+ String aidl = ConversionUtil.hidl2aidlUuid(hidl);
+ assertEquals("fedcba98-edcb-dcba-cba9-111213141516", aidl);
+
+ Uuid reconstructed = ConversionUtil.aidl2hidlUuid(aidl);
+ assertEquals(hidl, reconstructed);
+ }
+}
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
new file mode 100644
index 0000000..82f32f8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -0,0 +1,1306 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+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.clearInvocations;
+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 static org.mockito.Mockito.when;
+
+import android.hardware.audio.common.V2_0.AudioConfig;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
+import android.media.audio.common.AudioChannelMask;
+import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+import android.os.HwParcel;
+import android.os.IHwBinder;
+import android.os.IHwInterface;
+import android.os.RemoteException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+@RunWith(Parameterized.class)
+public class SoundTriggerMiddlewareImplTest {
+ private static final String TAG = "SoundTriggerMiddlewareImplTest";
+
+ // We run the test once for every version of the underlying driver.
+ @Parameterized.Parameters
+ public static Object[] data() {
+ return new Object[]{
+ mock(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_2.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_3.ISoundTriggerHw.class),
+ };
+ }
+
+ @Mock
+ @Parameterized.Parameter
+ public android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
+
+ @Mock
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider = mock(
+ SoundTriggerMiddlewareImpl.AudioSessionProvider.class);
+
+ private SoundTriggerMiddlewareImpl mService;
+
+ private static ISoundTriggerCallback createCallbackMock() {
+ return mock(ISoundTriggerCallback.Stub.class, Mockito.CALLS_REAL_METHODS);
+ }
+
+ private static SoundModel createGenericSoundModel() {
+ return createSoundModel(SoundModelType.GENERIC);
+ }
+
+ private static SoundModel createSoundModel(int type) {
+ SoundModel model = new SoundModel();
+ model.type = type;
+ model.uuid = "12345678-2345-3456-4567-abcdef987654";
+ model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
+ model.data = new byte[]{91, 92, 93, 94, 95};
+ return model;
+ }
+
+ private static PhraseSoundModel createPhraseSoundModel() {
+ PhraseSoundModel model = new PhraseSoundModel();
+ model.common = createSoundModel(SoundModelType.KEYPHRASE);
+ model.phrases = new Phrase[1];
+ model.phrases[0] = new Phrase();
+ model.phrases[0].id = 123;
+ model.phrases[0].users = new int[]{5, 6, 7};
+ model.phrases[0].locale = "locale";
+ model.phrases[0].text = "text";
+ model.phrases[0].recognitionModes =
+ RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
+ return model;
+ }
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties(
+ boolean supportConcurrentCapture) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
+ properties.implementor = "implementor";
+ properties.description = "description";
+ properties.version = 123;
+ properties.uuid = new Uuid();
+ properties.uuid.timeLow = 1;
+ properties.uuid.timeMid = 2;
+ properties.uuid.versionAndTimeHigh = 3;
+ properties.uuid.variantAndClockSeqHigh = 4;
+ properties.uuid.node = new byte[]{5, 6, 7, 8, 9, 10};
+
+ properties.maxSoundModels = 456;
+ properties.maxKeyPhrases = 567;
+ properties.maxUsers = 678;
+ properties.recognitionModes = 789;
+ properties.captureTransition = true;
+ properties.maxBufferMs = 321;
+ properties.concurrentCapture = supportConcurrentCapture;
+ properties.triggerInEvent = true;
+ properties.powerConsumptionMw = 432;
+ return properties;
+ }
+
+ private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+ boolean supportConcurrentCapture) {
+ assertEquals("implementor", properties.implementor);
+ assertEquals("description", properties.description);
+ assertEquals(123, properties.version);
+ assertEquals("00000001-0002-0003-0004-05060708090a", properties.uuid);
+ assertEquals(456, properties.maxSoundModels);
+ assertEquals(567, properties.maxKeyPhrases);
+ assertEquals(678, properties.maxUsers);
+ assertEquals(789, properties.recognitionModes);
+ assertTrue(properties.captureTransition);
+ assertEquals(321, properties.maxBufferMs);
+ assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+ assertTrue(properties.triggerInEvent);
+ assertEquals(432, properties.powerConsumptionMw);
+ }
+
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
+ int hwHandle,
+ int status) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent();
+ halEvent.status = status;
+ halEvent.type = SoundModelType.GENERIC;
+ halEvent.model = hwHandle;
+ halEvent.captureAvailable = true;
+ // This field is ignored.
+ halEvent.captureSession = 123;
+ halEvent.captureDelayMs = 234;
+ halEvent.capturePreambleMs = 345;
+ halEvent.triggerInData = true;
+ halEvent.audioConfig = new AudioConfig();
+ halEvent.audioConfig.sampleRateHz = 456;
+ halEvent.audioConfig.channelMask = AudioChannelMask.IN_LEFT;
+ halEvent.audioConfig.format = AudioFormat.MP3;
+ // hwEvent.audioConfig.offloadInfo is irrelevant.
+ halEvent.data.add((byte) 31);
+ halEvent.data.add((byte) 32);
+ halEvent.data.add((byte) 33);
+ return halEvent;
+ }
+
+ private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_1(
+ int hwHandle,
+ int status) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+ halEvent.header = createRecognitionEvent_2_0(hwHandle, status);
+ halEvent.header.data.clear();
+ halEvent.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[]{31, 32, 33});
+ return halEvent;
+ }
+
+ private static void validateRecognitionEvent(RecognitionEvent event, int status) {
+ assertEquals(status, event.status);
+ assertEquals(SoundModelType.GENERIC, event.type);
+ assertTrue(event.captureAvailable);
+ assertEquals(101, event.captureSession);
+ assertEquals(234, event.captureDelayMs);
+ assertEquals(345, event.capturePreambleMs);
+ assertTrue(event.triggerInData);
+ assertEquals(456, event.audioConfig.sampleRateHz);
+ assertEquals(AudioChannelMask.IN_LEFT, event.audioConfig.channelMask);
+ assertEquals(AudioFormat.MP3, event.audioConfig.format);
+ }
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_0(
+ int hwHandle, int status) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ halEvent.common = createRecognitionEvent_2_0(hwHandle, status);
+
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ halExtra.id = 123;
+ halExtra.confidenceLevel = 52;
+ halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ halLevel.userId = 31;
+ halLevel.levelPercent = 43;
+ halExtra.levels.add(halLevel);
+ halEvent.phraseExtras.add(halExtra);
+ return halEvent;
+ }
+
+ private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_1(
+ int hwHandle, int status) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ halEvent.common = createRecognitionEvent_2_1(hwHandle, status);
+
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ halExtra.id = 123;
+ halExtra.confidenceLevel = 52;
+ halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ halLevel.userId = 31;
+ halLevel.levelPercent = 43;
+ halExtra.levels.add(halLevel);
+ halEvent.phraseExtras.add(halExtra);
+ return halEvent;
+ }
+
+ private static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event, int status) {
+ validateRecognitionEvent(event.common, status);
+
+ assertEquals(1, event.phraseExtras.length);
+ assertEquals(123, event.phraseExtras[0].id);
+ assertEquals(52, event.phraseExtras[0].confidenceLevel);
+ assertEquals(RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER,
+ event.phraseExtras[0].recognitionModes);
+ assertEquals(1, event.phraseExtras[0].levels.length);
+ assertEquals(31, event.phraseExtras[0].levels[0].userId);
+ assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
+ }
+
+ private void initService(boolean supportConcurrentCapture) throws RemoteException {
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+ createDefaultProperties(
+ supportConcurrentCapture);
+ ((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
+ 0)).onValues(0,
+ properties);
+ return null;
+ }).when(mHalDriver).getProperties(any());
+ mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+ }
+
+ private int 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);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+ invocation.getArgument(1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadSoundModelCallback
+ resultCallback = invocation.getArgument(3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.status =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.model = hwHandle;
+ callback.soundModelCallback(modelEvent, callbackCookie);
+ return null;
+ }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadModel(model);
+ verify(mHalDriver).loadSoundModel(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel hidlModel =
+ modelCaptor.getValue();
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+ hidlModel.type);
+ assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.uuid));
+ assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
+ assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
+
+ return handle;
+ }
+
+ private int 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);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+ invocation.getArgument(1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback
+ resultCallback = invocation.getArgument(3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.header.status =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.header.model = hwHandle;
+ callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+ return null;
+ }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadModel(model);
+ verify(driver).loadSoundModel_2_1(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel hidlModel =
+ modelCaptor.getValue();
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+ hidlModel.header.type);
+ assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.uuid));
+ assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.vendorUuid));
+ assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+ HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
+
+ return handle;
+ }
+
+ private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return loadGenericModel_2_1(module, hwHandle);
+ } else {
+ return loadGenericModel_2_0(module, hwHandle);
+ }
+ }
+
+ private int 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);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+ invocation.getArgument(
+ 1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadPhraseSoundModelCallback
+ resultCallback =
+ invocation.getArgument(
+ 3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.status =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.model = hwHandle;
+ callback.soundModelCallback(modelEvent, callbackCookie);
+ return null;
+ }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadPhraseModel(model);
+ verify(mHalDriver).loadPhraseSoundModel(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel hidlModel =
+ modelCaptor.getValue();
+
+ // Validate common part.
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+ hidlModel.common.type);
+ assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.uuid));
+ assertEquals(model.common.vendorUuid,
+ ConversionUtil.hidl2aidlUuid(hidlModel.common.vendorUuid));
+ assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.common.data.toArray());
+
+ // Validate phrase part.
+ assertEquals(1, hidlModel.phrases.size());
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Phrase hidlPhrase =
+ hidlModel.phrases.get(0);
+ assertEquals(123, hidlPhrase.id);
+ assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+ assertEquals("locale", hidlPhrase.locale);
+ assertEquals("text", hidlPhrase.text);
+ assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+ hidlPhrase.recognitionModes);
+
+ return handle;
+ }
+
+ private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+ PhraseSoundModel model = createPhraseSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
+ modelCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+ invocation.getArgument(
+ 1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback
+ resultCallback =
+ invocation.getArgument(
+ 3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.header.status =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.header.model = hwHandle;
+ callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+ return null;
+ }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadPhraseModel(model);
+ verify(driver).loadPhraseSoundModel_2_1(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel hidlModel =
+ modelCaptor.getValue();
+
+ // Validate common part.
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+ hidlModel.common.header.type);
+ assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.header.uuid));
+ assertEquals(model.common.vendorUuid,
+ ConversionUtil.hidl2aidlUuid(hidlModel.common.header.vendorUuid));
+ assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+ HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.common.data));
+
+ // Validate phrase part.
+ assertEquals(1, hidlModel.phrases.size());
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Phrase hidlPhrase =
+ hidlModel.phrases.get(0);
+ assertEquals(123, hidlPhrase.id);
+ assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+ assertEquals("locale", hidlPhrase.locale);
+ assertEquals("text", hidlPhrase.text);
+ assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+ hidlPhrase.recognitionModes);
+
+ return handle;
+ }
+
+ private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return loadPhraseModel_2_1(module, hwHandle);
+ } else {
+ return loadPhraseModel_2_0(module, hwHandle);
+ }
+ }
+
+ private void unloadModel(ISoundTriggerModule module, int handle, int hwHandle)
+ throws RemoteException {
+ module.unloadModel(handle);
+ verify(mHalDriver).unloadSoundModel(hwHandle);
+ verify(mAudioSessionProvider).releaseSession(101);
+ }
+
+ private SoundTriggerHwCallback 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);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(mHalDriver).startRecognition(eq(hwHandle), any(), any(), anyInt());
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig halConfig =
+ configCaptor.getValue();
+ assertTrue(halConfig.captureRequested);
+ assertEquals(102, halConfig.captureHandle);
+ assertEquals(103, halConfig.captureDevice);
+ assertEquals(1, halConfig.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig.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}, halConfig.data.toArray());
+
+ return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+ }
+
+ private SoundTriggerHwCallback 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;
+
+ 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);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(driver).startRecognition_2_1(eq(hwHandle), any(), any(), anyInt());
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig =
+ configCaptor.getValue();
+ assertTrue(halConfig.header.captureRequested);
+ assertEquals(102, halConfig.header.captureHandle);
+ assertEquals(103, halConfig.header.captureDevice);
+ assertEquals(1, halConfig.header.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig.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.data));
+
+ return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+ }
+
+ private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return startRecognition_2_1(module, handle, hwHandle);
+ } else {
+ return startRecognition_2_0(module, handle, hwHandle);
+ }
+ }
+
+ private RecognitionConfig createRecognitionConfig() {
+ RecognitionConfig config = new RecognitionConfig();
+ config.captureRequested = true;
+ config.phraseRecognitionExtras = new PhraseRecognitionExtra[]{new PhraseRecognitionExtra()};
+ config.phraseRecognitionExtras[0].id = 123;
+ config.phraseRecognitionExtras[0].confidenceLevel = 4;
+ config.phraseRecognitionExtras[0].recognitionModes = 5;
+ config.phraseRecognitionExtras[0].levels = new ConfidenceLevel[]{new ConfidenceLevel()};
+ config.phraseRecognitionExtras[0].levels[0].userId = 234;
+ config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
+ config.data = new byte[]{5, 4, 3, 2, 1};
+ return config;
+ }
+
+ private void stopRecognition(ISoundTriggerModule module, int handle, int hwHandle)
+ throws RemoteException {
+ when(mHalDriver.stopRecognition(hwHandle)).thenReturn(0);
+ module.stopRecognition(handle);
+ verify(mHalDriver).stopRecognition(hwHandle);
+ }
+
+ private void verifyNotStartRecognition() throws RemoteException {
+ verify(mHalDriver, never()).startRecognition(anyInt(), any(), any(), anyInt());
+ 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());
+ }
+ }
+
+
+ @Before
+ public void setUp() throws Exception {
+ clearInvocations(mHalDriver);
+ clearInvocations(mAudioSessionProvider);
+
+ // This binder is associated with the mock, so it can be cast to either version of the
+ // HAL interface.
+ final IHwBinder binder = new IHwBinder() {
+ @Override
+ public void transact(int code, HwParcel request, HwParcel reply, int flags)
+ throws RemoteException {
+ // This is a little hacky, but a very easy way to gracefully reject a request for
+ // an unsupported interface (after queryLocalInterface() returns null, the client
+ // will attempt a remote transaction to obtain the interface. RemoteException will
+ // cause it to give up).
+ throw new RemoteException();
+ }
+
+ @Override
+ public IHwInterface queryLocalInterface(String descriptor) {
+ if (descriptor.equals("android.hardware.soundtrigger@2.0::ISoundTriggerHw")
+ || descriptor.equals("android.hardware.soundtrigger@2.1::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw
+ || descriptor.equals("android.hardware.soundtrigger@2.2::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw
+ || descriptor.equals("android.hardware.soundtrigger@2.3::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ return mHalDriver;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean unlinkToDeath(DeathRecipient recipient) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ when(mHalDriver.asBinder()).thenReturn(binder);
+ }
+
+ @Test
+ public void testSetUpAndTearDown() {
+ }
+
+ @Test
+ public void testListModules() throws Exception {
+ initService(true);
+ // Note: input and output properties are NOT the same type, even though they are in any way
+ // equivalent. One is a type that's exposed by the HAL and one is a type that's exposed by
+ // the service. The service actually performs a (trivial) conversion between the two.
+ SoundTriggerModuleDescriptor[] allDescriptors = mService.listModules();
+ assertEquals(1, allDescriptors.length);
+
+ SoundTriggerModuleProperties properties = allDescriptors[0].properties;
+
+ validateDefaultProperties(properties, true);
+ }
+
+ @Test
+ public void testAttachDetach() throws Exception {
+ // Normal attachment / detachment.
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testAttachDetachNotAvailable() throws Exception {
+ // Attachment / detachment during external capture, with a module not supporting concurrent
+ // capture.
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(false);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testAttachDetachAvailable() throws Exception {
+ // Attachment / detachment during external capture, with a module supporting concurrent
+ // capture.
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testLoadUnloadModel() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testLoadUnloadPhraseModel() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ final int hwHandle = 73;
+ int handle = loadPhraseModel(module, hwHandle);
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testStartStopRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testStartStopPhraseRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 67;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Signal a capture from the driver.
+ hwCallback.sendRecognitionEvent(hwHandle,
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testPhraseRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Signal a capture from the driver.
+ hwCallback.sendPhraseRecognitionEvent(hwHandle,
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testForceRecognition() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 17;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Force a trigger.
+ module.forceRecognitionEvent(handle);
+ verify(driver).getModelState(hwHandle);
+
+ // Signal a capture from the driver.
+ // '3' means 'forced', there's no constant for that in the HAL.
+ hwCallback.sendRecognitionEvent(hwHandle, 3);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testForcePhraseRecognition() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 17;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Force a trigger.
+ module.forceRecognitionEvent(handle);
+ verify(driver).getModelState(hwHandle);
+
+ // Signal a capture from the driver.
+ // '3' means 'forced', there's no constant for that in the HAL.
+ hwCallback.sendPhraseRecognitionEvent(hwHandle, 3);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testAbortRecognition() throws Exception {
+ // Make sure the HAL doesn't support concurrent capture.
+ initService(false);
+ mService.setExternalCaptureState(false);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Load the model.
+ final int hwHandle = 11;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Abort.
+ mService.setExternalCaptureState(true);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+
+ // Make sure we are notified of the lost availability.
+ verify(callback).onRecognitionAvailabilityChange(false);
+
+ // Attempt to start a new recognition - should get an abort event immediately, without
+ // involving the HAL.
+ clearInvocations(callback);
+ clearInvocations(mHalDriver);
+ module.startRecognition(handle, createRecognitionConfig());
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+ verifyNotStartRecognition();
+
+ // Now enable it and make sure we are notified.
+ mService.setExternalCaptureState(false);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testAbortPhraseRecognition() throws Exception {
+ // Make sure the HAL doesn't support concurrent capture.
+ initService(false);
+ mService.setExternalCaptureState(false);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Load the model.
+ final int hwHandle = 11;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Abort.
+ mService.setExternalCaptureState(true);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+
+ // Make sure we are notified of the lost availability.
+ verify(callback).onRecognitionAvailabilityChange(false);
+
+ // Attempt to start a new recognition - should get an abort event immediately, without
+ // involving the HAL.
+ clearInvocations(callback);
+ clearInvocations(mHalDriver);
+ module.startRecognition(handle, createRecognitionConfig());
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+ verifyNotStartRecognition();
+
+ // Now enable it and make sure we are notified.
+ mService.setExternalCaptureState(false);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testNotAbortRecognitionConcurrent() throws Exception {
+ // Make sure the HAL supports concurrent capture.
+ initService(true);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ clearInvocations(callback);
+
+ // Load the model.
+ final int hwHandle = 13;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Signal concurrent capture. Shouldn't abort.
+ mService.setExternalCaptureState(true);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Initiating a new one should work fine.
+ clearInvocations(mHalDriver);
+ startRecognition(module, handle, hwHandle);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ module.unloadModel(handle);
+ module.detach();
+ }
+
+ @Test
+ public void testNotAbortPhraseRecognitionConcurrent() throws Exception {
+ // Make sure the HAL supports concurrent capture.
+ initService(true);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ clearInvocations(callback);
+
+ // Load the model.
+ final int hwHandle = 13;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Signal concurrent capture. Shouldn't abort.
+ mService.setExternalCaptureState(true);
+ verify(callback, never()).onPhraseRecognition(anyInt(), any());
+ verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Initiating a new one should work fine.
+ clearInvocations(mHalDriver);
+ startRecognition(module, handle, hwHandle);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ module.unloadModel(handle);
+ module.detach();
+ }
+
+ @Test
+ public void testParameterSupported() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 12;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer((Answer<Void>) invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+ resultCallback = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_3.ModelParameterRange range =
+ new android.hardware.soundtrigger.V2_3.ModelParameterRange();
+ range.start = 23;
+ range.end = 45;
+ OptionalModelParameterRange optionalRange = new OptionalModelParameterRange();
+ optionalRange.range(range);
+ resultCallback.onValues(0, optionalRange);
+ return null;
+ }).when(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertEquals(23, range.minInclusive);
+ assertEquals(45, range.maxInclusive);
+ }
+
+ @Test
+ public void testParameterNotSupportedOld() throws Exception {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ return;
+ }
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 13;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ assertNull(range);
+ }
+
+ @Test
+ public void testParameterNotSupported() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 13;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+ resultCallback = invocation.getArgument(2);
+ // This is the return of this method.
+ resultCallback.onValues(0, new OptionalModelParameterRange());
+ return null;
+ }).when(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertNull(range);
+ }
+
+ @Test
+ public void testGetParameter() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 14;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
+ resultCallback = invocation.getArgument(2);
+ // This is the return of this method.
+ resultCallback.onValues(0, 234);
+ return null;
+ }).when(driver).getParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ int value = module.getModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).getParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertEquals(234, value);
+ }
+
+ @Test
+ public void testSetParameter() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 17;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ when(driver.setParameter(hwHandle,
+ android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
+ 456)).thenReturn(0);
+
+ module.setModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR, 456);
+
+ verify(driver).setParameter(hwHandle,
+ android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR, 456);
+ }
+
+ private static class SoundTriggerHwCallback {
+ private final android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback mCallback;
+ private final int mCookie;
+
+ SoundTriggerHwCallback(android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback,
+ int cookie) {
+ mCallback = callback;
+ mCookie = cookie;
+ }
+
+ private void sendRecognitionEvent(int hwHandle, int status) throws RemoteException {
+ if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+ ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).recognitionCallback_2_1(
+ createRecognitionEvent_2_1(hwHandle, status), mCookie);
+ } else {
+ mCallback.recognitionCallback(createRecognitionEvent_2_0(hwHandle, status),
+ mCookie);
+ }
+ }
+
+ private void sendPhraseRecognitionEvent(int hwHandle, int status) throws RemoteException {
+ if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+ ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).phraseRecognitionCallback_2_1(
+ createPhraseRecognitionEvent_2_1(hwHandle, status), mCookie);
+ } else {
+ mCallback.phraseRecognitionCallback(
+ createPhraseRecognitionEvent_2_0(hwHandle, status), mCookie);
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/ArrayMapWithHistoryTest.java b/services/tests/servicestests/src/com/android/server/timedetector/ArrayMapWithHistoryTest.java
new file mode 100644
index 0000000..b6eea46
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timedetector/ArrayMapWithHistoryTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.timedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.util.ArrayMap;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.timezonedetector.ArrayMapWithHistory;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.StringWriter;
+import java.util.concurrent.Callable;
+
+@RunWith(AndroidJUnit4.class)
+public class ArrayMapWithHistoryTest {
+
+ @Test
+ public void testValueHistoryBehavior() {
+ // Create a map that will retain 2 values per key.
+ ArrayMapWithHistory<String, String> historyMap = new ArrayMapWithHistory<>(2 /* history */);
+ ArrayMap<String, String> arrayMap = new ArrayMap<>();
+
+ compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", null));
+
+ assertEquals(0, historyMap.getHistoryCountForKeyForTests("K1"));
+ assertToStringAndDumpNotNull(historyMap);
+
+ putAndCompareReturnValue(historyMap, arrayMap, "K1", "V1");
+ compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V1"));
+ compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
+
+ assertEquals(1, historyMap.getHistoryCountForKeyForTests("K1"));
+ assertToStringAndDumpNotNull(historyMap);
+
+ // put() a new value for the same key.
+ putAndCompareReturnValue(historyMap, arrayMap, "K1", "V2");
+ compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V2"));
+ compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
+
+ assertEquals(2, historyMap.getHistoryCountForKeyForTests("K1"));
+ assertToStringAndDumpNotNull(historyMap);
+
+ // put() a new value for the same key. We should have hit the limit of "2 values retained
+ // per key".
+ putAndCompareReturnValue(historyMap, arrayMap, "K1", "V3");
+ compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V3"));
+ compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
+
+ assertEquals(2, historyMap.getHistoryCountForKeyForTests("K1"));
+ assertToStringAndDumpNotNull(historyMap);
+ }
+
+ @Test
+ public void testMapBehavior() throws Exception {
+ ArrayMapWithHistory<String, String> historyMap = new ArrayMapWithHistory<>(2);
+ ArrayMap<String, String> arrayMap = new ArrayMap<>();
+
+ compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", null), entry("K2", null));
+ assertIndexAccessThrowsException(0, historyMap, arrayMap);
+
+ assertEquals(0, historyMap.getHistoryCountForKeyForTests("K1"));
+ assertEquals(0, historyMap.getHistoryCountForKeyForTests("K2"));
+
+ putAndCompareReturnValue(historyMap, arrayMap, "K1", "V1");
+ compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V1"), entry("K2", null));
+ compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
+ // TODO Restore after http://b/146563025 is fixed and ArrayMap behaves properly in tests.
+ // assertIndexAccessThrowsException(1, historyMap, arrayMap);
+
+ assertEquals(1, historyMap.getHistoryCountForKeyForTests("K1"));
+ assertToStringAndDumpNotNull(historyMap);
+
+ putAndCompareReturnValue(historyMap, arrayMap, "K2", "V2");
+ compareGetAndSizeForKeys(historyMap, arrayMap, entry("K1", "V1"), entry("K2", "V2"));
+ compareKeyAtAndValueAtForIndex(0, historyMap, arrayMap);
+ compareKeyAtAndValueAtForIndex(1, historyMap, arrayMap);
+ // TODO Restore after http://b/146563025 is fixed and ArrayMap behaves properly in tests.
+ // assertIndexAccessThrowsException(2, historyMap, arrayMap);
+
+ assertEquals(1, historyMap.getHistoryCountForKeyForTests("K1"));
+ assertEquals(1, historyMap.getHistoryCountForKeyForTests("K2"));
+ assertToStringAndDumpNotNull(historyMap);
+ }
+
+ private static String dumpHistoryMap(ArrayMapWithHistory<?, ?> historyMap) {
+ StringWriter stringWriter = new StringWriter();
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ")) {
+ historyMap.dump(ipw);
+ return stringWriter.toString();
+ }
+ }
+
+ private static <K, V> void putAndCompareReturnValue(ArrayMapWithHistory<K, V> historyMap,
+ ArrayMap<K, V> arrayMap, K key, V value) {
+ assertEquals(arrayMap.put(key, value), historyMap.put(key, value));
+ }
+
+ private static class Entry<K, V> {
+ public final K key;
+ public final V value;
+
+ Entry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+ }
+
+ private static <K, V> Entry<K, V> entry(K key, V value) {
+ return new Entry<>(key, value);
+ }
+
+ @SafeVarargs
+ private static <K, V> void compareGetAndSizeForKeys(ArrayMapWithHistory<K, V> historyMap,
+ ArrayMap<K, V> arrayMap, Entry<K, V>... expectedEntries) {
+ for (Entry<K, V> expectedEntry : expectedEntries) {
+ assertEquals(arrayMap.get(expectedEntry.key), historyMap.get(expectedEntry.key));
+ assertEquals(expectedEntry.value, historyMap.get(expectedEntry.key));
+ }
+ assertEquals(arrayMap.size(), historyMap.size());
+ }
+
+ private static void compareKeyAtAndValueAtForIndex(
+ int index, ArrayMapWithHistory<?, ?> historyMap, ArrayMap<?, ?> arrayMap) {
+ assertEquals(arrayMap.keyAt(index), historyMap.keyAt(index));
+ assertEquals(arrayMap.valueAt(index), historyMap.valueAt(index));
+ }
+
+ private static void assertIndexAccessThrowsException(
+ int index, ArrayMapWithHistory<?, ?> historyMap, ArrayMap<?, ?> arrayMap)
+ throws Exception {
+ assertThrowsArrayIndexOutOfBoundsException(
+ "ArrayMap.keyAt(" + index + ")", () -> arrayMap.keyAt(index));
+ assertThrowsArrayIndexOutOfBoundsException(
+ "ArrayMapWithHistory.keyAt(" + index + ")", () -> historyMap.keyAt(index));
+ assertThrowsArrayIndexOutOfBoundsException(
+ "ArrayMap.keyAt(" + index + ")", () -> arrayMap.valueAt(index));
+ assertThrowsArrayIndexOutOfBoundsException(
+ "ArrayMapWithHistory.keyAt(" + index + ")", () -> historyMap.valueAt(index));
+ }
+
+ private static void assertThrowsArrayIndexOutOfBoundsException(
+ String description, Callable<?> callable) throws Exception {
+ try {
+ callable.call();
+ fail("Expected exception for " + description);
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // This is fine.
+ } catch (Exception e) {
+ // Any other exception is just rethrown.
+ throw e;
+ }
+ }
+
+ private static void assertToStringAndDumpNotNull(ArrayMapWithHistory<?, ?> historyMap) {
+ assertNotNull(historyMap.toString());
+ assertNotNull(dumpHistoryMap(historyMap));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/ReferenceWithHistoryTest.java b/services/tests/servicestests/src/com/android/server/timedetector/ReferenceWithHistoryTest.java
new file mode 100644
index 0000000..ce72499
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timedetector/ReferenceWithHistoryTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.timedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.timezonedetector.ReferenceWithHistory;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.StringWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class ReferenceWithHistoryTest {
+
+ @Test
+ public void testBasicReferenceBehavior() {
+ // Create a reference that will retain 2 history values.
+ ReferenceWithHistory<String> referenceWithHistory =
+ new ReferenceWithHistory<>(2 /* history */);
+ TestRef<String> reference = new TestRef<>();
+
+ // Check unset behavior.
+ compareGet(referenceWithHistory, reference, null);
+ assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
+ compareToString(referenceWithHistory, reference, "null");
+
+ // Try setting null.
+ setAndCompareReturnValue(referenceWithHistory, reference, null);
+ compareGet(referenceWithHistory, reference, null);
+ assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
+ compareToString(referenceWithHistory, reference, "null");
+
+ // Try setting a non-null value.
+ setAndCompareReturnValue(referenceWithHistory, reference, "Foo");
+ compareGet(referenceWithHistory, reference, "Foo");
+ assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
+ compareToString(referenceWithHistory, reference, "Foo");
+
+ // Try setting null again.
+ setAndCompareReturnValue(referenceWithHistory, reference, "Foo");
+ compareGet(referenceWithHistory, reference, "Foo");
+ assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
+ compareToString(referenceWithHistory, reference, "Foo");
+
+ // Try a non-null value again.
+ setAndCompareReturnValue(referenceWithHistory, reference, "Bar");
+ compareGet(referenceWithHistory, reference, "Bar");
+ assertNotNull(dumpReferenceWithHistory(referenceWithHistory));
+ compareToString(referenceWithHistory, reference, "Bar");
+ }
+
+ @Test
+ public void testValueHistoryBehavior() {
+ // Create a reference that will retain 2 history values.
+ ReferenceWithHistory<String> referenceWithHistory =
+ new ReferenceWithHistory<>(2 /* history */);
+ TestRef<String> reference = new TestRef<>();
+
+ // Assert behavior before anything is set.
+ assertEquals(0, referenceWithHistory.getHistoryCount());
+
+ // Set a value (1).
+ setAndCompareReturnValue(referenceWithHistory, reference, "V1");
+ assertEquals(1, referenceWithHistory.getHistoryCount());
+
+ // Set a value (2).
+ setAndCompareReturnValue(referenceWithHistory, reference, "V2");
+ assertEquals(2, referenceWithHistory.getHistoryCount());
+
+ // Set a value (3).
+ // We should have hit the limit of "2 history values retained per key".
+ setAndCompareReturnValue(referenceWithHistory, reference, "V3");
+ assertEquals(2, referenceWithHistory.getHistoryCount());
+ }
+
+ /**
+ * A simple class that has the same behavior as ReferenceWithHistory without the history. Used
+ * in tests for comparison.
+ */
+ private static class TestRef<V> {
+ private V mValue;
+
+ public V get() {
+ return mValue;
+ }
+
+ public V set(V value) {
+ V previous = mValue;
+ mValue = value;
+ return previous;
+ }
+
+ public String toString() {
+ return String.valueOf(mValue);
+ }
+ }
+
+ private static void compareGet(
+ ReferenceWithHistory<?> referenceWithHistory, TestRef<?> reference, Object value) {
+ assertEquals(reference.get(), referenceWithHistory.get());
+ assertEquals(value, reference.get());
+ }
+
+ private static <T> void setAndCompareReturnValue(
+ ReferenceWithHistory<T> referenceWithHistory, TestRef<T> reference, T newValue) {
+ assertEquals(reference.set(newValue), referenceWithHistory.set(newValue));
+ }
+
+ private static void compareToString(
+ ReferenceWithHistory<?> referenceWithHistory, TestRef<?> reference, String expected) {
+ assertEquals(reference.toString(), referenceWithHistory.toString());
+ assertEquals(expected, referenceWithHistory.toString());
+ }
+
+ private static String dumpReferenceWithHistory(ReferenceWithHistory<?> referenceWithHistory) {
+ StringWriter stringWriter = new StringWriter();
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ")) {
+ referenceWithHistory.dump(ipw);
+ return stringWriter.toString();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java
deleted file mode 100644
index 7a0a28d..0000000
--- a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java
+++ /dev/null
@@ -1,661 +0,0 @@
-/*
- * 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.android.server.timedetector;
-
-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.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 androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.time.Duration;
-
-@RunWith(AndroidJUnit4.class)
-public class SimpleTimeDetectorStrategyTest {
-
- private static final Scenario SCENARIO_1 = new Scenario.Builder()
- .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
- .setInitialDeviceRealtimeMillis(123456789L)
- .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
- .build();
-
- private static final int ARBITRARY_PHONE_ID = 123456;
-
- private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis();
-
- private Script mScript;
-
- @Before
- public void setUp() {
- mScript = new Script();
- }
-
- @Test
- public void testSuggestPhoneTime_autoTimeEnabled() {
- Scenario scenario = SCENARIO_1;
- mScript.pokeFakeClocks(scenario)
- .pokeTimeDetectionEnabled(true);
-
- PhoneTimeSuggestion timeSuggestion =
- scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
- final int clockIncrement = 1000;
- long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
-
- mScript.simulateTimePassing(clockIncrement)
- .simulatePhoneTimeSuggestion(timeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectSystemClockMillis, true /* expectNetworkBroadcast */);
- }
-
- @Test
- public void testSuggestPhoneTime_emptySuggestionIgnored() {
- Scenario scenario = SCENARIO_1;
- mScript.pokeFakeClocks(scenario)
- .pokeTimeDetectionEnabled(true);
-
- PhoneTimeSuggestion timeSuggestion = createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, null);
-
- mScript.simulatePhoneTimeSuggestion(timeSuggestion)
- .verifySystemClockWasNotSetAndResetCallTracking();
- }
-
- @Test
- public void testSuggestPhoneTime_systemClockThreshold() {
- Scenario scenario = SCENARIO_1;
- final int systemClockUpdateThresholdMillis = 1000;
- mScript.pokeFakeClocks(scenario)
- .pokeThresholds(systemClockUpdateThresholdMillis)
- .pokeTimeDetectionEnabled(true);
-
- PhoneTimeSuggestion timeSuggestion1 =
- scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
- TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
-
- final int clockIncrement = 100;
- // Increment the the device clocks to simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
-
- long expectSystemClockMillis1 =
- TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
-
- // Send the first time signal. It should be used.
- mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasSetAndResetCallTracking(
- expectSystemClockMillis1, true /* expectNetworkBroadcast */);
-
- // Now send another time signal, but one that is too similar to the last one and should be
- // ignored.
- int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
- TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
- mScript.peekElapsedRealtimeMillis(),
- mScript.peekSystemClockMillis() + underThresholdMillis);
- PhoneTimeSuggestion timeSuggestion2 =
- createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
- mScript.simulateTimePassing(clockIncrement)
- .simulatePhoneTimeSuggestion(timeSuggestion2)
- .verifySystemClockWasNotSetAndResetCallTracking();
-
- // Now send another time signal, but one that is on the threshold and so should be used.
- TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
- mScript.peekElapsedRealtimeMillis(),
- mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
-
- PhoneTimeSuggestion timeSuggestion3 =
- createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
- mScript.simulateTimePassing(clockIncrement);
-
- long expectSystemClockMillis3 =
- TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
-
- mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
- .verifySystemClockWasSetAndResetCallTracking(
- expectSystemClockMillis3, true /* expectNetworkBroadcast */);
- }
-
- @Test
- public void testSuggestPhoneTime_autoTimeDisabled() {
- Scenario scenario = SCENARIO_1;
- mScript.pokeFakeClocks(scenario)
- .pokeTimeDetectionEnabled(false);
-
- PhoneTimeSuggestion timeSuggestion =
- scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
- mScript.simulatePhoneTimeSuggestion(timeSuggestion)
- .verifySystemClockWasNotSetAndResetCallTracking();
- }
-
- @Test
- public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
- Scenario scenario = SCENARIO_1;
- final int systemClockUpdateThreshold = 2000;
- mScript.pokeFakeClocks(scenario)
- .pokeThresholds(systemClockUpdateThreshold)
- .pokeTimeDetectionEnabled(true);
- PhoneTimeSuggestion timeSuggestion1 =
- scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
- TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
-
- // Initialize the strategy / device with a time set from NITZ.
- mScript.simulateTimePassing(100);
- long expectedSystemClockMillis1 =
- TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
- mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
-
- // The UTC time increment should be larger than the system clock update threshold so we
- // know it shouldn't be ignored for other reasons.
- long validUtcTimeMillis = utcTime1.getValue() + (2 * systemClockUpdateThreshold);
-
- // Now supply a new signal that has an obviously bogus reference time : older than the last
- // one.
- long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
- TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
- referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
- PhoneTimeSuggestion timeSuggestion2 =
- createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
- mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
- .verifySystemClockWasNotSetAndResetCallTracking();
-
- // Now supply a new signal that has an obviously bogus reference time : substantially in the
- // future.
- long referenceTimeInFutureMillis =
- utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
- TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
- referenceTimeInFutureMillis, validUtcTimeMillis);
- PhoneTimeSuggestion timeSuggestion3 =
- createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
- mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
- .verifySystemClockWasNotSetAndResetCallTracking();
-
- // Just to prove validUtcTimeMillis is valid.
- long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
- TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
- validReferenceTimeMillis, validUtcTimeMillis);
- long expectedSystemClockMillis4 =
- TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
- PhoneTimeSuggestion timeSuggestion4 =
- createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4);
- mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis4, true /* expectNetworkBroadcast */);
- }
-
- @Test
- public void testSuggestPhoneTime_timeDetectionToggled() {
- Scenario scenario = SCENARIO_1;
- final int clockIncrementMillis = 100;
- final int systemClockUpdateThreshold = 2000;
- mScript.pokeFakeClocks(scenario)
- .pokeThresholds(systemClockUpdateThreshold)
- .pokeTimeDetectionEnabled(false);
-
- PhoneTimeSuggestion timeSuggestion1 =
- scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
- TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
-
- // Simulate time passing.
- mScript.simulateTimePassing(clockIncrementMillis);
-
- // Simulate the time signal being received. It should not be used because auto time
- // detection is off but it should be recorded.
- mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasNotSetAndResetCallTracking();
-
- // Simulate more time passing.
- mScript.simulateTimePassing(clockIncrementMillis);
-
- long expectedSystemClockMillis1 =
- TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
-
- // Turn on auto time detection.
- mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */);
-
- // Turn off auto time detection.
- mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasNotSetAndResetCallTracking();
-
- // Receive another valid time signal.
- // It should be on the threshold and accounting for the clock increments.
- TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
- mScript.peekElapsedRealtimeMillis(),
- mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
- PhoneTimeSuggestion timeSuggestion2 =
- createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
-
- // Simulate more time passing.
- mScript.simulateTimePassing(clockIncrementMillis);
-
- long expectedSystemClockMillis2 =
- TimeDetectorStrategy.getTimeAt(utcTime2, mScript.peekElapsedRealtimeMillis());
-
- // The new time, though valid, should not be set in the system clock because auto time is
- // disabled.
- mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
- .verifySystemClockWasNotSetAndResetCallTracking();
-
- // Turn on auto time detection.
- mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis2, true /* expectNetworkBroadcast */);
- }
-
- @Test
- public void testSuggestManualTime_autoTimeDisabled() {
- Scenario scenario = SCENARIO_1;
- mScript.pokeFakeClocks(scenario)
- .pokeTimeDetectionEnabled(false);
-
- ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
- final int clockIncrement = 1000;
- long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
-
- mScript.simulateTimePassing(clockIncrement)
- .simulateManualTimeSuggestion(timeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectSystemClockMillis, false /* expectNetworkBroadcast */);
- }
-
- @Test
- public void testSuggestManualTime_retainsAutoSignal() {
- Scenario scenario = SCENARIO_1;
-
- // Configure the start state.
- mScript.pokeFakeClocks(scenario)
- .pokeTimeDetectionEnabled(true);
-
- // Simulate a phone suggestion.
- PhoneTimeSuggestion phoneTimeSuggestion =
- scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
- long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
- final int clockIncrement = 1000;
-
- // Simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
- expectedAutoClockMillis += clockIncrement;
-
- mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedAutoClockMillis, true /* expectNetworkBroadcast */);
-
- // Simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
- expectedAutoClockMillis += clockIncrement;
-
- // Switch to manual.
- mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasNotSetAndResetCallTracking();
-
- // Simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
- expectedAutoClockMillis += clockIncrement;
-
-
- // Simulate a manual suggestion 1 day different from the auto suggestion.
- long manualTimeMillis = SCENARIO_1.getActualTimeMillis() + ONE_DAY_MILLIS;
- long expectedManualClockMillis = manualTimeMillis;
- ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(manualTimeMillis);
- mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedManualClockMillis, false /* expectNetworkBroadcast */);
-
- // Simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
- expectedAutoClockMillis += clockIncrement;
-
- // Switch back to auto.
- mScript.simulateAutoTimeDetectionToggle();
-
- mScript.verifySystemClockWasSetAndResetCallTracking(
- expectedAutoClockMillis, true /* expectNetworkBroadcast */);
-
- // Switch back to manual - nothing should happen to the clock.
- mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasNotSetAndResetCallTracking();
- }
-
- /**
- * Manual suggestions should be ignored if auto time is enabled.
- */
- @Test
- public void testSuggestManualTime_autoTimeEnabled() {
- Scenario scenario = SCENARIO_1;
- mScript.pokeFakeClocks(scenario)
- .pokeTimeDetectionEnabled(true);
-
- ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual();
- final int clockIncrement = 1000;
-
- mScript.simulateTimePassing(clockIncrement)
- .simulateManualTimeSuggestion(timeSuggestion)
- .verifySystemClockWasNotSetAndResetCallTracking();
- }
-
- /**
- * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
- * like the real thing should, it also asserts preconditions.
- */
- private static class FakeCallback implements TimeDetectorStrategy.Callback {
- private boolean mTimeDetectionEnabled;
- private boolean mWakeLockAcquired;
- private long mElapsedRealtimeMillis;
- private long mSystemClockMillis;
- private int mSystemClockUpdateThresholdMillis = 2000;
-
- // Tracking operations.
- private boolean mSystemClockWasSet;
- private Intent mBroadcastSent;
-
- @Override
- public int systemClockUpdateThresholdMillis() {
- return mSystemClockUpdateThresholdMillis;
- }
-
- @Override
- public boolean isAutoTimeDetectionEnabled() {
- return mTimeDetectionEnabled;
- }
-
- @Override
- public void acquireWakeLock() {
- if (mWakeLockAcquired) {
- fail("Wake lock already acquired");
- }
- mWakeLockAcquired = true;
- }
-
- @Override
- public long elapsedRealtimeMillis() {
- assertWakeLockAcquired();
- return mElapsedRealtimeMillis;
- }
-
- @Override
- public long systemClockMillis() {
- assertWakeLockAcquired();
- return mSystemClockMillis;
- }
-
- @Override
- public void setSystemClock(long newTimeMillis) {
- assertWakeLockAcquired();
- mSystemClockWasSet = true;
- mSystemClockMillis = newTimeMillis;
- }
-
- @Override
- public void releaseWakeLock() {
- assertWakeLockAcquired();
- mWakeLockAcquired = false;
- }
-
- @Override
- public void sendStickyBroadcast(Intent intent) {
- assertNotNull(intent);
- mBroadcastSent = intent;
- }
-
- // Methods below are for managing the fake's behavior.
-
- public void pokeSystemClockUpdateThreshold(int thresholdMillis) {
- mSystemClockUpdateThresholdMillis = thresholdMillis;
- }
-
- public void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
- mElapsedRealtimeMillis = elapsedRealtimeMillis;
- }
-
- public void pokeSystemClockMillis(long systemClockMillis) {
- mSystemClockMillis = systemClockMillis;
- }
-
- public void pokeAutoTimeDetectionEnabled(boolean enabled) {
- mTimeDetectionEnabled = enabled;
- }
-
- public long peekElapsedRealtimeMillis() {
- return mElapsedRealtimeMillis;
- }
-
- public long peekSystemClockMillis() {
- return mSystemClockMillis;
- }
-
- public void simulateTimePassing(int incrementMillis) {
- mElapsedRealtimeMillis += incrementMillis;
- mSystemClockMillis += incrementMillis;
- }
-
- public void simulateAutoTimeZoneDetectionToggle() {
- mTimeDetectionEnabled = !mTimeDetectionEnabled;
- }
-
- public void verifySystemClockNotSet() {
- assertFalse(mSystemClockWasSet);
- }
-
- public void verifySystemClockWasSet(long expectSystemClockMillis) {
- assertTrue(mSystemClockWasSet);
- assertEquals(expectSystemClockMillis, mSystemClockMillis);
- }
-
- public void verifyIntentWasBroadcast() {
- assertTrue(mBroadcastSent != null);
- }
-
- public void verifyIntentWasNotBroadcast() {
- assertNull(mBroadcastSent);
- }
-
- public void resetCallTracking() {
- mSystemClockWasSet = false;
- mBroadcastSent = null;
- }
-
- private void assertWakeLockAcquired() {
- assertTrue("The operation must be performed only after acquiring the wakelock",
- mWakeLockAcquired);
- }
- }
-
- /**
- * A fluent helper class for tests.
- */
- private class Script {
-
- private final FakeCallback mFakeCallback;
- private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy;
-
- Script() {
- mFakeCallback = new FakeCallback();
- mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy();
- mSimpleTimeDetectorStrategy.initialize(mFakeCallback);
-
- }
-
- Script pokeTimeDetectionEnabled(boolean enabled) {
- mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
- return this;
- }
-
- Script pokeFakeClocks(Scenario scenario) {
- mFakeCallback.pokeElapsedRealtimeMillis(scenario.getInitialRealTimeMillis());
- mFakeCallback.pokeSystemClockMillis(scenario.getInitialSystemClockMillis());
- return this;
- }
-
- Script pokeThresholds(int systemClockUpdateThreshold) {
- mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
- return this;
- }
-
- long peekElapsedRealtimeMillis() {
- return mFakeCallback.peekElapsedRealtimeMillis();
- }
-
- long peekSystemClockMillis() {
- return mFakeCallback.peekSystemClockMillis();
- }
-
- Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
- mSimpleTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
- return this;
- }
-
- Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
- mSimpleTimeDetectorStrategy.suggestManualTime(timeSuggestion);
- return this;
- }
-
- Script simulateAutoTimeDetectionToggle() {
- mFakeCallback.simulateAutoTimeZoneDetectionToggle();
- mSimpleTimeDetectorStrategy.handleAutoTimeDetectionChanged();
- return this;
- }
-
- Script simulateTimePassing(int clockIncrement) {
- mFakeCallback.simulateTimePassing(clockIncrement);
- return this;
- }
-
- Script verifySystemClockWasNotSetAndResetCallTracking() {
- mFakeCallback.verifySystemClockNotSet();
- mFakeCallback.verifyIntentWasNotBroadcast();
- mFakeCallback.resetCallTracking();
- return this;
- }
-
- Script verifySystemClockWasSetAndResetCallTracking(
- long expectSystemClockMillis, boolean expectNetworkBroadcast) {
- mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis);
- if (expectNetworkBroadcast) {
- mFakeCallback.verifyIntentWasBroadcast();
- }
- mFakeCallback.resetCallTracking();
- return this;
- }
- }
-
- /**
- * A starting scenario used during tests. Describes a fictional "physical" reality.
- */
- private static class Scenario {
-
- private final long mInitialDeviceSystemClockMillis;
- private final long mInitialDeviceRealtimeMillis;
- private final long mActualTimeMillis;
-
- Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis) {
- mInitialDeviceSystemClockMillis = initialDeviceSystemClock;
- mActualTimeMillis = timeMillis;
- mInitialDeviceRealtimeMillis = elapsedRealtime;
- }
-
- long getInitialRealTimeMillis() {
- return mInitialDeviceRealtimeMillis;
- }
-
- long getInitialSystemClockMillis() {
- return mInitialDeviceSystemClockMillis;
- }
-
- long getActualTimeMillis() {
- return mActualTimeMillis;
- }
-
- PhoneTimeSuggestion createPhoneTimeSuggestionForActual(int phoneId) {
- TimestampedValue<Long> time = new TimestampedValue<>(
- mInitialDeviceRealtimeMillis, mActualTimeMillis);
- return createPhoneTimeSuggestion(phoneId, time);
- }
-
- ManualTimeSuggestion createManualTimeSuggestionForActual() {
- TimestampedValue<Long> time = new TimestampedValue<>(
- mInitialDeviceRealtimeMillis, mActualTimeMillis);
- return new ManualTimeSuggestion(time);
- }
-
- static class Builder {
-
- private long mInitialDeviceSystemClockMillis;
- private long mInitialDeviceRealtimeMillis;
- private long mActualTimeMillis;
-
- Builder setInitialDeviceSystemClockUtc(int year, int monthInYear, int day,
- int hourOfDay, int minute, int second) {
- mInitialDeviceSystemClockMillis = createUtcTime(year, monthInYear, day, hourOfDay,
- minute, second);
- return this;
- }
-
- Builder setInitialDeviceRealtimeMillis(long realtimeMillis) {
- mInitialDeviceRealtimeMillis = realtimeMillis;
- return this;
- }
-
- Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
- int minute, int second) {
- mActualTimeMillis =
- createUtcTime(year, monthInYear, day, hourOfDay, minute, second);
- return this;
- }
-
- Scenario build() {
- return new Scenario(mInitialDeviceSystemClockMillis, mInitialDeviceRealtimeMillis,
- mActualTimeMillis);
- }
- }
- }
-
- private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,
- TimestampedValue<Long> utcTime) {
- return new PhoneTimeSuggestion.Builder(phoneId)
- .setUtcTime(utcTime)
- .build();
- }
-
- private ManualTimeSuggestion createManualTimeSuggestion(long timeMillis) {
- TimestampedValue<Long> utcTime =
- new TimestampedValue<>(mScript.peekElapsedRealtimeMillis(), timeMillis);
- return new ManualTimeSuggestion(utcTime);
- }
-
- private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
- int second) {
- Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
- cal.clear();
- cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
- return cal.getTimeInMillis();
- }
-}
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 84b495f..72a7f50 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -38,8 +38,6 @@
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.timedetector.TimeDetectorStrategy.Callback;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -52,7 +50,6 @@
private Context mMockContext;
private StubbedTimeDetectorStrategy mStubbedTimeDetectorStrategy;
- private Callback mMockCallback;
private TimeDetectorService mTimeDetectorService;
private HandlerThread mHandlerThread;
@@ -68,12 +65,10 @@
mHandlerThread.start();
mTestHandler = new TestHandler(mHandlerThread.getLooper());
- mMockCallback = mock(Callback.class);
mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy();
mTimeDetectorService = new TimeDetectorService(
- mMockContext, mTestHandler, mMockCallback,
- mStubbedTimeDetectorStrategy);
+ mMockContext, mTestHandler, mStubbedTimeDetectorStrategy);
}
@After
@@ -100,13 +95,13 @@
@Test
public void testSuggestManualTime() throws Exception {
- doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+ doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
mTimeDetectorService.suggestManualTime(manualTimeSuggestion);
mTestHandler.assertTotalMessagesEnqueued(1);
- verify(mMockContext).enforceCallingPermission(
+ verify(mMockContext).enforceCallingOrSelfPermission(
eq(android.Manifest.permission.SET_TIME),
anyString());
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
new file mode 100644
index 0000000..1aa3d8f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -0,0 +1,758 @@
+/*
+ * 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.android.server.timedetector;
+
+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.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 androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeDetectorStrategyImplTest {
+
+ private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO =
+ new TimestampedValue<>(
+ 123456789L /* realtimeClockMillis */,
+ createUtcTime(1977, 1, 1, 12, 0, 0));
+
+ 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
+ public void setUp() {
+ mScript = new Script();
+ }
+
+ @Test
+ public void testSuggestPhoneTime_autoTimeEnabled() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ 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(clockIncrement)
+ .simulatePhoneTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
+ }
+
+ @Test
+ public void testSuggestPhoneTime_emptySuggestionIgnored() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ int phoneId = ARBITRARY_PHONE_ID;
+ PhoneTimeSuggestion timeSuggestion =
+ mScript.generatePhoneTimeSuggestion(phoneId, null);
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, null);
+ }
+
+ @Test
+ public void testSuggestPhoneTime_systemClockThreshold() {
+ int systemClockUpdateThresholdMillis = 1000;
+ 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();
+
+ // Increment the the device clocks to simulate the passage of time.
+ mScript.simulateTimePassing(clockIncrement);
+
+ long expectedSystemClockMillis1 =
+ TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
+
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+ }
+
+ // Now send another time signal, but one that is too similar to the last one and should be
+ // stored, but not used to set the system clock.
+ {
+ int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
+ PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
+ phoneId, mScript.peekSystemClockMillis() + underThresholdMillis);
+ mScript.simulateTimePassing(clockIncrement)
+ .simulatePhoneTimeSuggestion(timeSuggestion2)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+ }
+
+ // Now send another time signal, but one that is on the threshold and so should be used.
+ {
+ PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion(
+ phoneId,
+ mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
+ mScript.simulateTimePassing(clockIncrement);
+
+ long expectedSystemClockMillis3 =
+ TimeDetectorStrategy.getTimeAt(timeSuggestion3.getUtcTime(),
+ mScript.peekElapsedRealtimeMillis());
+
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
+ }
+ }
+
+ @Test
+ public void testSuggestPhoneTime_multiplePhoneIdsAndBucketing() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ // There are 2 phones in this test. Phone 2 has a different idea of the current time.
+ // phone1Id < phone2Id (which is important because the strategy uses the lowest ID when
+ // multiple phone suggestions are available.
+ 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;
+
+ // Make a suggestion with phone2Id.
+ {
+ PhoneTimeSuggestion phone2TimeSuggestion =
+ mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
+ mScript.simulateTimePassing(clockIncrement);
+
+ long expectedSystemClockMillis = phone2TimeMillis + clockIncrement;
+
+ mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phone1Id, null)
+ .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+ }
+
+ mScript.simulateTimePassing(clockIncrement);
+
+ // Now make a different suggestion with phone1Id.
+ {
+ PhoneTimeSuggestion phone1TimeSuggestion =
+ mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis);
+ mScript.simulateTimePassing(clockIncrement);
+
+ long expectedSystemClockMillis = phone1TimeMillis + clockIncrement;
+
+ mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
+
+ }
+
+ mScript.simulateTimePassing(clockIncrement);
+
+ // 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.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+ }
+
+ // Let enough time pass that phone1Id's suggestion should now be too old.
+ mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_BUCKET_SIZE_MILLIS);
+
+ // Make another suggestion with phone2Id. It should be used because the phoneId1
+ // is in an older "bucket".
+ {
+ PhoneTimeSuggestion phone2TimeSuggestion =
+ mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
+ mScript.simulateTimePassing(clockIncrement);
+
+ long expectedSystemClockMillis = phone2TimeMillis + clockIncrement;
+
+ mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
+ }
+ }
+
+ @Test
+ public void testSuggestPhoneTime_autoTimeDisabled() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(false);
+
+ int phoneId = ARBITRARY_PHONE_ID;
+ PhoneTimeSuggestion timeSuggestion =
+ mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
+ mScript.simulateTimePassing(1000)
+ .simulatePhoneTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
+ }
+
+ @Test
+ public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() {
+ final int systemClockUpdateThreshold = 2000;
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeThresholds(systemClockUpdateThreshold)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+ int phoneId = ARBITRARY_PHONE_ID;
+
+ PhoneTimeSuggestion timeSuggestion1 =
+ mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+ 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.simulatePhoneTimeSuggestion(timeSuggestion1)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+ // The UTC time increment should be larger than the system clock update threshold so we
+ // know it shouldn't be ignored for other reasons.
+ long validUtcTimeMillis = utcTime1.getValue() + (2 * systemClockUpdateThreshold);
+
+ // Now supply a new signal that has an obviously bogus reference time : older than the last
+ // one.
+ long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
+ TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
+ referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
+ PhoneTimeSuggestion timeSuggestion2 =
+ createPhoneTimeSuggestion(phoneId, utcTime2);
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+ // Now supply a new signal that has an obviously bogus reference time : substantially in the
+ // future.
+ long referenceTimeInFutureMillis =
+ utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
+ TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
+ referenceTimeInFutureMillis, validUtcTimeMillis);
+ PhoneTimeSuggestion timeSuggestion3 =
+ createPhoneTimeSuggestion(phoneId, utcTime3);
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+ // Just to prove validUtcTimeMillis is valid.
+ long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
+ TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
+ validReferenceTimeMillis, validUtcTimeMillis);
+ long expectedSystemClockMillis4 =
+ TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
+ PhoneTimeSuggestion timeSuggestion4 =
+ createPhoneTimeSuggestion(phoneId, utcTime4);
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
+ }
+
+ @Test
+ public void testSuggestPhoneTime_timeDetectionToggled() {
+ final int clockIncrementMillis = 100;
+ final int systemClockUpdateThreshold = 2000;
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeThresholds(systemClockUpdateThreshold)
+ .pokeAutoTimeDetectionEnabled(false);
+
+ int phoneId = ARBITRARY_PHONE_ID;
+ long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+ PhoneTimeSuggestion timeSuggestion1 =
+ mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+ TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
+
+ // Simulate time passing.
+ mScript.simulateTimePassing(clockIncrementMillis);
+
+ // Simulate the time signal being received. It should not be used because auto time
+ // detection is off but it should be recorded.
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+ // Simulate more time passing.
+ mScript.simulateTimePassing(clockIncrementMillis);
+
+ long expectedSystemClockMillis1 =
+ TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
+
+ // Turn on auto time detection.
+ mScript.simulateAutoTimeDetectionToggle()
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+ // Turn off auto time detection.
+ mScript.simulateAutoTimeDetectionToggle()
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
+
+ // Receive another valid time signal.
+ // It should be on the threshold and accounting for the clock increments.
+ PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
+ phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
+
+ // Simulate more time passing.
+ mScript.simulateTimePassing(clockIncrementMillis);
+
+ long expectedSystemClockMillis2 = TimeDetectorStrategy.getTimeAt(
+ timeSuggestion2.getUtcTime(), mScript.peekElapsedRealtimeMillis());
+
+ // The new time, though valid, should not be set in the system clock because auto time is
+ // disabled.
+ mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+
+ // Turn on auto time detection.
+ mScript.simulateAutoTimeDetectionToggle()
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
+ }
+
+ @Test
+ public void testSuggestPhoneTime_maxSuggestionAge() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ int phoneId = ARBITRARY_PHONE_ID;
+ long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+ PhoneTimeSuggestion phoneSuggestion =
+ mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
+ int clockIncrementMillis = 1000;
+
+ mScript.simulateTimePassing(clockIncrementMillis)
+ .simulatePhoneTimeSuggestion(phoneSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ testTimeMillis + clockIncrementMillis, true /* 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);
+
+ // Look inside and check what the strategy considers the current best phone suggestion. It
+ // should still be the, it's just no longer used.
+ assertNull(mScript.peekBestPhoneSuggestion());
+ mScript.assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
+ }
+
+ @Test
+ public void testSuggestManualTime_autoTimeDisabled() {
+ 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;
+
+ mScript.simulateTimePassing(clockIncrement)
+ .simulateManualTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+ }
+
+ @Test
+ public void testSuggestManualTime_retainsAutoSignal() {
+ // Configure the start state.
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ int phoneId = ARBITRARY_PHONE_ID;
+
+ // Simulate a phone suggestion.
+ 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.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+
+ // Simulate the passage of time.
+ mScript.simulateTimePassing(clockIncrement);
+ expectedAutoClockMillis += clockIncrement;
+
+ // Switch to manual.
+ mScript.simulateAutoTimeDetectionToggle()
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+
+ // Simulate the passage of time.
+ mScript.simulateTimePassing(clockIncrement);
+ expectedAutoClockMillis += clockIncrement;
+
+ // Simulate a manual suggestion 1 day different from the auto suggestion.
+ long manualTimeMillis = testTimeMillis + ONE_DAY_MILLIS;
+ long expectedManualClockMillis = manualTimeMillis;
+ ManualTimeSuggestion manualTimeSuggestion =
+ mScript.generateManualTimeSuggestion(manualTimeMillis);
+ mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ expectedManualClockMillis, false /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+
+ // Simulate the passage of time.
+ mScript.simulateTimePassing(clockIncrement);
+ expectedAutoClockMillis += clockIncrement;
+
+ // Switch back to auto.
+ mScript.simulateAutoTimeDetectionToggle();
+
+ mScript.verifySystemClockWasSetAndResetCallTracking(
+ expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+ .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+
+ // Switch back to manual - nothing should happen to the clock.
+ mScript.simulateAutoTimeDetectionToggle()
+ .verifySystemClockWasNotSetAndResetCallTracking()
+ .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
+ }
+
+ /**
+ * Manual suggestions should be ignored if auto time is enabled.
+ */
+ @Test
+ public void testSuggestManualTime_autoTimeEnabled() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ ManualTimeSuggestion timeSuggestion =
+ mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
+ final int clockIncrement = 1000;
+
+ mScript.simulateTimePassing(clockIncrement)
+ .simulateManualTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+ }
+
+ /**
+ * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
+ * like the real thing should, it also asserts preconditions.
+ */
+ private static class FakeCallback implements TimeDetectorStrategy.Callback {
+ private boolean mAutoTimeDetectionEnabled;
+ private boolean mWakeLockAcquired;
+ private long mElapsedRealtimeMillis;
+ private long mSystemClockMillis;
+ private int mSystemClockUpdateThresholdMillis = 2000;
+
+ // Tracking operations.
+ private boolean mSystemClockWasSet;
+ private Intent mBroadcastSent;
+
+ @Override
+ public int systemClockUpdateThresholdMillis() {
+ return mSystemClockUpdateThresholdMillis;
+ }
+
+ @Override
+ public boolean isAutoTimeDetectionEnabled() {
+ return mAutoTimeDetectionEnabled;
+ }
+
+ @Override
+ public void acquireWakeLock() {
+ if (mWakeLockAcquired) {
+ fail("Wake lock already acquired");
+ }
+ mWakeLockAcquired = true;
+ }
+
+ @Override
+ public long elapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
+
+ @Override
+ public long systemClockMillis() {
+ assertWakeLockAcquired();
+ return mSystemClockMillis;
+ }
+
+ @Override
+ public void setSystemClock(long newTimeMillis) {
+ assertWakeLockAcquired();
+ mSystemClockWasSet = true;
+ mSystemClockMillis = newTimeMillis;
+ }
+
+ @Override
+ public void releaseWakeLock() {
+ assertWakeLockAcquired();
+ 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) {
+ mSystemClockUpdateThresholdMillis = thresholdMillis;
+ }
+
+ void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) {
+ mElapsedRealtimeMillis = elapsedRealtimeMillis;
+ }
+
+ void pokeSystemClockMillis(long systemClockMillis) {
+ mSystemClockMillis = systemClockMillis;
+ }
+
+ void pokeAutoTimeDetectionEnabled(boolean enabled) {
+ mAutoTimeDetectionEnabled = enabled;
+ }
+
+ long peekElapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
+
+ long peekSystemClockMillis() {
+ return mSystemClockMillis;
+ }
+
+ void simulateTimePassing(long incrementMillis) {
+ mElapsedRealtimeMillis += incrementMillis;
+ mSystemClockMillis += incrementMillis;
+ }
+
+ void simulateAutoTimeZoneDetectionToggle() {
+ mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled;
+ }
+
+ void verifySystemClockNotSet() {
+ assertFalse(mSystemClockWasSet);
+ }
+
+ void verifySystemClockWasSet(long expectedSystemClockMillis) {
+ assertTrue(mSystemClockWasSet);
+ assertEquals(expectedSystemClockMillis, mSystemClockMillis);
+ }
+
+ void verifyIntentWasBroadcast() {
+ assertTrue(mBroadcastSent != null);
+ }
+
+ void verifyIntentWasNotBroadcast() {
+ assertNull(mBroadcastSent);
+ }
+
+ void resetCallTracking() {
+ mSystemClockWasSet = false;
+ mBroadcastSent = null;
+ }
+
+ private void assertWakeLockAcquired() {
+ assertTrue("The operation must be performed only after acquiring the wakelock",
+ mWakeLockAcquired);
+ }
+ }
+
+ /**
+ * A fluent helper class for tests.
+ */
+ private class Script {
+
+ private final FakeCallback mFakeCallback;
+ private final TimeDetectorStrategyImpl mTimeDetectorStrategy;
+
+ Script() {
+ mFakeCallback = new FakeCallback();
+ mTimeDetectorStrategy = new TimeDetectorStrategyImpl();
+ mTimeDetectorStrategy.initialize(mFakeCallback);
+
+ }
+
+ Script pokeAutoTimeDetectionEnabled(boolean enabled) {
+ mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
+ return this;
+ }
+
+ Script pokeFakeClocks(TimestampedValue<Long> timeInfo) {
+ mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
+ mFakeCallback.pokeSystemClockMillis(timeInfo.getValue());
+ return this;
+ }
+
+ Script pokeThresholds(int systemClockUpdateThreshold) {
+ mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
+ return this;
+ }
+
+ long peekElapsedRealtimeMillis() {
+ return mFakeCallback.peekElapsedRealtimeMillis();
+ }
+
+ long peekSystemClockMillis() {
+ return mFakeCallback.peekSystemClockMillis();
+ }
+
+ Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
+ mTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
+ return this;
+ }
+
+ Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) {
+ mTimeDetectorStrategy.suggestManualTime(timeSuggestion);
+ return this;
+ }
+
+ Script simulateAutoTimeDetectionToggle() {
+ mFakeCallback.simulateAutoTimeZoneDetectionToggle();
+ mTimeDetectorStrategy.handleAutoTimeDetectionChanged();
+ return this;
+ }
+
+ Script simulateTimePassing(long clockIncrementMillis) {
+ mFakeCallback.simulateTimePassing(clockIncrementMillis);
+ return this;
+ }
+
+ Script verifySystemClockWasNotSetAndResetCallTracking() {
+ mFakeCallback.verifySystemClockNotSet();
+ mFakeCallback.verifyIntentWasNotBroadcast();
+ mFakeCallback.resetCallTracking();
+ return this;
+ }
+
+ Script verifySystemClockWasSetAndResetCallTracking(
+ long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
+ mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
+ if (expectNetworkBroadcast) {
+ mFakeCallback.verifyIntentWasBroadcast();
+ }
+ mFakeCallback.resetCallTracking();
+ return this;
+ }
+
+ /**
+ * White box test info: Asserts the latest suggestion for the phone ID is as expected.
+ */
+ Script assertLatestPhoneSuggestion(int phoneId, PhoneTimeSuggestion expected) {
+ assertEquals(expected, mTimeDetectorStrategy.getLatestPhoneSuggestion(phoneId));
+ return this;
+ }
+
+ /**
+ * White box test info: Returns the phone suggestion that would be used, if any, given the
+ * current elapsed real time clock.
+ */
+ PhoneTimeSuggestion peekBestPhoneSuggestion() {
+ return mTimeDetectorStrategy.findBestPhoneSuggestionForTests();
+ }
+
+ /**
+ * Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
+ * reference time.
+ */
+ ManualTimeSuggestion generateManualTimeSuggestion(long timeMillis) {
+ TimestampedValue<Long> utcTime =
+ new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis);
+ return new ManualTimeSuggestion(utcTime);
+ }
+
+ /**
+ * Generates a PhoneTimeSuggestion using the current elapsed realtime clock for the
+ * reference time.
+ */
+ PhoneTimeSuggestion generatePhoneTimeSuggestion(int phoneId, Long timeMillis) {
+ TimestampedValue<Long> time = null;
+ if (timeMillis != null) {
+ time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis);
+ }
+ return createPhoneTimeSuggestion(phoneId, time);
+ }
+ }
+
+ private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,
+ TimestampedValue<Long> utcTime) {
+ return new PhoneTimeSuggestion.Builder(phoneId)
+ .setUtcTime(utcTime)
+ .build();
+ }
+
+ private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
+ int second) {
+ Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
+ cal.clear();
+ cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
+ return cal.getTimeInMillis();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
index 270436d..2429cfc 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
@@ -50,7 +50,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
-import java.util.Objects;
/**
* White-box unit tests for {@link TimeZoneDetectorStrategy}.
@@ -445,8 +444,7 @@
static class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback {
private boolean mAutoTimeZoneDetectionEnabled;
- private TestState<TimeZoneChange> mTimeZoneChanges = new TestState<>();
- private String mTimeZoneId;
+ private TestState<String> mTimeZoneId = new TestState<>();
@Override
public boolean isAutoTimeZoneDetectionEnabled() {
@@ -455,18 +453,17 @@
@Override
public boolean isDeviceTimeZoneInitialized() {
- return mTimeZoneId != null;
+ return mTimeZoneId.getLatest() != null;
}
@Override
public String getDeviceTimeZone() {
- return mTimeZoneId;
+ return mTimeZoneId.getLatest();
}
@Override
- public void setDeviceTimeZone(String zoneId, boolean withNetworkBroadcast) {
- mTimeZoneId = zoneId;
- mTimeZoneChanges.set(new TimeZoneChange(zoneId, withNetworkBroadcast));
+ public void setDeviceTimeZone(String zoneId) {
+ mTimeZoneId.set(zoneId);
}
void initializeAutoTimeZoneDetection(boolean enabled) {
@@ -474,7 +471,7 @@
}
void initializeTimeZone(String zoneId) {
- mTimeZoneId = zoneId;
+ mTimeZoneId.init(zoneId);
}
void setAutoTimeZoneDetectionEnabled(boolean enabled) {
@@ -482,46 +479,17 @@
}
void assertTimeZoneNotSet() {
- mTimeZoneChanges.assertHasNotBeenSet();
+ mTimeZoneId.assertHasNotBeenSet();
}
- void assertTimeZoneSet(String timeZoneId, boolean withNetworkBroadcast) {
- mTimeZoneChanges.assertHasBeenSet();
- mTimeZoneChanges.assertChangeCount(1);
- TimeZoneChange expectedChange = new TimeZoneChange(timeZoneId, withNetworkBroadcast);
- mTimeZoneChanges.assertLatestEquals(expectedChange);
+ void assertTimeZoneSet(String timeZoneId) {
+ mTimeZoneId.assertHasBeenSet();
+ mTimeZoneId.assertChangeCount(1);
+ mTimeZoneId.assertLatestEquals(timeZoneId);
}
void commitAllChanges() {
- mTimeZoneChanges.commitLatest();
- }
- }
-
- private static class TimeZoneChange {
- private final String mTimeZoneId;
- private final boolean mWithNetworkBroadcast;
-
- private TimeZoneChange(String timeZoneId, boolean withNetworkBroadcast) {
- mTimeZoneId = timeZoneId;
- mWithNetworkBroadcast = withNetworkBroadcast;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- TimeZoneChange that = (TimeZoneChange) o;
- return mWithNetworkBroadcast == that.mWithNetworkBroadcast
- && mTimeZoneId.equals(that.mTimeZoneId);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mTimeZoneId, mWithNetworkBroadcast);
+ mTimeZoneId.commitLatest();
}
}
@@ -614,21 +582,13 @@
}
Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion suggestion) {
- // Phone suggestions should cause a TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE
- // broadcast.
- boolean withNetworkBroadcast = true;
- mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(
- suggestion.getZoneId(), withNetworkBroadcast);
+ mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId());
mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
return this;
}
Script verifyTimeZoneSetAndReset(ManualTimeZoneSuggestion suggestion) {
- // Manual suggestions should not cause a TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE
- // broadcast.
- boolean withNetworkBroadcast = false;
- mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(
- suggestion.getZoneId(), withNetworkBroadcast);
+ mFakeTimeZoneDetectorStrategyCallback.assertTimeZoneSet(suggestion.getZoneId());
mFakeTimeZoneDetectorStrategyCallback.commitAllChanges();
return this;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 0b4760d..a328568 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.spy;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -271,6 +272,18 @@
}
@Test
+ public void testRankingScoreOverrides() {
+ NotificationComparator comp = new NotificationComparator(mContext);
+ NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
+
+ when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
+ assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
+ assertTrue(comp.compare(mRecordCheater, recordMinCallNonInterruptive) > 0);
+ assertTrue(comp.compare(mRecordColorizedCall, recordMinCallNonInterruptive) < 0);
+ }
+
+ @Test
public void testMessaging() {
NotificationComparator comp = new NotificationComparator(mContext);
assertTrue(comp.isImportantMessaging(mRecordInlineReply));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 7c22350..fab6b7f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -52,11 +52,13 @@
import android.media.AudioAttributes;
import android.metrics.LogMaker;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.StatusBarNotification;
+import android.widget.RemoteViews;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -114,7 +116,9 @@
when(mMockContext.getResources()).thenReturn(getContext().getResources());
when(mMockContext.getPackageManager()).thenReturn(mPm);
- when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.targetSdkVersion = Build.VERSION_CODES.O;
+ when(mMockContext.getApplicationInfo()).thenReturn(appInfo);
}
private StatusBarNotification getNotification(String pkg, boolean noisy, boolean defaultSound,
@@ -168,6 +172,28 @@
return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
}
+ private StatusBarNotification getStyledNotification(boolean customContent, boolean customBig,
+ boolean customHeadsUp, Notification.Style style) {
+ final Builder builder = new Builder(mMockContext)
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ if (style != null) {
+ builder.setStyle(style);
+ }
+ if (customContent) {
+ builder.setCustomContentView(mock(RemoteViews.class));
+ }
+ if (customBig) {
+ builder.setCustomBigContentView(mock(RemoteViews.class));
+ }
+ if (customHeadsUp) {
+ builder.setCustomHeadsUpContentView(mock(RemoteViews.class));
+ }
+
+ Notification n = builder.build();
+ return new StatusBarNotification(pkg, pkg, id1, tag1, uid, uid, n, mUser, null, uid);
+ }
+
//
// Tests
//
@@ -999,4 +1025,74 @@
assertEquals(IMPORTANCE_LOW, record.getImportance());
}
+
+ @Test
+ public void testHasUndecoratedRemoteViews_NoRemoteViews() {
+ StatusBarNotification sbn = getStyledNotification(false, false, false, null);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_NoRemoteViewsWithStyle() {
+ StatusBarNotification sbn = getStyledNotification(false, false, false,
+ new Notification.BigPictureStyle());
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_UndecoratedContent() {
+ StatusBarNotification sbn = getStyledNotification(true, false, false, null);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+ }
+
+
+ @Test
+ public void testHasUndecoratedRemoteViews_UndecoratedBig() {
+ StatusBarNotification sbn = getStyledNotification(false, true, false, null);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+ }
+
+
+ @Test
+ public void testHasUndecoratedRemoteViews_UndecoratedHeadsup() {
+ StatusBarNotification sbn = getStyledNotification(false, false, true, null);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_DecoratedRemoteViews() {
+ StatusBarNotification sbn = getStyledNotification(true, true, true,
+ new Notification.DecoratedCustomViewStyle());
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_DecoratedMediaRemoteViews() {
+ StatusBarNotification sbn = getStyledNotification(true, true, true,
+ new Notification.DecoratedMediaCustomViewStyle());
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertFalse("false positive detection", record.hasUndecoratedRemoteView());
+ }
+
+ @Test
+ public void testHasUndecoratedRemoteViews_UndecoratedWrongStyle() {
+ StatusBarNotification sbn = getStyledNotification(true, true, true,
+ new Notification.BigPictureStyle());
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertTrue("false negative detection", record.hasUndecoratedRemoteView());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index de2bba2..9c17de9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -47,7 +47,7 @@
import org.junit.runner.RunWith;
/**
- * Tests for the {@link ActivityDisplay} class.
+ * Tests for the {@link DisplayContent} class.
*
* Build/Install/Run:
* atest WmTests:ActivityDisplayTests
@@ -55,12 +55,13 @@
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
+// TODO(b/144248496): Merge to DisplayContentTests
public class ActivityDisplayTests extends ActivityTestsBase {
@Test
public void testLastFocusedStackIsUpdatedWhenMovingStack() {
// Create a stack at bottom.
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final ActivityStack stack =
new StackBuilder(mRootActivityContainer).setOnTop(!ON_TOP).build();
final ActivityStack prevFocusedStack = display.getFocusedStack();
@@ -103,13 +104,13 @@
}
/**
- * Test {@link ActivityDisplay#mPreferredTopFocusableStack} will be cleared when the stack is
+ * Test {@link DisplayContent#mPreferredTopFocusableStack} will be cleared when the stack is
* removed or moved to back, and the focused stack will be according to z-order.
*/
@Test
public void testStackShouldNotBeFocusedAfterMovingToBackOrRemoving() {
// Create a display which only contains 2 stacks.
- final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+ final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
final ActivityStack stack1 = createFullscreenStackWithSimpleActivityAt(display);
final ActivityStack stack2 = createFullscreenStackWithSimpleActivityAt(display);
@@ -128,13 +129,13 @@
}
/**
- * Verifies {@link ActivityDisplay#remove} should not resume home stack on the removing display.
+ * Verifies {@link DisplayContent#remove} should not resume home stack on the removing display.
*/
@Test
public void testNotResumeHomeStackOnRemovingDisplay() {
// Create a display which supports system decoration and allows reparenting stacks to
// another display when the display is removed.
- final ActivityDisplay display = new TestActivityDisplay.Builder(
+ final DisplayContent display = new TestDisplayContent.Builder(
mService, 1000, 1500).setSystemDecorations(true).build();
doReturn(false).when(display).shouldDestroyContentOnRemove();
@@ -144,7 +145,7 @@
// Put a finishing standard activity which will be reparented.
final ActivityStack stack = createFullscreenStackWithSimpleActivityAt(display);
- stack.topRunningActivityLocked().makeFinishingLocked();
+ stack.topRunningActivity().makeFinishingLocked();
clearInvocations(homeStack);
display.remove();
@@ -154,7 +155,7 @@
verify(homeStack, never()).resumeTopActivityUncheckedLocked(any(), any());
}
- private ActivityStack createFullscreenStackWithSimpleActivityAt(ActivityDisplay display) {
+ private ActivityStack createFullscreenStackWithSimpleActivityAt(DisplayContent display) {
final ActivityStack fullscreenStack = display.createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP);
final Task fullscreenTask = new TaskBuilder(mService.mStackSupervisor)
@@ -168,7 +169,7 @@
*/
@Test
public void testTopRunningActivity() {
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final KeyguardController keyguard = mSupervisor.getKeyguardController();
final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
final ActivityRecord activity = stack.getTopNonFinishingActivity();
@@ -208,7 +209,7 @@
assertTopRunningActivity(showWhenLockedActivity, display);
}
- private static void assertTopRunningActivity(ActivityRecord top, ActivityDisplay display) {
+ private static void assertTopRunningActivity(ActivityRecord top, DisplayContent display) {
assertEquals(top, display.topRunningActivity());
assertEquals(top, display.topRunningActivity(true /* considerKeyguardState */));
}
@@ -218,7 +219,7 @@
*/
@Test
public void testAlwaysOnTopStackLocation() {
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final ActivityStack alwaysOnTopStack = display.createStack(WINDOWING_MODE_FREEFORM,
ACTIVITY_TYPE_STANDARD, true /* onTop */);
final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true)
@@ -283,7 +284,7 @@
}
private void removeStackTests(Runnable runnable) {
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final ActivityStack stack1 = display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, ON_TOP);
final ActivityStack stack2 = display.createStack(WINDOWING_MODE_FULLSCREEN,
@@ -301,24 +302,18 @@
doAnswer(invocation -> {
display.positionStackAtTop(stack3, false);
return true;
- }).when(mSupervisor).removeTaskByIdLocked(eq(task4.mTaskId), anyBoolean(), anyBoolean(),
- any());
+ }).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
// Removing stacks from the display while removing stacks.
doAnswer(invocation -> {
display.removeStack(stack2);
return true;
- }).when(mSupervisor).removeTaskByIdLocked(eq(task2.mTaskId), anyBoolean(), anyBoolean(),
- any());
+ }).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
runnable.run();
- verify(mSupervisor).removeTaskByIdLocked(eq(task4.mTaskId), anyBoolean(), anyBoolean(),
- any());
- verify(mSupervisor).removeTaskByIdLocked(eq(task3.mTaskId), anyBoolean(), anyBoolean(),
- any());
- verify(mSupervisor).removeTaskByIdLocked(eq(task2.mTaskId), anyBoolean(), anyBoolean(),
- any());
- verify(mSupervisor).removeTaskByIdLocked(eq(task1.mTaskId), anyBoolean(), anyBoolean(),
- any());
+ verify(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
+ verify(mSupervisor).removeTask(eq(task3), anyBoolean(), anyBoolean(), any());
+ verify(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
+ verify(mSupervisor).removeTask(eq(task1), anyBoolean(), anyBoolean(), any());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 12074dc3..3eee031 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -44,6 +44,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import java.util.Arrays;
@@ -57,6 +58,7 @@
*/
@SmallTest
@Presubmit
+@RunWith(WindowTestRunner.class)
public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase {
private ActivityMetricsLogger mActivityMetricsLogger;
private ActivityMetricsLogger.LaunchingState mLaunchingState;
@@ -116,11 +118,18 @@
return argThat(new ActivityRecordMatcher(record));
}
- static <T> T verifyAsync(T mock) {
+ private <T> T verifyAsync(T mock) {
+ // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any
+ // messages that are waiting for the lock.
+ waitHandlerIdle(mService.mH);
// AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
}
+ private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
+ verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(activity), anyLong());
+ }
+
private void onIntentStarted(Intent intent) {
notifyActivityLaunching(intent);
@@ -159,7 +168,7 @@
notifyTransitionStarting(mTopActivity);
notifyWindowsDrawn(mTopActivity);
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
+ verifyOnActivityLaunchFinished(mTopActivity);
verifyNoMoreInteractions(mLaunchObserver);
}
@@ -210,7 +219,7 @@
notifyWindowsDrawn(mTopActivity);
verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong());
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
+ verifyOnActivityLaunchFinished(mTopActivity);
verifyNoMoreInteractions(mLaunchObserver);
}
@@ -267,7 +276,7 @@
notifyWindowsDrawn(mTopActivity);
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
+ verifyOnActivityLaunchFinished(mTopActivity);
verifyNoMoreInteractions(mLaunchObserver);
}
@@ -322,16 +331,40 @@
assertWithMessage("Different callers should get 2 indepedent launching states")
.that(previousState).isNotEqualTo(mLaunchingState);
-
- notifyTransitionStarting(otherActivity);
- notifyWindowsDrawn(otherActivity);
-
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(otherActivity), anyLong());
+ transitToDrawnAndVerifyOnLaunchFinished(otherActivity);
// The first transition should still be valid.
- notifyTransitionStarting(mTopActivity);
- notifyWindowsDrawn(mTopActivity);
+ transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
+ }
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
+ @Test
+ public void testConsecutiveLaunchOnDifferentDisplay() {
+ onActivityLaunched(mTopActivity);
+
+ final ActivityStack stack = new StackBuilder(mRootActivityContainer)
+ .setDisplay(addNewDisplayContentAt(DisplayContent.POSITION_BOTTOM))
+ .setCreateActivity(false)
+ .build();
+ final ActivityRecord activityOnNewDisplay = new ActivityBuilder(mService)
+ .setStack(stack)
+ .setCreateTask(true)
+ .setProcessName("new")
+ .build();
+
+ // Before TopActivity is drawn, it launches another activity on a different display.
+ mActivityMetricsLogger.notifyActivityLaunching(activityOnNewDisplay.intent,
+ mTopActivity /* caller */);
+ notifyActivityLaunched(START_SUCCESS, activityOnNewDisplay);
+
+ // There should be 2 events instead of coalescing as one event.
+ transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
+ transitToDrawnAndVerifyOnLaunchFinished(activityOnNewDisplay);
+ }
+
+ private void transitToDrawnAndVerifyOnLaunchFinished(ActivityRecord activity) {
+ notifyTransitionStarting(activity);
+ notifyWindowsDrawn(activity);
+
+ verifyOnActivityLaunchFinished(activity);
}
}
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 dbcfa94..65704c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -19,6 +19,7 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Process.NOBODY_UID;
@@ -62,6 +63,7 @@
import static org.mockito.Mockito.never;
import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PauseActivityItem;
@@ -70,6 +72,7 @@
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
@@ -107,7 +110,7 @@
@Before
public void setUp() throws Exception {
mStack = new StackBuilder(mRootActivityContainer).build();
- mTask = mStack.getChildAt(0);
+ mTask = mStack.getBottomMostTask();
mActivity = mTask.getTopNonFinishingActivity();
doReturn(false).when(mService).isBooting();
@@ -376,6 +379,64 @@
}
@Test
+ public void ignoreRequestedOrientationInFreeformWindows() {
+ mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ final Rect stableRect = new Rect();
+ mStack.getDisplay().mDisplayContent.getStableRect(stableRect);
+ final boolean isScreenPortrait = stableRect.width() <= stableRect.height();
+ final Rect bounds = new Rect(stableRect);
+ if (isScreenPortrait) {
+ // Landscape bounds
+ final int newHeight = stableRect.width() - 10;
+ bounds.top = stableRect.top + (stableRect.height() - newHeight) / 2;
+ bounds.bottom = bounds.top + newHeight;
+ } else {
+ // Portrait bounds
+ final int newWidth = stableRect.height() - 10;
+ bounds.left = stableRect.left + (stableRect.width() - newWidth) / 2;
+ bounds.right = bounds.left + newWidth;
+ }
+ mTask.setBounds(bounds);
+
+ // Requests orientation that's different from its bounds.
+ mActivity.setRequestedOrientation(
+ isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
+
+ // Asserts it has orientation derived from bounds.
+ assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT,
+ mActivity.getConfiguration().orientation);
+ }
+
+ @Test
+ public void ignoreRequestedOrientationInSplitWindows() {
+ mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ final Rect stableRect = new Rect();
+ mStack.getDisplay().mDisplayContent.getStableRect(stableRect);
+ final boolean isScreenPortrait = stableRect.width() <= stableRect.height();
+ final Rect bounds = new Rect(stableRect);
+ if (isScreenPortrait) {
+ // Landscape bounds
+ final int newHeight = stableRect.width() - 10;
+ bounds.top = stableRect.top + (stableRect.height() - newHeight) / 2;
+ bounds.bottom = bounds.top + newHeight;
+ } else {
+ // Portrait bounds
+ final int newWidth = stableRect.height() - 10;
+ bounds.left = stableRect.left + (stableRect.width() - newWidth) / 2;
+ bounds.right = bounds.left + newWidth;
+ }
+ mTask.setBounds(bounds);
+
+ // Requests orientation that's different from its bounds.
+ mActivity.setRequestedOrientation(
+ isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE);
+
+ // Asserts it has orientation derived from bounds.
+ assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT,
+ mActivity.getConfiguration().orientation);
+ }
+
+ @Test
public void testShouldMakeActive_deferredResume() {
mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
@@ -635,12 +696,12 @@
final ActivityStack stack1 = new StackBuilder(mRootActivityContainer).build();
mStack.moveToFront("test");
// The stack2 is needed here for moving back to simulate the
- // {@link ActivityDisplay#mPreferredTopFocusableStack} is cleared, so
- // {@link ActivityDisplay#getFocusedStack} will rely on the order of focusable-and-visible
+ // {@link DisplayContent#mPreferredTopFocusableStack} is cleared, so
+ // {@link DisplayContent#getFocusedStack} will rely on the order of focusable-and-visible
// stacks. Then when mActivity is finishing, its stack will be invisible (no running
// activities in the stack) that is the key condition to verify.
final ActivityStack stack2 = new StackBuilder(mRootActivityContainer).build();
- stack2.moveToBack("test", stack2.getChildAt(0));
+ stack2.moveToBack("test", stack2.getBottomMostTask());
assertTrue(mStack.isTopStackOnDisplay());
@@ -948,9 +1009,7 @@
public void testDestroyIfPossible_lastActivityAboveEmptyHomeStack() {
// Empty the home stack.
final ActivityStack homeStack = mActivity.getDisplay().getHomeStack();
- for (Task t : homeStack.getAllTasks()) {
- homeStack.removeChild(t, "test");
- }
+ homeStack.forAllTasks((t) -> { homeStack.removeChild(t, "test"); });
mActivity.finishing = true;
doReturn(false).when(mRootActivityContainer).resumeFocusedStacksTopActivities();
spyOn(mStack);
@@ -974,9 +1033,7 @@
public void testCompleteFinishing_lastActivityAboveEmptyHomeStack() {
// Empty the home stack.
final ActivityStack homeStack = mActivity.getDisplay().getHomeStack();
- for (Task t : homeStack.getAllTasks()) {
- homeStack.removeChild(t, "test");
- }
+ homeStack.forAllTasks((t) -> { homeStack.removeChild(t, "test"); });
mActivity.finishing = true;
spyOn(mStack);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
index 0219539..530adb5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackSupervisorTests.java
@@ -112,7 +112,7 @@
@Test
public void testHandleNonResizableTaskOnSecondaryDisplay() {
// Create an unresizable task on secondary display.
- final ActivityDisplay newDisplay = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+ final DisplayContent newDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
final ActivityStack stack = new StackBuilder(mRootActivityContainer)
.setDisplay(newDisplay).build();
final ActivityRecord unresizableActivity = stack.getTopNonFinishingActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 7806d40..8a1a10d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -54,7 +54,6 @@
import static org.junit.Assert.assertTrue;
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 android.content.ComponentName;
@@ -78,7 +77,7 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityStackTests extends ActivityTestsBase {
- private ActivityDisplay mDefaultDisplay;
+ private DisplayContent mDefaultDisplay;
private ActivityStack mStack;
private Task mTask;
@@ -279,19 +278,19 @@
@Test
public void testMoveStackToBackIncludingParent() {
- final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+ final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
final ActivityStack stack1 = createStackForShouldBeVisibleTest(display,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
final ActivityStack stack2 = createStackForShouldBeVisibleTest(display,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
// Do not move display to back because there is still another stack.
- stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.topTask());
+ stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask());
verify(stack2).positionChildAtBottom(any(), eq(false) /* includingParents */);
// Also move display to back because there is only one stack left.
display.removeStack(stack1);
- stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.topTask());
+ stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.getTopMostTask());
verify(stack2).positionChildAtBottom(any(), eq(true) /* includingParents */);
}
@@ -545,7 +544,7 @@
public void testShouldBeVisible_Finishing() {
final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+ ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity();
if (topRunningHomeActivity == null) {
topRunningHomeActivity = new ActivityBuilder(mService)
.setStack(homeStack)
@@ -563,7 +562,7 @@
topRunningHomeActivity.finishing = true;
final ActivityRecord topRunningTranslucentActivity =
- translucentStack.topRunningActivityLocked();
+ translucentStack.topRunningActivity();
topRunningTranslucentActivity.finishing = true;
// Home stack should be visible even there are no running activities.
@@ -815,7 +814,7 @@
@SuppressWarnings("TypeParameterUnusedInFormals")
private ActivityStack createStackForShouldBeVisibleTest(
- ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {
+ DisplayContent display, int windowingMode, int activityType, boolean onTop) {
final ActivityStack stack;
if (activityType == ACTIVITY_TYPE_HOME) {
// Home stack and activity are created in ActivityTestsBase#setupActivityManagerService
@@ -883,7 +882,7 @@
// removed from the task. Since the overlay activity should be removed as well, the task
// should be empty.
assertFalse(mTask.hasChild());
- assertThat(mStack.getAllTasks()).isEmpty();
+ assertFalse(mStack.hasChild());
}
@Test
@@ -905,7 +904,7 @@
mStack.handleAppDiedLocked(secondActivity.app);
assertFalse(mTask.hasChild());
- assertThat(mStack.getAllTasks()).isEmpty();
+ assertFalse(mStack.hasChild());
}
@Test
@@ -919,7 +918,7 @@
mStack.handleAppDiedLocked(activity.app);
assertEquals(1, mTask.getChildCount());
- assertEquals(1, mStack.getAllTasks().size());
+ assertEquals(1, mStack.getChildCount());
}
@Test
@@ -933,7 +932,7 @@
mStack.handleAppDiedLocked(activity.app);
assertFalse(mTask.hasChild());
- assertThat(mStack.getAllTasks()).isEmpty();
+ assertFalse(mStack.hasChild());
}
@Test
@@ -947,7 +946,7 @@
mStack.handleAppDiedLocked(activity.app);
assertEquals(1, mTask.getChildCount());
- assertEquals(1, mStack.getAllTasks().size());
+ assertEquals(1, mStack.getChildCount());
}
@Test
@@ -961,7 +960,7 @@
mStack.handleAppDiedLocked(activity.app);
assertFalse(mTask.hasChild());
- assertThat(mStack.getAllTasks()).isEmpty();
+ assertFalse(mStack.hasChild());
}
@Test
@@ -982,7 +981,7 @@
final ActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
- ActivityRecord activity = homeStack.topRunningActivityLocked();
+ ActivityRecord activity = homeStack.topRunningActivity();
if (activity == null) {
activity = new ActivityBuilder(mService)
.setStack(homeStack)
@@ -998,7 +997,7 @@
@Test
public void testFinishCurrentActivity() {
// Create 2 activities on a new display.
- final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+ final DisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
final ActivityStack stack1 = createStackForShouldBeVisibleTest(display,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
final ActivityStack stack2 = createStackForShouldBeVisibleTest(display,
@@ -1020,7 +1019,7 @@
}
private ActivityRecord finishTopActivity(ActivityStack stack) {
- final ActivityRecord activity = stack.topRunningActivityLocked();
+ final ActivityRecord activity = stack.topRunningActivity();
assertNotNull(activity);
activity.setState(STOPPED, "finishTopActivity");
activity.makeFinishingLocked();
@@ -1065,6 +1064,7 @@
StackOrderChangedListener listener = new StackOrderChangedListener();
mDefaultDisplay.registerStackOrderChangedListener(listener);
try {
+ mStack.mReparenting = true;
mDefaultDisplay.addStack(mStack, 0);
} finally {
mDefaultDisplay.unregisterStackOrderChangedListener(listener);
@@ -1156,10 +1156,10 @@
private void verifyShouldSleepActivities(boolean focusedStack,
boolean keyguardGoingAway, boolean displaySleeping, boolean expected) {
- final ActivityDisplay display = mock(ActivityDisplay.class);
+ final DisplayContent display = mock(DisplayContent.class);
final KeyguardController keyguardController = mSupervisor.getKeyguardController();
- doReturn(display).when(mRootActivityContainer).getActivityDisplay(anyInt());
+ doReturn(display).when(mStack).getDisplay();
doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
doReturn(displaySleeping).when(display).isSleeping();
doReturn(focusedStack).when(mStack).isFocusedStackOnDisplay();
@@ -1168,7 +1168,7 @@
}
private static class StackOrderChangedListener
- implements ActivityDisplay.OnStackOrderChangedListener {
+ implements DisplayContent.OnStackOrderChangedListener {
public boolean mChanged = false;
@Override
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 d78e3af..4234720 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
@@ -137,8 +138,8 @@
// Mock KeyguardManager
when(mContext.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(mKeyguardManager);
when(mKeyguardManager.createConfirmDeviceCredentialIntent(
- nullable(CharSequence.class), nullable(CharSequence.class), eq(TEST_USER_ID)))
- .thenReturn(CONFIRM_CREDENTIALS_INTENT);
+ nullable(CharSequence.class), nullable(CharSequence.class), eq(TEST_USER_ID),
+ eq(true))).thenReturn(CONFIRM_CREDENTIALS_INTENT);
// Mock PackageManager
when(mService.getPackageManager()).thenReturn(mPackageManager);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index d5fdf98..7e22dfc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -47,13 +47,14 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.ActivityDisplay.POSITION_BOTTOM;
-import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -501,7 +502,7 @@
*/
@Test
public void testTaskModeViolation() {
- final ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
display.removeAllTasks();
assertNoTasks(display);
@@ -516,10 +517,10 @@
assertNoTasks(display);
}
- private void assertNoTasks(ActivityDisplay display) {
+ private void assertNoTasks(DisplayContent display) {
for (int i = display.getStackCount() - 1; i >= 0; --i) {
final ActivityStack stack = display.getStackAt(i);
- assertThat(stack.getAllTasks()).isEmpty();
+ assertFalse(stack.hasChild());
}
}
@@ -760,8 +761,8 @@
false /* mockGetLaunchStack */);
// Create a secondary display at bottom.
- final TestActivityDisplay secondaryDisplay =
- new TestActivityDisplay.Builder(mService, 1000, 1500)
+ final TestDisplayContent secondaryDisplay =
+ new TestDisplayContent.Builder(mService, 1000, 1500)
.setPosition(POSITION_BOTTOM).build();
final ActivityStack stack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -799,8 +800,8 @@
false /* mockGetLaunchStack */);
// Create a secondary display with an activity.
- final TestActivityDisplay secondaryDisplay =
- new TestActivityDisplay.Builder(mService, 1000, 1500).build();
+ final TestDisplayContent secondaryDisplay =
+ new TestDisplayContent.Builder(mService, 1000, 1500).build();
mRootActivityContainer.addChild(secondaryDisplay, POSITION_TOP);
final ActivityRecord singleTaskActivity = createSingleTaskActivityOn(
secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
@@ -852,7 +853,7 @@
false /* mockGetLaunchStack */);
// Create a secondary display at bottom.
- final TestActivityDisplay secondaryDisplay = addNewActivityDisplayAt(POSITION_BOTTOM);
+ final TestDisplayContent secondaryDisplay = addNewDisplayContentAt(POSITION_BOTTOM);
secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
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 dae1052..f1de6e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -19,15 +19,21 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.IBinder;
import android.view.IDisplayWindowListener;
import android.view.WindowContainerTransaction;
@@ -36,6 +42,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
import java.util.ArrayList;
@@ -59,7 +66,7 @@
@Test
public void testActivityFinish() {
final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
- final ActivityRecord activity = stack.getChildAt(0).getTopNonFinishingActivity();
+ final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
assertTrue("Activity must be finished", mService.finishActivity(activity.appToken,
0 /* resultCode */, null /* resultData */,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY));
@@ -75,7 +82,7 @@
removeGlobalMinSizeRestriction();
final ActivityStack stack = new StackBuilder(mRootActivityContainer)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
- final Task task = stack.topTask();
+ final Task task = stack.getTopMostTask();
WindowContainerTransaction t = new WindowContainerTransaction();
Rect newBounds = new Rect(10, 10, 100, 100);
t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100));
@@ -126,7 +133,7 @@
assertEquals(0, removed.size());
added.clear();
// Check adding a display
- ActivityDisplay newDisp1 = new TestActivityDisplay.Builder(mService, 600, 800).build();
+ DisplayContent newDisp1 = new TestDisplayContent.Builder(mService, 600, 800).build();
assertEquals(1, added.size());
assertEquals(0, changed.size());
assertEquals(0, removed.size());
@@ -148,5 +155,43 @@
assertEquals(0, changed.size());
assertEquals(1, removed.size());
}
+
+ /*
+ a test to verify b/144045134 - ignore PIP mode request for destroyed activity.
+ mocks r.getParent() to return null to cause NPE inside enterPipRunnable#run() in
+ ActivityTaskMangerservice#enterPictureInPictureMode(), which rebooted the device.
+ It doesn't fully simulate the issue's reproduce steps, but this should suffice.
+ */
+ @Test
+ public void testEnterPipModeWhenRecordParentChangesToNull() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(ActivityRecord.class)
+ .startMocking();
+
+ ActivityRecord record = mock(ActivityRecord.class);
+ IBinder token = mock(IBinder.class);
+ PictureInPictureParams params = mock(PictureInPictureParams.class);
+ record.pictureInPictureArgs = params;
+
+ //mock operations in private method ensureValidPictureInPictureActivityParamsLocked()
+ when(ActivityRecord.forTokenLocked(token)).thenReturn(record);
+ doReturn(true).when(record).supportsPictureInPicture();
+ doReturn(false).when(params).hasSetAspectRatio();
+
+ //mock other operations
+ doReturn(true).when(record)
+ .checkEnterPictureInPictureState("enterPictureInPictureMode", false);
+ doReturn(false).when(mService).isInPictureInPictureMode(any());
+ doReturn(false).when(mService).isKeyguardLocked();
+
+ //to simulate NPE
+ doReturn(null).when(record).getParent();
+
+ mService.enterPictureInPictureMode(token, params);
+ //if record's null parent is not handled gracefully, test will fail with NPE
+
+ mockSession.finishMocking();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 0021cc5..b72cc94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -77,9 +77,9 @@
mRootActivityContainer = mService.mRootActivityContainer;
}
- /** Creates and adds a {@link TestActivityDisplay} to supervisor at the given position. */
- TestActivityDisplay addNewActivityDisplayAt(int position) {
- return new TestActivityDisplay.Builder(mService, 1000, 1500).setPosition(position).build();
+ /** Creates and adds a {@link TestDisplayContent} to supervisor at the given position. */
+ TestDisplayContent addNewDisplayContentAt(int position) {
+ return new TestDisplayContent.Builder(mService, 1000, 1500).setPosition(position).build();
}
/** Sets the default minimum task size to 1 so that tests can use small task sizes */
@@ -381,7 +381,7 @@
static class StackBuilder {
private final RootActivityContainer mRootActivityContainer;
- private ActivityDisplay mDisplay;
+ private DisplayContent mDisplay;
private int mStackId = -1;
private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
private int mActivityType = ACTIVITY_TYPE_STANDARD;
@@ -408,15 +408,8 @@
return this;
}
- // TODO(display-merge): Remove
- StackBuilder setDisplay(ActivityDisplay display) {
- mDisplay = display;
- return this;
- }
-
StackBuilder setDisplay(DisplayContent display) {
- // TODO(display-merge): Remove cast
- mDisplay = (ActivityDisplay) display;
+ mDisplay = display;
return this;
}
@@ -449,7 +442,7 @@
if (mOnTop) {
// We move the task to front again in order to regain focus after activity
// added to the stack.
- // Or {@link ActivityDisplay#mPreferredTopFocusableStack} could be other
+ // Or {@link DisplayContent#mPreferredTopFocusableStack} could be other
// stacks (e.g. home stack).
stack.moveToFront("createActivityStack");
} else {
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 c6203c5..a39be56 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
@@ -60,6 +60,7 @@
}
@Test
+ @FlakyTest(bugId = 144611135)
public void testDeferring() {
final ActivityRecord activity1 = createActivityRecord(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
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 d415f25..1311889 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
@@ -29,6 +30,7 @@
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import android.view.WindowManager;
import androidx.test.filters.FlakyTest;
@@ -116,4 +118,358 @@
assertTrue(mAppTransitionController.isTransitWithinTask(TRANSIT_ACTIVITY_OPEN, task));
assertFalse(mAppTransitionController.isTransitWithinTask(TRANSIT_TASK_OPEN, task));
}
+
+ @Test
+ public void testGetAnimationTargets_noHierarchicalAnimations() {
+ WindowManagerService.sHierarchicalAnimations = false;
+
+ // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, invisible)
+ // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, visible)
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Don't promote when the flag is disabled.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_visibilityAlreadyUpdated() {
+ // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, visible)
+ // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible)
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = false;
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // No animation, since visibility of the opening and closing apps are already updated
+ // outside of AppTransition framework.
+ WindowManagerService.sHierarchicalAnimations = false;
+ assertEquals(
+ new ArraySet<>(),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+
+ WindowManagerService.sHierarchicalAnimations = true;
+ assertEquals(
+ new ArraySet<>(),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_exitingBeforeTransition() {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity = WindowTestUtils.createTestActivityRecord(stack);
+ activity.setVisible(false);
+ activity.mIsExiting = true;
+
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity);
+
+ // Animate closing apps even if it's not visible when it is exiting before we had a chance
+ // to play the transition animation.
+ WindowManagerService.sHierarchicalAnimations = false;
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity}),
+ AppTransitionController.getAnimationTargets(
+ new ArraySet<>(), closing, false /* visible */));
+
+ WindowManagerService.sHierarchicalAnimations = true;
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack}),
+ AppTransitionController.getAnimationTargets(
+ new ArraySet<>(), closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_windowsAreBeingReplaced() {
+ // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (opening, visible)
+ // +- [AppWindow1] (being-replaced)
+ // +- [TaskStack2] - [Task2] - [ActivityRecord2] (closing, invisible)
+ // +- [AppWindow2] (being-replaced)
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+ TYPE_BASE_APPLICATION);
+ attrs.setTitle("AppWindow1");
+ final WindowTestUtils.TestWindowState appWindow1 = createWindowState(attrs, activity1);
+ appWindow1.mWillReplaceWindow = true;
+ activity1.addWindow(appWindow1);
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = false;
+ attrs.setTitle("AppWindow2");
+ final WindowTestUtils.TestWindowState appWindow2 = createWindowState(attrs, activity2);
+ appWindow2.mWillReplaceWindow = true;
+ activity2.addWindow(appWindow2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Animate opening apps even if it's already visible in case its windows are being replaced.
+ // Don't animate closing apps if it's already invisible even though its windows are being
+ // replaced.
+ WindowManagerService.sHierarchicalAnimations = false;
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+
+ WindowManagerService.sHierarchicalAnimations = true;
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_openingClosingInDifferentTask() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible)
+ // | +- [ActivityRecord2] (invisible)
+ // |
+ // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible)
+ // +- [ActivityRecord4] (invisible)
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = false;
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+ final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+ final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+ activity4.setVisible(false);
+ activity4.mVisibleRequested = false;
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity3);
+
+ // Promote animation targets to TaskStack level. Invisible ActivityRecords don't affect
+ // promotion decision.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_openingClosingInSameTask() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] - [TaskStack] - [Task] -+- [ActivityRecord1] (opening, invisible)
+ // +- [ActivityRecord2] (closing, visible)
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Don't promote an animation target to Task level, since the same task contains both
+ // opening and closing app.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_animateOnlyTranslucentApp() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible)
+ // | +- [ActivityRecord2] (visible)
+ // |
+ // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible)
+ // +- [ActivityRecord4] (visible)
+
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ activity1.setOccludesParent(false);
+
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+ final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+ activity3.setOccludesParent(false);
+ final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity3);
+
+ // Don't promote an animation target to Task level, since opening (closing) app is
+ // translucent and is displayed over other non-animating app.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{activity3}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_animateTranslucentAndOpaqueApps() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] -+- [TaskStack1] - [Task1] -+- [ActivityRecord1] (opening, invisible)
+ // | +- [ActivityRecord2] (opening, invisible)
+ // |
+ // +- [TaskStack2] - [Task2] -+- [ActivityRecord3] (closing, visible)
+ // +- [ActivityRecord4] (closing, visible)
+
+ final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ activity1.setOccludesParent(false);
+
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = true;
+
+ final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+ final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+ final ActivityRecord activity3 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+ activity3.setOccludesParent(false);
+ final ActivityRecord activity4 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ opening.add(activity2);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity3);
+ closing.add(activity4);
+
+ // Promote animation targets to TaskStack level even though opening (closing) app is
+ // translucent as long as all visible siblings animate at the same time.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{stack2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_stackContainsMultipleTasks() {
+ WindowManagerService.sHierarchicalAnimations = true;
+
+ // [DisplayContent] - [TaskStack] -+- [Task1] - [ActivityRecord1] (opening, invisible)
+ // +- [Task2] - [ActivityRecord2] (closing, visible)
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task1 = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity1 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task1);
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+ final Task task2 = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity2 = WindowTestUtils.createActivityRecordInTask(
+ mDisplayContent, task2);
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Promote animation targets up to Task level, not beyond.
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{task1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(
+ new ArraySet<>(new WindowContainer[]{task2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 0382bf8..70e5ee7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -31,7 +31,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.TRANSIT_UNSET;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -136,17 +135,19 @@
@Test
@Presubmit
- public void testGetTopFullscreenWindow() {
- assertNull(mActivity.getTopFullscreenWindow());
+ public void testGetTopFullscreenOpaqueWindow() {
+ assertNull(mActivity.getTopFullscreenOpaqueWindow());
final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, mActivity, "window1");
final WindowState window11 = createWindow(null, TYPE_APPLICATION, mActivity, "window11");
final WindowState window12 = createWindow(null, TYPE_APPLICATION, mActivity, "window12");
- assertEquals(window12, mActivity.getTopFullscreenWindow());
+ assertEquals(window12, mActivity.getTopFullscreenOpaqueWindow());
window12.mAttrs.width = 500;
- assertEquals(window11, mActivity.getTopFullscreenWindow());
+ assertEquals(window11, mActivity.getTopFullscreenOpaqueWindow());
window11.mAttrs.width = 500;
- assertEquals(window1, mActivity.getTopFullscreenWindow());
+ assertEquals(window1, mActivity.getTopFullscreenOpaqueWindow());
+ window1.mAttrs.alpha = 0f;
+ assertNull(mActivity.getTopFullscreenOpaqueWindow());
mActivity.removeImmediately();
}
@@ -182,7 +183,7 @@
@Test
public void testLandscapeSeascapeRotationByPolicy() {
- // This instance has been spied in {@link TestActivityDisplay}.
+ // This instance has been spied in {@link TestDisplayContent}.
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
@@ -303,8 +304,8 @@
"closingWindow");
closingWindow.mAnimatingExit = true;
closingWindow.mRemoveOnExit = true;
- closingWindow.mActivityRecord.commitVisibility(null, false /* visible */, TRANSIT_UNSET,
- true /* performLayout */, false /* isVoiceInteraction */);
+ closingWindow.mActivityRecord.commitVisibility(
+ false /* visible */, true /* performLayout */);
// We pretended that we were running an exit animation, but that should have been cleared up
// by changing visibility of ActivityRecord
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 716e777..ccbafd4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -244,7 +244,6 @@
// Add stack with activity.
final ActivityStack stack = createTaskStackOnDisplay(dc);
assertEquals(dc.getDisplayId(), stack.getDisplayContent().getDisplayId());
- assertEquals(dc, stack.getParent().getParent());
assertEquals(dc, stack.getDisplayContent());
final Task task = createTaskInStack(stack, 0 /* userId */);
@@ -256,7 +255,6 @@
// Move stack to first display.
mDisplayContent.moveStackToDisplay(stack, true /* onTop */);
assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId());
- assertEquals(mDisplayContent, stack.getParent().getParent());
assertEquals(mDisplayContent, stack.getDisplayContent());
assertEquals(mDisplayContent, task.getDisplayContent());
assertEquals(mDisplayContent, activity.getDisplayContent());
@@ -744,7 +742,7 @@
final ActivityStack stack =
new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer)
.setDisplay(dc).build();
- final ActivityRecord activity = stack.topTask().getTopNonFinishingActivity();
+ final ActivityRecord activity = stack.getTopMostTask().getTopNonFinishingActivity();
activity.setRequestedOrientation(newOrientation);
@@ -766,13 +764,12 @@
final ActivityStack stack =
new ActivityTestsBase.StackBuilder(mWm.mAtmService.mRootActivityContainer)
.setDisplay(dc).build();
- final ActivityRecord activity = stack.topTask().getTopNonFinishingActivity();
+ final ActivityRecord activity = stack.getTopMostTask().getTopNonFinishingActivity();
activity.setRequestedOrientation(newOrientation);
- // TODO(display-merge): Remove cast
- verify((ActivityDisplay) dc, never()).updateDisplayOverrideConfigurationLocked(any(),
- eq(activity), anyBoolean(), same(null));
+ verify(dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity),
+ anyBoolean(), same(null));
assertEquals(dc.getDisplayRotation().getUserRotation(), dc.getRotation());
}
@@ -966,7 +963,7 @@
invocation -> {
continued[0] = true;
return true;
- }).when((ActivityDisplay) dc).updateDisplayOverrideConfigurationLocked();
+ }).when(dc).updateDisplayOverrideConfigurationLocked();
final boolean[] called = new boolean[1];
mWm.mDisplayRotationController =
new IDisplayWindowRotationController.Stub() {
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 62ab11c..de73645 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -36,6 +36,7 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -45,6 +46,7 @@
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
@@ -55,6 +57,10 @@
import android.util.Pair;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.InsetsState;
+import android.view.ViewRootImpl;
+import android.view.WindowInsets.Side;
+import android.view.WindowInsets.Type;
import android.view.WindowManager;
import androidx.test.filters.FlakyTest;
@@ -97,8 +103,6 @@
final WindowManager.LayoutParams attrs = mWindow.mAttrs;
attrs.width = MATCH_PARENT;
attrs.height = MATCH_PARENT;
- attrs.flags =
- FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
attrs.format = PixelFormat.TRANSLUCENT;
}
@@ -134,8 +138,186 @@
}
@Test
+ public void layoutWindowLw_fitStatusBars() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.setFitWindowInsetsTypes(Type.statusBars());
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
+ assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ @Test
+ public void layoutWindowLw_fitNavigationBars() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.setFitWindowInsetsTypes(Type.navigationBars());
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getParentFrame(), 0, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ @Test
public void layoutWindowLw_appDrawsBars() {
- mWindow.mAttrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars());
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
+ assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ @Test
+ public void layoutWindowLw_forceAppDrawBars() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.privateFlags = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+ mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars());
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
+ assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ @Test
+ public void layoutWindowLw_onlyDrawBottomBar() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.privateFlags = PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
+ mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars());
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
+ assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ @Test
+ public void layoutWindowLw_fitAllSides() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.setFitWindowInsetsSides(Side.all());
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ @Test
+ public void layoutWindowLw_fitTopOnly() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.setFitWindowInsetsSides(Side.TOP);
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0);
+ assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ @Test
+ public void layoutWindowLw_fitMax() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ final InsetsState state =
+ mDisplayContent.getInsetsStateController().getInsetsForDispatch(mWindow);
+ state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
+ state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
+ mWindow.mAttrs.setFitIgnoreVisibility(true);
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ @Test
+ public void layoutWindowLw_fitNonMax() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ final InsetsState state =
+ mDisplayContent.getInsetsStateController().getInsetsForDispatch(mWindow);
+ state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
+ state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
+ mWindow.mAttrs.setFitIgnoreVisibility(false);
+ addWindow(mWindow);
+
+ mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
+
+ assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
+ assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
+ assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
+ }
+
+ // TODO(b/118118435): remove after migration
+ @Test
+ public void layoutWindowLw_appDrawsBarsLegacy() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -148,9 +330,12 @@
assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
}
+ // TODO(b/118118435): remove after migration
@Test
public void layoutWindowLw_appWontDrawBars() {
- mWindow.mAttrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -163,10 +348,13 @@
assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, NAV_BAR_HEIGHT);
}
+ // TODO(b/118118435): remove after migration
@Test
public void layoutWindowLw_appWontDrawBars_forceStatusAndNav() throws Exception {
- mWindow.mAttrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
+ mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+ mWindow.mAttrs.privateFlags = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -179,11 +367,14 @@
assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
}
+ // TODO(b/118118435): remove after migration (keyguard dialog is not special with the new logic)
@Test
public void layoutWindowLw_keyguardDialog_hideNav() {
mWindow.mAttrs.type = TYPE_KEYGUARD_DIALOG;
- mWindow.mAttrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ mWindow.mAttrs.setFitWindowInsetsTypes(0 /* types */);
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* uiMode */);
@@ -200,6 +391,8 @@
public void layoutWindowLw_withDisplayCutout() {
addDisplayCutout();
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -216,6 +409,8 @@
public void layoutWindowLw_withDisplayCutout_never() {
addDisplayCutout();
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
addWindow(mWindow);
@@ -233,7 +428,11 @@
public void layoutWindowLw_withDisplayCutout_layoutFullscreen() {
addDisplayCutout();
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ mWindow.mAttrs.setFitWindowInsetsTypes(
+ mWindow.mAttrs.getFitWindowInsetsTypes() & ~Type.statusBars());
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -243,14 +442,18 @@
assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0);
- assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
+ assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0);
}
@Test
public void layoutWindowLw_withDisplayCutout_fullscreen() {
addDisplayCutout();
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+ mDisplayContent.getInsetsStateController().getInsetsForDispatch(mWindow)
+ .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -267,7 +470,11 @@
public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() {
addDisplayCutout();
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+ mDisplayContent.getInsetsStateController().getInsetsForDispatch(mWindow)
+ .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
addWindow(mWindow);
@@ -286,6 +493,9 @@
public void layoutWindowLw_withDisplayCutout_landscape() {
addDisplayCutout();
setRotation(ROTATION_90);
+
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -303,6 +513,9 @@
public void layoutWindowLw_withDisplayCutout_seascape() {
addDisplayCutout();
setRotation(ROTATION_270);
+
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -321,7 +534,11 @@
addDisplayCutout();
setRotation(ROTATION_90);
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ mWindow.mAttrs.setFitWindowInsetsTypes(
+ mWindow.mAttrs.getFitWindowInsetsTypes() & ~Type.statusBars());
addWindow(mWindow);
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -339,6 +556,7 @@
addDisplayCutout();
mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN;
+ mWindow.mAttrs.setFitWindowInsetsTypes(Type.systemBars() & ~Type.statusBars());
mWindow.mAttrs.type = TYPE_APPLICATION_OVERLAY;
mWindow.mAttrs.width = DISPLAY_WIDTH;
mWindow.mAttrs.height = DISPLAY_HEIGHT;
@@ -356,7 +574,11 @@
addDisplayCutout();
setRotation(ROTATION_90);
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ mWindow.mAttrs.setFitWindowInsetsTypes(
+ mWindow.mAttrs.getFitWindowInsetsTypes() & ~Type.statusBars());
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
addWindow(mWindow);
@@ -372,6 +594,8 @@
@Test
public void layoutWindowLw_withForwardInset_SoftInputAdjustResize() {
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
addWindow(mWindow);
@@ -392,6 +616,8 @@
@Test
public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() {
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
addWindow(mWindow);
@@ -408,9 +634,12 @@
assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
}
+ // TODO(b/118118435): remove after removing PolicyControl
@FlakyTest(bugId = 129711077)
@Test
public void layoutWindowLw_withImmersive_SoftInputAdjustResize() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
synchronized (mWm.mGlobalLock) {
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
mWindow.mAttrs.flags = 0;
@@ -433,9 +662,12 @@
}
}
+ // TODO(b/118118435): remove after removing PolicyControl
@FlakyTest(bugId = 129711077)
@Test
public void layoutWindowLw_withImmersive_SoftInputAdjustNothing() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
synchronized (mWm.mGlobalLock) {
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
mWindow.mAttrs.flags = 0;
@@ -457,9 +689,12 @@
}
}
+ // TODO(b/118118435): remove after removing PolicyControl
@FlakyTest(bugId = 129711077)
@Test
public void layoutWindowLw_withForceImmersive_fullscreen() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
synchronized (mWm.mGlobalLock) {
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
mWindow.mAttrs.flags = 0;
@@ -481,9 +716,12 @@
}
}
+ // TODO(b/118118435): remove after removing PolicyControl
@FlakyTest(bugId = 129711077)
@Test
public void layoutWindowLw_withForceImmersive_nonFullscreen() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
+
synchronized (mWm.mGlobalLock) {
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
mWindow.mAttrs.flags = 0;
@@ -509,6 +747,9 @@
@Test
public void layoutHint_appWindow() {
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
// Initialize DisplayFrames
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -530,6 +771,9 @@
@Test
public void layoutHint_appWindowInTask() {
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
// Initialize DisplayFrames
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -553,6 +797,9 @@
@Test
public void layoutHint_appWindowInTask_outsideContentFrame() {
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
// Initialize DisplayFrames
mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
@@ -581,7 +828,8 @@
public void forceShowSystemBars_clearsSystemUIFlags() {
mDisplayPolicy.mLastSystemUiFlags |= SYSTEM_UI_FLAG_FULLSCREEN;
mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
- mWindow.mAttrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ mWindow.mAttrs.flags =
+ FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mSystemUiVisibility = SYSTEM_UI_FLAG_FULLSCREEN;
mDisplayPolicy.setForceShowSystemBars(true);
addWindow(mWindow);
@@ -600,7 +848,8 @@
@Test
public void testScreenDecorWindows() {
final WindowState decorWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "decorWindow");
- decorWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+ mWindow.mAttrs.flags = FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR
+ | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
decorWindow.mAttrs.privateFlags |= PRIVATE_FLAG_IS_SCREEN_DECOR;
addWindow(decorWindow);
addWindow(mWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index d4558dc..d0b3350 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -78,6 +78,8 @@
resources.addOverride(R.dimen.navigation_bar_height, NAV_BAR_HEIGHT);
resources.addOverride(R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT);
resources.addOverride(R.dimen.navigation_bar_width, NAV_BAR_HEIGHT);
+ resources.addOverride(R.dimen.navigation_bar_frame_height_landscape, NAV_BAR_HEIGHT);
+ resources.addOverride(R.dimen.navigation_bar_frame_height, NAV_BAR_HEIGHT);
doReturn(resources.getResources()).when(mDisplayPolicy).getCurrentUserResources();
doReturn(true).when(mDisplayPolicy).hasNavigationBar();
doReturn(true).when(mDisplayPolicy).hasStatusBar();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 9e5d9da..84914bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -80,7 +80,7 @@
private TestPersisterQueue mPersisterQueue;
private File mFolder;
- private ActivityDisplay mTestDisplay;
+ private DisplayContent mTestDisplay;
private String mDisplayUniqueId;
private Task mTestTask;
private Task mTaskWithDifferentUser;
@@ -104,9 +104,9 @@
deleteRecursively(mFolder);
mDisplayUniqueId = "test:" + Integer.toString(sNextUniqueId++);
- mTestDisplay = new TestActivityDisplay.Builder(mService, 1000, 1500)
+ mTestDisplay = new TestDisplayContent.Builder(mService, 1000, 1500)
.setUniqueId(mDisplayUniqueId).build();
- when(mRootActivityContainer.getActivityDisplay(eq(mDisplayUniqueId)))
+ when(mRootActivityContainer.getDisplayContent(eq(mDisplayUniqueId)))
.thenReturn(mTestDisplay);
ActivityStack stack = mTestDisplay.createStack(TEST_WINDOWING_MODE,
@@ -180,7 +180,7 @@
public void testReturnsEmptyDisplayIfDisplayIsNotFound() {
mTarget.saveTask(mTestTask);
- when(mRootActivityContainer.getActivityDisplay(eq(mDisplayUniqueId))).thenReturn(null);
+ when(mRootActivityContainer.getDisplayContent(eq(mDisplayUniqueId))).thenReturn(null);
mTarget.getLaunchParams(mTestTask, null, mResult);
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 6ced816..0cc2626 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -106,6 +106,7 @@
@Mock private ActivityStackSupervisor mSupervisor;
@Mock private RootActivityContainer mRootActivityContainer;
+ @Mock private RootWindowContainer mRootWindowContainer;
@Mock private IDevicePolicyManager mDevicePolicyManager;
@Mock private IStatusBarService mStatusBarService;
@Mock private WindowManagerService mWindowManager;
@@ -134,6 +135,7 @@
mSupervisor.mRecentTasks = mRecentTasks;
mSupervisor.mRootActivityContainer = mRootActivityContainer;
+ mRootActivityContainer.mRootWindowContainer = mRootWindowContainer;
mLockTaskController = new LockTaskController(mContext, mSupervisor,
new ImmediatelyExecuteHandler());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 9f97c48..eaf4aa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -41,8 +41,12 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -98,7 +102,7 @@
UserManager.USER_TYPE_PROFILE_MANAGED);
private static final int INVALID_STACK_ID = 999;
- private ActivityDisplay mDisplay;
+ private DisplayContent mDisplay;
private ActivityStack mStack;
private TestTaskPersister mTaskPersister;
private TestRecentTasks mRecentTasks;
@@ -112,7 +116,8 @@
@Before
public void setUp() throws Exception {
mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
- mDisplay = mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY);
+ spyOn(mTaskPersister);
+ mDisplay = mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY);
// Set the recent tasks we should use for testing in this class.
mRecentTasks = new TestRecentTasks(mService, mTaskPersister);
@@ -171,6 +176,46 @@
}
@Test
+ public void testPersister() {
+ // Add some tasks, ensure the persister is woken
+ mRecentTasks.add(mTasks.get(0));
+ mRecentTasks.add(mTasks.get(1));
+ verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+ verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(1)), anyBoolean());
+ reset(mTaskPersister);
+
+ // Update a task, ensure the persister is woken
+ mRecentTasks.add(mTasks.get(0));
+ verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+ reset(mTaskPersister);
+
+ // Remove some tasks, ensure the persister is woken
+ mRecentTasks.remove(mTasks.get(0));
+ mRecentTasks.remove(mTasks.get(1));
+ verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+ verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(1)), anyBoolean());
+ reset(mTaskPersister);
+ }
+
+ @Test
+ public void testPersisterTrimmed() {
+ mRecentTasks.setOnlyTestVisibleRange();
+
+ // Limit the global maximum number of recent tasks to a fixed size
+ mRecentTasks.setGlobalMaxNumTasks(1 /* globalMaxNumTasks */);
+
+ mRecentTasks.add(mTasks.get(0));
+ verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+ reset(mTaskPersister);
+
+ // Add N+1 tasks to ensure the previous task is trimmed
+ mRecentTasks.add(mTasks.get(1));
+ verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
+ verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(1)), anyBoolean());
+ assertTrimmed(mTasks.get(0));
+ }
+
+ @Test
public void testAddTasksNoMultiple_expectNoTrim() {
// Add same non-multiple-task document tasks will remove the task (to re-add it) but not
// trim it
@@ -608,8 +653,8 @@
mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */);
- final ActivityDisplay singleTaskDisplay =
- addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+ final DisplayContent singleTaskDisplay =
+ addNewDisplayContentAt(DisplayContent.POSITION_TOP);
singleTaskDisplay.setDisplayToSingleTaskInstance();
ActivityStack singleTaskStack = singleTaskDisplay.createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -788,7 +833,7 @@
mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
final ActivityStack homeStack = mDisplay.getHomeStack();
- final ActivityDisplay otherDisplay = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+ final DisplayContent otherDisplay = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
final ActivityStack otherDisplayStack = otherDisplay.createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -1282,10 +1327,10 @@
@Override
void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
- int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays,
+ int ignoreWindowingMode, RootActivityContainer root,
int callingUid, boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
mLastAllowed = allowed;
- super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
+ super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, root,
callingUid, allowed, crossUser, profileIds);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 2374847..945928d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -138,8 +138,7 @@
@Test
public void testIncludedApps_expectTargetAndVisible() {
mWm.setRecentsAnimationController(mController);
- // TODO(display-merge): Remove cast
- final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack(
+ final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
final ActivityRecord homeActivity =
new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
@@ -164,8 +163,7 @@
@Test
public void testWallpaperIncluded_expectTarget() throws Exception {
mWm.setRecentsAnimationController(mController);
- // TODO(display-merge): Remove cast
- final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack(
+ final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
final ActivityRecord homeAppWindow =
new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
@@ -194,8 +192,7 @@
@Test
public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
mWm.setRecentsAnimationController(mController);
- // TODO(display-merge): Remove cast
- final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack(
+ final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
final ActivityRecord homeActivity =
new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
@@ -226,8 +223,7 @@
@Test
public void testFinish_expectTargetAndWallpaperAdaptersRemoved() {
mWm.setRecentsAnimationController(mController);
- // TODO(display-merge): Remove cast
- final ActivityStack homeStack = ((ActivityDisplay) mDisplayContent).getOrCreateStack(
+ final ActivityStack homeStack = mDisplayContent.getOrCreateStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
final ActivityRecord homeActivity =
new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 4abab63..07be3e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -88,7 +88,7 @@
@Test
public void testRecentsActivityVisiblility() {
- ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mRootActivityContainer.getDefaultDisplay();
ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_RECENTS, true /* onTop */);
ActivityRecord recentActivity = new ActivityBuilder(mService)
@@ -116,11 +116,11 @@
@Test
public void testPreloadRecentsActivity() {
- final ActivityDisplay defaultDisplay = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent defaultDisplay = mRootActivityContainer.getDefaultDisplay();
final ActivityStack homeStack =
defaultDisplay.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
defaultDisplay.positionStackAtTop(homeStack, false /* includingParents */);
- ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+ ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity();
if (topRunningHomeActivity == null) {
topRunningHomeActivity = new ActivityBuilder(mService)
.setStack(homeStack)
@@ -149,7 +149,7 @@
mService.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
null /* recentsAnimationRunner */);
- ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mRootActivityContainer.getDefaultDisplay();
ActivityStack recentsStack = display.getStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_RECENTS);
assertThat(recentsStack).isNotNull();
@@ -178,7 +178,7 @@
@Test
public void testRestartRecentsActivity() throws Exception {
// Have a recents activity that is not attached to its process (ActivityRecord.app = null).
- ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mRootActivityContainer.getDefaultDisplay();
ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_RECENTS, true /* onTop */);
ActivityRecord recentActivity = new ActivityBuilder(mService).setComponent(
@@ -207,7 +207,7 @@
@Test
public void testSetLaunchTaskBehindOfTargetActivity() {
- ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mRootActivityContainer.getDefaultDisplay();
display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class);
ActivityStack homeStack = display.getHomeStack();
// Assume the home activity support recents.
@@ -253,7 +253,7 @@
@Test
public void testCancelAnimationOnVisibleStackOrderChange() {
- ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class);
ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, true /* onTop */);
@@ -299,7 +299,7 @@
@Test
public void testKeepAnimationOnHiddenStackOrderChange() {
- ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, true /* onTop */);
new ActivityBuilder(mService)
@@ -335,7 +335,7 @@
@Test
public void testMultipleUserHomeActivity_findUserHomeTask() {
- ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
ActivityStack homeStack = display.getStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
ActivityRecord otherUserHomeActivity = new ActivityBuilder(mService)
.setStack(homeStack)
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 59c7c02..2d45a59 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -128,7 +128,7 @@
mRootActivityContainer.moveActivityToPinnedStack(firstActivity, sourceBounds,
0f /*aspectRatio*/, "initialMove");
- final ActivityDisplay display = mFullscreenStack.getDisplay();
+ final DisplayContent display = mFullscreenStack.getDisplay();
ActivityStack pinnedStack = display.getPinnedStack();
// Ensure a task has moved over.
ensureStackPlacement(pinnedStack, firstActivity);
@@ -147,7 +147,7 @@
}
private static void ensureStackPlacement(ActivityStack stack, ActivityRecord... activities) {
- final Task task = stack.getAllTasks().get(0);
+ final Task task = stack.getBottomMostTask();
final ArrayList<ActivityRecord> stackActivities = new ArrayList<>();
task.forAllActivities((Consumer<ActivityRecord>) stackActivities::add, false);
@@ -166,7 +166,7 @@
@Test
public void testApplySleepTokens() {
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final KeyguardController keyguard = mSupervisor.getKeyguardController();
final ActivityStack stack = new StackBuilder(mRootActivityContainer)
.setCreateActivity(false)
@@ -202,7 +202,7 @@
false /* expectResumeTopActivity */);
}
- private void verifySleepTokenBehavior(ActivityDisplay display, KeyguardController keyguard,
+ private void verifySleepTokenBehavior(DisplayContent display, KeyguardController keyguard,
ActivityStack stack, boolean displaySleeping, boolean displayShouldSleep,
boolean isFocusedStack, boolean keyguardShowing, boolean expectWakeFromSleep,
boolean expectResumeTopActivity) {
@@ -225,7 +225,7 @@
*/
@Test
public void testRemovingStackOnAppCrash() {
- final ActivityDisplay defaultDisplay = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent defaultDisplay = mRootActivityContainer.getDefaultDisplay();
final int originalStackCount = defaultDisplay.getStackCount();
final ActivityStack stack = mRootActivityContainer.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
@@ -320,7 +320,7 @@
.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
.setOnTop(true)
.build();
- final Task task = primaryStack.topTask();
+ final Task task = primaryStack.getTopMostTask();
// Resize dock stack.
mService.resizeDockedStack(stackSize, taskSize, null, null, null);
@@ -336,11 +336,11 @@
@Test
public void testFindTaskToMoveToFrontWhenRecentsOnTop() {
// Create stack/task on default display.
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final ActivityStack targetStack = new StackBuilder(mRootActivityContainer)
.setOnTop(false)
.build();
- final Task targetTask = targetStack.getChildAt(0);
+ final Task targetTask = targetStack.getBottomMostTask();
// Create Recents on top of the display.
final ActivityStack stack = new StackBuilder(mRootActivityContainer).setActivityType(
@@ -360,14 +360,14 @@
@Test
public void testFindTaskToMoveToFrontWhenRecentsOnOtherDisplay() {
// Create stack/task on default display.
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final ActivityStack targetStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, false /* onTop */);
final Task targetTask = new TaskBuilder(mSupervisor).setStack(targetStack).build();
// Create Recents on secondary display.
- final TestActivityDisplay secondDisplay = addNewActivityDisplayAt(
- ActivityDisplay.POSITION_TOP);
+ final TestDisplayContent secondDisplay = addNewDisplayContentAt(
+ DisplayContent.POSITION_TOP);
final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_RECENTS, true /* onTop */);
final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
@@ -387,7 +387,7 @@
@Test
public void testResumeActivityWhenNonTopmostStackIsTopFocused() {
// Create a stack at bottom.
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, false /* onTop */));
final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
@@ -415,8 +415,9 @@
@Test
public void testResumeFocusedStacksStartsHomeActivity_NoActivities() {
mFullscreenStack.removeIfPossible();
- mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().removeIfPossible();
- mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY)
+ mService.mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY).getHomeStack()
+ .removeIfPossible();
+ mService.mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY)
.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
doReturn(true).when(mRootActivityContainer).resumeHomeActivity(any(), any(), anyInt());
@@ -437,13 +438,14 @@
@Test
public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() {
mFullscreenStack.removeIfPossible();
- mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().removeIfPossible();
- mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY)
+ mService.mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY).getHomeStack()
+ .removeIfPossible();
+ mService.mRootActivityContainer.getDisplayContent(DEFAULT_DISPLAY)
.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
// Create an activity on secondary display.
- final TestActivityDisplay secondDisplay = addNewActivityDisplayAt(
- ActivityDisplay.POSITION_TOP);
+ final TestDisplayContent secondDisplay = addNewDisplayContentAt(
+ DisplayContent.POSITION_TOP);
final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, true /* onTop */);
final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
@@ -467,7 +469,7 @@
@Test
public void testResumeActivityLingeringTransition() {
// Create a stack at top.
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, false /* onTop */));
final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
@@ -487,7 +489,7 @@
@Test
public void testResumeActivityLingeringTransition_notExecuted() {
// Create a stack at bottom.
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mRootActivityContainer.getDefaultDisplay();
final ActivityStack targetStack = spy(display.createStack(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, false /* onTop */));
final Task task = new TaskBuilder(mSupervisor).setStack(targetStack).build();
@@ -515,8 +517,8 @@
mockResolveSecondaryHomeActivity();
// Create secondary displays.
- final TestActivityDisplay secondDisplay =
- new TestActivityDisplay.Builder(mService, 1000, 1500)
+ final TestDisplayContent secondDisplay =
+ new TestDisplayContent.Builder(mService, 1000, 1500)
.setSystemDecorations(true).build();
doReturn(true).when(mRootActivityContainer)
@@ -582,8 +584,8 @@
@Test
public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() {
// Create secondary displays.
- final TestActivityDisplay secondDisplay =
- new TestActivityDisplay.Builder(mService, 1000, 1500)
+ final TestDisplayContent secondDisplay =
+ new TestDisplayContent.Builder(mService, 1000, 1500)
.setSystemDecorations(true).build();
// Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
@@ -608,8 +610,8 @@
@Test
public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() {
// Create secondary displays.
- final TestActivityDisplay secondDisplay =
- new TestActivityDisplay.Builder(mService, 1000, 1500)
+ final TestDisplayContent secondDisplay =
+ new TestDisplayContent.Builder(mService, 1000, 1500)
.setSystemDecorations(false).build();
mRootActivityContainer.startHomeOnDisplay(0 /* userId */, "testStartSecondaryHome",
@@ -831,8 +833,8 @@
@Test
public void testGetLaunchStackWithRealCallerId() {
// Create a non-system owned virtual display.
- final TestActivityDisplay secondaryDisplay =
- new TestActivityDisplay.Builder(mService, 1000, 1500)
+ final TestDisplayContent secondaryDisplay =
+ new TestDisplayContent.Builder(mService, 1000, 1500)
.setType(TYPE_VIRTUAL).setOwnerUid(100).build();
// Create an activity with specify the original launch pid / uid.
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index 5c9ccae..b32cb8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -57,9 +57,9 @@
@Test
public void testCollectTasksByLastActiveTime() {
// Create a number of stacks with tasks (of incrementing active time)
- final ArrayList<ActivityDisplay> displays = new ArrayList<>();
- final ActivityDisplay display =
- new TestActivityDisplay.Builder(mService, 1000, 2500).build();
+ final ArrayList<DisplayContent> displays = new ArrayList<>();
+ final DisplayContent display =
+ new TestDisplayContent.Builder(mService, 1000, 2500).build();
displays.add(display);
final int numStacks = 2;
@@ -82,8 +82,8 @@
final int numFetchTasks = 5;
ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
mRunningTasks.getTasks(5, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
- displays, -1 /* callingUid */, true /* allowed */, true /*crossUser */,
- PROFILE_IDS);
+ mRootActivityContainer, -1 /* callingUid */, true /* allowed */,
+ true /*crossUser */, PROFILE_IDS);
assertThat(tasks).hasSize(numFetchTasks);
for (int i = 0; i < numFetchTasks; i++) {
assertEquals(numTasks - i - 1, tasks.get(i).id);
@@ -93,8 +93,8 @@
// and does not crash
tasks.clear();
mRunningTasks.getTasks(100, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
- displays, -1 /* callingUid */, true /* allowed */, true /* crossUser */,
- PROFILE_IDS);
+ mRootActivityContainer, -1 /* callingUid */, true /* allowed */,
+ true /* crossUser */, PROFILE_IDS);
assertThat(tasks).hasSize(numTasks);
for (int i = 0; i < numTasks; i++) {
assertEquals(numTasks - i - 1, tasks.get(i).id);
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 212931b..be0fd2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
@@ -27,7 +26,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
@@ -36,7 +35,6 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -54,7 +52,6 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
/**
* Tests for Size Compatibility mode.
@@ -70,9 +67,9 @@
private Task mTask;
private ActivityRecord mActivity;
- private void setUpApp(ActivityDisplay display) {
+ private void setUpApp(DisplayContent display) {
mStack = new StackBuilder(mRootActivityContainer).setDisplay(display).build();
- mTask = mStack.getChildAt(0);
+ mTask = mStack.getBottomMostTask();
mActivity = mTask.getTopNonFinishingActivity();
}
@@ -82,7 +79,7 @@
@Test
public void testRestartProcessIfVisible() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
mActivity.mVisibleRequested = true;
mActivity.setSavedState(null /* savedState */);
@@ -102,7 +99,7 @@
public void testKeepBoundsWhenChangingFromFreeformToFullscreen() {
removeGlobalMinSizeRestriction();
// create freeform display and a freeform app
- ActivityDisplay display = new TestActivityDisplay.Builder(mService, 2000, 1000)
+ DisplayContent display = new TestDisplayContent.Builder(mService, 2000, 1000)
.setCanRotate(false)
.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build();
setUpApp(display);
@@ -129,7 +126,7 @@
@Test
public void testFixedAspectRatioBoundsWithDecor() {
final int decorHeight = 200; // e.g. The device has cutout.
- setUpApp(new TestActivityDisplay.Builder(mService, 600, 800)
+ setUpApp(new TestDisplayContent.Builder(mService, 600, 800)
.setNotch(decorHeight).build());
mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
@@ -155,11 +152,11 @@
@Test
public void testFixedScreenConfigurationWhenMovingToDisplay() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
// Make a new less-tall display with lower density
- final ActivityDisplay newDisplay =
- new TestActivityDisplay.Builder(mService, 1000, 2000)
+ final DisplayContent newDisplay =
+ new TestDisplayContent.Builder(mService, 1000, 2000)
.setDensityDpi(200).build();
mActivity = new ActivityBuilder(mService)
@@ -183,7 +180,7 @@
@Test
public void testFixedScreenBoundsWhenDisplaySizeChanged() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
prepareUnresizable(-1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertFalse(mActivity.inSizeCompatMode());
@@ -206,7 +203,7 @@
@Test
public void testLetterboxFullscreenBounds() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
// Fill out required fields on default display since WM-side is mocked out
prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
@@ -216,10 +213,10 @@
@Test
public void testMoveToDifferentOrientDisplay() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
- final ActivityDisplay newDisplay =
- new TestActivityDisplay.Builder(mService, 2000, 1000)
+ final DisplayContent newDisplay =
+ new TestDisplayContent.Builder(mService, 2000, 1000)
.setCanRotate(false).build();
prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
@@ -238,7 +235,7 @@
@Test
public void testFixedOrientRotateCutoutDisplay() {
// Create a display with a notch/cutout
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).setNotch(60).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).setNotch(60).build());
prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
final Rect origBounds = new Rect(mActivity.getBounds());
@@ -262,10 +259,11 @@
@Test
public void testFixedAspOrientChangeOrient() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
- assertTrue(mActivity.inSizeCompatMode());
+ // The display aspect ratio 2.5 > 1.4 (max of activity), so the size is fitted.
+ assertFalse(mActivity.inSizeCompatMode());
final Rect originalBounds = new Rect(mActivity.getBounds());
final Rect originalAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
@@ -288,7 +286,7 @@
@Test
public void testFixedScreenLayoutSizeBits() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
| Configuration.SCREENLAYOUT_SIZE_NORMAL
| Configuration.SCREENLAYOUT_COMPAT_NEEDED;
@@ -316,38 +314,36 @@
@Test
public void testResetNonVisibleActivity() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
- final ActivityDisplay display = mStack.getDisplay();
- spyOn(display);
-
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
prepareUnresizable(1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
+ final DisplayContent display = mStack.getDisplay();
+ // Resize the display so the activity is in size compatibility mode.
+ resizeDisplay(display, 900, 1800);
+
mActivity.setState(STOPPED, "testSizeCompatMode");
mActivity.mVisibleRequested = false;
mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
- // Make the parent bounds to be different so the activity is in size compatibility mode.
- mTask.getWindowConfiguration().setAppBounds(new Rect(0, 0, 600, 1200));
// Simulate the display changes orientation.
- when(display.getLastOverrideConfigurationChanges()).thenReturn(
- ActivityInfo.CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION
- | ActivityInfo.CONFIG_WINDOW_CONFIGURATION);
- mActivity.onConfigurationChanged(mTask.getConfiguration());
- when(display.getLastOverrideConfigurationChanges()).thenCallRealMethod();
- // The override configuration should not change so it is still in size compatibility mode.
+ final Configuration c = new Configuration();
+ display.getDisplayRotation().setRotation(ROTATION_90);
+ display.computeScreenConfiguration(c);
+ display.onRequestedOverrideConfigurationChanged(c);
+ // Size compatibility mode is able to handle orientation change so the process shouldn't be
+ // restarted and the override configuration won't be cleared.
+ verify(mActivity, never()).restartProcessIfVisible();
assertTrue(mActivity.inSizeCompatMode());
// Change display density
- final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
- displayContent.mBaseDisplayDensity = (int) (0.7f * displayContent.mBaseDisplayDensity);
- final Configuration c = new Configuration();
- displayContent.computeScreenConfiguration(c);
+ display.mBaseDisplayDensity = (int) (0.7f * display.mBaseDisplayDensity);
+ display.computeScreenConfiguration(c);
mService.mAmInternal = mock(ActivityManagerInternal.class);
- mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
+ display.onRequestedOverrideConfigurationChanged(c);
// The override configuration should be reset and the activity's process will be killed.
assertFalse(mActivity.inSizeCompatMode());
verify(mActivity).restartProcessIfVisible();
- mLockRule.runWithScissors(mService.mH, () -> { }, TimeUnit.SECONDS.toMillis(3));
+ waitHandlerIdle(mService.mH);
verify(mService.mAmInternal).killProcess(
eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
}
@@ -358,11 +354,10 @@
*/
@Test
public void testHandleActivitySizeCompatMode() {
- setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2000).build());
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2000).build());
ActivityRecord activity = mActivity;
activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
- ensureActivityConfiguration();
assertFalse(mActivity.inSizeCompatMode());
final ArrayList<IBinder> compatTokens = new ArrayList<>();
@@ -417,7 +412,7 @@
ensureActivityConfiguration();
}
- private void resizeDisplay(ActivityDisplay display, int width, int height) {
+ private void resizeDisplay(DisplayContent display, int width, int height) {
final DisplayContent displayContent = display.mDisplayContent;
displayContent.mBaseDisplayWidth = width;
displayContent.mBaseDisplayHeight = height;
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 3008a75..e011280 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -88,6 +88,7 @@
mFinishCallbackLatch.countDown();
}
+ @FlakyTest(bugId = 144611135)
@Test
public void testAnimation() throws Exception {
mSurfaceAnimationRunner
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 d3b68e0..811f46f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -286,9 +286,12 @@
// Mock root, some default display, and home stack.
spyOn(mWmService.mRoot);
- final ActivityDisplay display = mAtmService.mRootActivityContainer.getDefaultDisplay();
+ final DisplayContent display = mAtmService.mRootActivityContainer.getDefaultDisplay();
+ // Set default display to be in fullscreen mode. Devices with PC feature may start their
+ // default display in freeform mode but some of tests in WmTests have implicit assumption on
+ // that the default display is in fullscreen mode.
+ display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
spyOn(display);
- spyOn(display.mDisplayContent);
final ActivityStack homeStack = display.getStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
spyOn(homeStack);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 9a91efe..5aa41eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -107,7 +107,7 @@
// =============================
@Test
public void testDefaultToPrimaryDisplay() {
- createNewActivityDisplay(WINDOWING_MODE_FREEFORM);
+ createNewDisplayContent(WINDOWING_MODE_FREEFORM);
assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
@@ -127,8 +127,8 @@
@Test
public void testUsesPreviousDisplayIdIfSet() {
- createNewActivityDisplay(WINDOWING_MODE_FREEFORM);
- final TestActivityDisplay display = createNewActivityDisplay(WINDOWING_MODE_FULLSCREEN);
+ createNewDisplayContent(WINDOWING_MODE_FREEFORM);
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
mCurrent.mPreferredDisplayId = display.mDisplayId;
@@ -140,9 +140,9 @@
@Test
public void testUsesSourcesDisplayIdIfSet() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
- final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
WINDOWING_MODE_FULLSCREEN);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -157,9 +157,9 @@
@Test
public void testUsesOptionsDisplayIdIfSet() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
- final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
WINDOWING_MODE_FULLSCREEN);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -176,9 +176,9 @@
@Test
public void testUsesTasksDisplayIdPriorToSourceIfSet() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
- final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
WINDOWING_MODE_FULLSCREEN);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -193,7 +193,7 @@
@Test
public void testUsesTaskDisplayIdIfSet() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
ActivityRecord source = createSourceActivity(freeformDisplay);
@@ -205,9 +205,9 @@
@Test
public void testUsesNoDisplaySourceHandoverDisplayIdIfSet() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
- final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
WINDOWING_MODE_FULLSCREEN);
mCurrent.mPreferredDisplayId = fullscreenDisplay.mDisplayId;
@@ -227,7 +227,7 @@
// =====================================
@Test
public void testBoundsInOptionsInfersFreeformOnFreeformDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -258,7 +258,7 @@
@Test
public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() {
- final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
WINDOWING_MODE_FULLSCREEN);
final ActivityRecord source = createSourceActivity(fullscreenDisplay);
source.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -272,7 +272,7 @@
@Test
public void testKeepsPictureInPictureLaunchModeInOptions() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -289,7 +289,7 @@
@Test
public void testKeepsPictureInPictureLaunchModeWithBoundsInOptions() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -321,7 +321,7 @@
@Test
public void testNonEmptyLayoutInfersFreeformOnFreeformDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -338,7 +338,7 @@
@Test
public void testNonEmptyLayoutInfersFreeformWithEmptySize() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -382,7 +382,7 @@
@Test
public void testRespectsFullyResolvedCurrentParam_Fullscreen() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -397,7 +397,7 @@
@Test
public void testRespectsModeFromFullyResolvedCurrentParam_NonEmptyBounds() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -414,7 +414,7 @@
@Test
public void testForceMaximizesUnresizeableApp() {
mService.mSizeCompatFreeform = false;
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -437,7 +437,7 @@
@Test
public void testLaunchesAppInWindowOnFreeformDisplay() {
mService.mSizeCompatFreeform = true;
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
Rect expectedLaunchBounds = new Rect(0, 0, 200, 100);
@@ -494,7 +494,7 @@
@Test
public void testUsesFullscreenWhenRequestedOnFreeformDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -510,7 +510,7 @@
@Test
public void testUsesFreeformByDefaultForPostNApp() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -525,7 +525,7 @@
@Test
public void testUsesFreeformByDefaultForPreNResizeableAppWithoutOrientationRequest() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -559,7 +559,7 @@
// ===============================
@Test
public void testKeepsBoundsWithPictureInPictureLaunchModeInOptions() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -578,7 +578,7 @@
@Test
public void testRespectsLaunchBoundsWithFreeformSourceOnFullscreenDisplay() {
- final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
WINDOWING_MODE_FULLSCREEN);
final ActivityRecord source = createSourceActivity(fullscreenDisplay);
source.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -595,7 +595,7 @@
@Test
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_LeftGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -611,7 +611,7 @@
@Test
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -627,7 +627,7 @@
@Test
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopLeftGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -644,7 +644,7 @@
@Test
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_RightGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -660,7 +660,7 @@
@Test
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -676,7 +676,7 @@
@Test
public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomRightGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -693,7 +693,7 @@
@Test
public void testNonEmptyLayoutBoundsOnFreeformDisplay_CenterToDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -709,7 +709,7 @@
@Test
public void testNonEmptyLayoutBoundsOnFreeformDisplay_LeftGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -725,7 +725,7 @@
@Test
public void testNonEmptyLayoutBoundsOnFreeformDisplay_TopGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -741,7 +741,7 @@
@Test
public void testNonEmptyLayoutBoundsOnFreeformDisplay_TopLeftGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -757,7 +757,7 @@
@Test
public void testNonEmptyLayoutBoundsOnFreeformDisplay_RightGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -773,7 +773,7 @@
@Test
public void testNonEmptyLayoutBoundsOnFreeformDisplay_BottomGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -789,7 +789,7 @@
@Test
public void testNonEmptyLayoutBoundsOnFreeformDisplay_RightBottomGravity() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -805,7 +805,7 @@
@Test
public void testNonEmptyLayoutFractionBoundsOnFreeformDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -821,7 +821,7 @@
@Test
public void testRespectBoundsFromFullyResolvedCurrentParam_NonEmptyBounds() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -836,7 +836,7 @@
@Test
public void testReturnBoundsForFullscreenWindowingMode() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
@@ -851,7 +851,7 @@
@Test
public void testUsesDisplayOrientationForNoSensorOrientation() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -875,7 +875,7 @@
@Test
public void testRespectsAppRequestedOrientation_Landscape() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -892,7 +892,7 @@
@Test
public void testRespectsAppRequestedOrientation_Portrait() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -909,7 +909,7 @@
@Test
public void testDefaultSizeSmallerThanBigScreen() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -933,7 +933,7 @@
@Test
public void testDefaultFreeformSizeCenteredToDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -954,7 +954,7 @@
@Test
public void testCascadesToSourceSizeForFreeform() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -981,7 +981,7 @@
@Test
public void testAdjustBoundsToFitDisplay_TopLeftOutOfDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1003,7 +1003,7 @@
@Test
public void testAdjustBoundsToFitDisplay_BottomRightOutOfDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1025,7 +1025,7 @@
@Test
public void testAdjustBoundsToFitNewDisplay_LargerThanDisplay() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1052,7 +1052,7 @@
overrideConfig.setLayoutDirection(new Locale("ar", "EG"));
mRootActivityContainer.onRequestedOverrideConfigurationChanged(overrideConfig);
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1073,7 +1073,7 @@
@Test
public void testRespectsLayoutMinDimensions() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
// This test case requires a relatively big app bounds to ensure the default size calculated
@@ -1098,7 +1098,7 @@
@Test
public void testRotatesInPlaceInitialBoundsMismatchOrientation() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -1115,7 +1115,7 @@
@Test
public void testShiftsToRightForCloseToLeftBoundsWhenConflict() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
addFreeformTaskTo(freeformDisplay, new Rect(50, 50, 100, 150));
@@ -1132,7 +1132,7 @@
@Test
public void testShiftsToLeftForCloseToRightBoundsWhenConflict() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
addFreeformTaskTo(freeformDisplay, new Rect(1720, 50, 1830, 150));
@@ -1149,7 +1149,7 @@
@Test
public void testShiftsToRightFirstForHorizontallyCenteredAndCloseToTopWhenConflict() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
addFreeformTaskTo(freeformDisplay, new Rect(0, 0, 100, 300));
@@ -1166,7 +1166,7 @@
@Test
public void testShiftsToLeftNoSpaceOnRightForHorizontallyCenteredAndCloseToTopWhenConflict() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
addFreeformTaskTo(freeformDisplay, new Rect(120, 0, 240, 300));
@@ -1183,7 +1183,7 @@
@Test
public void testShiftsToBottomRightFirstForCenteredBoundsWhenConflict() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
addFreeformTaskTo(freeformDisplay, new Rect(120, 0, 240, 100));
@@ -1200,7 +1200,7 @@
@Test
public void testShiftsToTopLeftIfNoSpaceOnBottomRightForCenteredBoundsWhenConflict() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
addFreeformTaskTo(freeformDisplay, new Rect(120, 67, 240, 100));
@@ -1230,7 +1230,7 @@
@Test
public void testAdjustsBoundsToFitInDisplayFullyResolvedBounds() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
mCurrent.mPreferredDisplayId = Display.INVALID_DISPLAY;
@@ -1248,7 +1248,7 @@
@Test
public void testAdjustsBoundsToAvoidConflictFullyResolvedBounds() {
- final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
addFreeformTaskTo(freeformDisplay, new Rect(0, 0, 200, 100));
@@ -1283,7 +1283,7 @@
@Test
public void testNoMultiDisplaySupports() {
final boolean orgValue = mService.mSupportsMultiDisplay;
- final TestActivityDisplay display = createNewActivityDisplay(WINDOWING_MODE_FULLSCREEN);
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
mCurrent.mPreferredDisplayId = display.mDisplayId;
try {
@@ -1296,8 +1296,8 @@
}
}
- private TestActivityDisplay createNewActivityDisplay(int windowingMode) {
- final TestActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+ private TestDisplayContent createNewDisplayContent(int windowingMode) {
+ final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
display.setWindowingMode(windowingMode);
display.setBounds(DISPLAY_BOUNDS);
display.getConfiguration().densityDpi = DENSITY_DEFAULT;
@@ -1311,13 +1311,13 @@
return display;
}
- private ActivityRecord createSourceActivity(TestActivityDisplay display) {
+ private ActivityRecord createSourceActivity(TestDisplayContent display) {
final ActivityStack stack = display.createStack(display.getWindowingMode(),
ACTIVITY_TYPE_STANDARD, true);
return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build();
}
- private void addFreeformTaskTo(TestActivityDisplay display, Rect bounds) {
+ private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) {
final ActivityStack stack = display.createStack(display.getWindowingMode(),
ACTIVITY_TYPE_STANDARD, true);
stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 599edb1..de6d752 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -193,7 +193,7 @@
@FlakyTest(bugId = 137879065)
public void testFitWithinBounds() {
final Rect parentBounds = new Rect(10, 10, 200, 200);
- ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
ActivityStack stack = display.createStack(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
@@ -232,10 +232,10 @@
@Test
@FlakyTest(bugId = 137879065)
public void testBoundsOnModeChangeFreeformToFullscreen() {
- ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
ActivityStack stack = new StackBuilder(mRootActivityContainer).setDisplay(display)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
- Task task = stack.getChildAt(0);
+ Task task = stack.getBottomMostTask();
task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
DisplayInfo info = new DisplayInfo();
display.mDisplay.getDisplayInfo(info);
@@ -265,9 +265,9 @@
public void testFullscreenBoundsForcedOrientation() {
final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
final Rect fullScreenBoundsPort = new Rect(0, 0, 1080, 1920);
- ActivityDisplay display = new TestActivityDisplay.Builder(
+ DisplayContent display = new TestDisplayContent.Builder(
mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
- assertTrue(mRootActivityContainer.getActivityDisplay(display.mDisplayId) != null);
+ assertTrue(mRootActivityContainer.getDisplayContent(display.mDisplayId) != null);
// Fix the display orientation to landscape which is the natural rotation (0) for the test
// display.
final DisplayRotation dr = display.mDisplayContent.getDisplayRotation();
@@ -276,7 +276,7 @@
ActivityStack stack = new StackBuilder(mRootActivityContainer)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
- Task task = stack.getChildAt(0);
+ Task task = stack.getBottomMostTask();
ActivityRecord root = task.getTopNonFinishingActivity();
assertEquals(fullScreenBounds, task.getBounds());
@@ -328,7 +328,7 @@
@Test
public void testIgnoresForcedOrientationWhenParentHandles() {
final Rect fullScreenBounds = new Rect(0, 0, 1920, 1080);
- ActivityDisplay display = new TestActivityDisplay.Builder(
+ DisplayContent display = new TestDisplayContent.Builder(
mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
display.getRequestedOverrideConfiguration().orientation =
@@ -337,7 +337,7 @@
display.getRequestedOverrideConfiguration());
ActivityStack stack = new StackBuilder(mRootActivityContainer)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
- Task task = stack.getChildAt(0);
+ Task task = stack.getBottomMostTask();
ActivityRecord root = task.getTopNonFinishingActivity();
final WindowContainer parentWindowContainer =
@@ -427,6 +427,61 @@
assertNotEquals(origScreenH, task.getConfiguration().screenHeightDp);
}
+ @Test
+ public void testInsetDisregardedWhenFreeformOverlapsNavBar() {
+ DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
+ ActivityStack stack = display.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+ true /* onTop */);
+ DisplayInfo displayInfo = new DisplayInfo();
+ mService.mContext.getDisplay().getDisplayInfo(displayInfo);
+ final int displayHeight = displayInfo.logicalHeight;
+ final Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
+ final Configuration inOutConfig = new Configuration();
+ final Configuration parentConfig = new Configuration();
+ final int longSide = 1200;
+ final int shortSide = 600;
+ parentConfig.densityDpi = 400;
+ parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px
+ parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
+ parentConfig.windowConfiguration.setRotation(ROTATION_0);
+
+ final float density = 2.5f; // densityDpi / DENSITY_DEFAULT_SCALE = 400 / 160.0f
+ final int longSideDp = 480; // longSide / density = 1200 / 400 * 160
+ final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160
+ final int screenLayout = parentConfig.screenLayout
+ & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
+ final int reducedScreenLayout =
+ Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp);
+
+ // Portrait bounds overlapping with navigation bar, without insets.
+ inOutConfig.windowConfiguration.getBounds().set(0,
+ displayHeight - 10 - longSide,
+ shortSide,
+ displayHeight - 10);
+ // Set to freeform mode to verify bug fix.
+ inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+ assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
+ assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+ assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
+
+ inOutConfig.setToDefaults();
+ // Landscape bounds overlapping with navigtion bar, without insets.
+ inOutConfig.windowConfiguration.getBounds().set(0,
+ displayHeight - 10 - shortSide,
+ longSide,
+ displayHeight - 10);
+ inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+ assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
+ assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+ assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
+ }
+
/** Ensures that the alias intent won't have target component resolved. */
@Test
public void testTaskIntentActivityAlias() {
@@ -803,13 +858,13 @@
private Task getTestTask() {
final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
- return stack.getChildAt(0);
+ return stack.getBottomMostTask();
}
private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
Rect expectedConfigBounds) {
- ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ DisplayContent display = mService.mRootActivityContainer.getDefaultDisplay();
ActivityStack stack = display.createStack(windowingMode, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
Task task = new TaskBuilder(mSupervisor).setStack(stack).build();
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 6ebaf29..8fe0cdb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -18,15 +18,28 @@
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.TRANSIT_UNSET;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_APP_THEME;
import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -34,6 +47,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
/**
* Test class for {@link TaskSnapshotController}.
@@ -50,8 +64,8 @@
public void testGetClosingApps_closing() {
final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
"closingWindow");
- closingWindow.mActivityRecord.commitVisibility(null, false /* visible */, TRANSIT_UNSET,
- true /* performLayout */, false /* isVoiceInteraction */);
+ closingWindow.mActivityRecord.commitVisibility(
+ false /* visible */, true /* performLayout */);
final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
closingApps.add(closingWindow.mActivityRecord);
final ArraySet<Task> closingTasks = new ArraySet<>();
@@ -66,10 +80,10 @@
"closingWindow");
final WindowState openingWindow = createAppWindow(closingWindow.getTask(),
FIRST_APPLICATION_WINDOW, "openingWindow");
- closingWindow.mActivityRecord.commitVisibility(null, false /* visible */, TRANSIT_UNSET,
- true /* performLayout */, false /* isVoiceInteraction */);
- openingWindow.mActivityRecord.commitVisibility(null, true /* visible */, TRANSIT_UNSET,
- true /* performLayout */, false /* isVoiceInteraction */);
+ closingWindow.mActivityRecord.commitVisibility(
+ false /* visible */, true /* performLayout */);
+ openingWindow.mActivityRecord.commitVisibility(
+ true /* visible */, true /* performLayout */);
final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
closingApps.add(closingWindow.mActivityRecord);
final ArraySet<Task> closingTasks = new ArraySet<>();
@@ -81,8 +95,8 @@
public void testGetClosingApps_skipClosingAppsSnapshotTasks() {
final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
"closingWindow");
- closingWindow.mActivityRecord.commitVisibility(null, false /* visible */, TRANSIT_UNSET,
- true /* performLayout */, false /* isVoiceInteraction */);
+ closingWindow.mActivityRecord.commitVisibility(
+ false /* visible */, true /* performLayout */);
final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
closingApps.add(closingWindow.mActivityRecord);
final ArraySet<Task> closingTasks = new ArraySet<>();
@@ -111,4 +125,75 @@
assertEquals(SNAPSHOT_MODE_APP_THEME,
mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask()));
}
+
+ @Test
+ public void testSnapshotBuilder() {
+ final GraphicBuffer buffer = Mockito.mock(GraphicBuffer.class);
+ final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
+ final long id = 1234L;
+ final ComponentName activityComponent = new ComponentName("package", ".Class");
+ final int windowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+ final int systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN;
+ final int pixelFormat = PixelFormat.RGBA_8888;
+ final int orientation = Configuration.ORIENTATION_PORTRAIT;
+ final float scaleFraction = 0.25f;
+ final Rect contentInsets = new Rect(1, 2, 3, 4);
+
+ try {
+ ActivityManager.TaskSnapshot.Builder builder =
+ new ActivityManager.TaskSnapshot.Builder();
+ builder.setId(id);
+ builder.setTopActivityComponent(activityComponent);
+ builder.setSystemUiVisibility(systemUiVisibility);
+ builder.setWindowingMode(windowingMode);
+ builder.setColorSpace(sRGB);
+ builder.setReducedResolution(true);
+ builder.setOrientation(orientation);
+ builder.setContentInsets(contentInsets);
+ builder.setIsTranslucent(true);
+ builder.setScaleFraction(0.25f);
+ builder.setSnapshot(buffer);
+ builder.setIsRealSnapshot(true);
+ builder.setPixelFormat(pixelFormat);
+
+ // Not part of TaskSnapshot itself, used in screenshot process
+ assertEquals(pixelFormat, builder.getPixelFormat());
+
+ ActivityManager.TaskSnapshot snapshot = builder.build();
+ assertEquals(id, snapshot.getId());
+ assertEquals(activityComponent, snapshot.getTopActivityComponent());
+ assertEquals(systemUiVisibility, snapshot.getSystemUiVisibility());
+ assertEquals(windowingMode, snapshot.getWindowingMode());
+ assertEquals(sRGB, snapshot.getColorSpace());
+ assertTrue(snapshot.isReducedResolution());
+ assertEquals(orientation, snapshot.getOrientation());
+ assertEquals(contentInsets, snapshot.getContentInsets());
+ assertTrue(snapshot.isTranslucent());
+ assertEquals(scaleFraction, builder.getScaleFraction(), 0.001f);
+ assertSame(buffer, snapshot.getSnapshot());
+ assertTrue(snapshot.isRealSnapshot());
+ } finally {
+ if (buffer != null) {
+ buffer.destroy();
+ }
+ }
+ }
+
+ @Test
+ public void testPrepareTaskSnapshot() {
+ mAppWindow.mWinAnimator.mLastAlpha = 1f;
+ spyOn(mAppWindow.mWinAnimator);
+ doReturn(true).when(mAppWindow.mWinAnimator).getShown();
+ doReturn(true).when(mAppWindow.mActivityRecord).isSurfaceShowing();
+
+ final ActivityManager.TaskSnapshot.Builder builder =
+ new ActivityManager.TaskSnapshot.Builder();
+ final float scaleFraction = 0.8f;
+ mWm.mTaskSnapshotController.prepareTaskSnapshot(mAppWindow.mActivityRecord.getTask(),
+ scaleFraction, PixelFormat.UNKNOWN, builder);
+
+ assertEquals(scaleFraction, builder.getScaleFraction(), 0 /* delta */);
+ // The pixel format should be selected automatically.
+ assertNotEquals(PixelFormat.UNKNOWN, builder.getPixelFormat());
+ }
}
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 817344f..2d418ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -74,7 +74,7 @@
0 /* systemUiVisibility */, false /* isTranslucent */);
mSurface = new TaskSnapshotSurface(mWm, new Window(), new SurfaceControl(), snapshot, "Test",
createTaskDescription(Color.WHITE, Color.RED, Color.BLUE), sysuiVis, windowFlags, 0,
- taskBounds, ORIENTATION_PORTRAIT);
+ taskBounds, ORIENTATION_PORTRAIT, null /* insetsState */);
}
private static TaskDescription createTaskDescription(int background, int statusBar,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
similarity index 94%
rename from services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
rename to services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 247cd87..325cea7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -35,11 +35,11 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
-class TestActivityDisplay extends ActivityDisplay {
+class TestDisplayContent extends DisplayContent {
private final ActivityStackSupervisor mSupervisor;
- private TestActivityDisplay(ActivityStackSupervisor supervisor, Display display) {
- super(supervisor.mService.mRootActivityContainer, display);
+ private TestDisplayContent(ActivityStackSupervisor supervisor, Display display) {
+ super(display, supervisor.mService.mRootActivityContainer);
// Normally this comes from display-properties as exposed by WM. Without that, just
// hard-code to FULLSCREEN for tests.
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -140,13 +140,13 @@
mInfo.logicalDensityDpi = dpi;
return this;
}
- TestActivityDisplay build() {
+ TestDisplayContent build() {
final int displayId = SystemServicesTestRule.sNextDisplayId++;
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
- final TestActivityDisplay newDisplay;
+ final TestDisplayContent newDisplay;
synchronized (mService.mGlobalLock) {
- newDisplay = new TestActivityDisplay(mService.mStackSupervisor, display);
+ newDisplay = new TestDisplayContent(mService.mStackSupervisor, display);
mService.mRootActivityContainer.addChild(newDisplay, mPosition);
}
// disable the normal system decorations
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 4d25a83..6108db3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -61,33 +61,33 @@
assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
// Register to display 1 as a listener.
- TestActivityDisplay testActivityDisplay1 = createTestActivityDisplayInContainer();
- mWpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
- assertTrue(testActivityDisplay1.containsListener(mWpc));
- assertEquals(testActivityDisplay1.mDisplayId, mWpc.getDisplayId());
+ TestDisplayContent testDisplayContent1 = createTestDisplayContentInContainer();
+ mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent1);
+ assertTrue(testDisplayContent1.containsListener(mWpc));
+ assertEquals(testDisplayContent1.mDisplayId, mWpc.getDisplayId());
// Move to display 2.
- TestActivityDisplay testActivityDisplay2 = createTestActivityDisplayInContainer();
- mWpc.registerDisplayConfigurationListenerLocked(testActivityDisplay2);
- assertFalse(testActivityDisplay1.containsListener(mWpc));
- assertTrue(testActivityDisplay2.containsListener(mWpc));
- assertEquals(testActivityDisplay2.mDisplayId, mWpc.getDisplayId());
+ TestDisplayContent testDisplayContent2 = createTestDisplayContentInContainer();
+ mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent2);
+ assertFalse(testDisplayContent1.containsListener(mWpc));
+ assertTrue(testDisplayContent2.containsListener(mWpc));
+ assertEquals(testDisplayContent2.mDisplayId, mWpc.getDisplayId());
- // Null ActivityDisplay will not change anything.
+ // Null DisplayContent will not change anything.
mWpc.registerDisplayConfigurationListenerLocked(null);
- assertTrue(testActivityDisplay2.containsListener(mWpc));
- assertEquals(testActivityDisplay2.mDisplayId, mWpc.getDisplayId());
+ assertTrue(testDisplayContent2.containsListener(mWpc));
+ assertEquals(testDisplayContent2.mDisplayId, mWpc.getDisplayId());
// Unregister listener will remove the wpc from registered displays.
mWpc.unregisterDisplayConfigurationListenerLocked();
- assertFalse(testActivityDisplay1.containsListener(mWpc));
- assertFalse(testActivityDisplay2.containsListener(mWpc));
+ assertFalse(testDisplayContent1.containsListener(mWpc));
+ assertFalse(testDisplayContent2.containsListener(mWpc));
assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
// Unregistration still work even if the display was removed.
- mWpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
- assertEquals(testActivityDisplay1.mDisplayId, mWpc.getDisplayId());
- mRootActivityContainer.removeChild(testActivityDisplay1);
+ mWpc.registerDisplayConfigurationListenerLocked(testDisplayContent1);
+ assertEquals(testDisplayContent1.mDisplayId, mWpc.getDisplayId());
+ mRootActivityContainer.removeChild(testDisplayContent1);
mWpc.unregisterDisplayConfigurationListenerLocked();
assertEquals(INVALID_DISPLAY, mWpc.getDisplayId());
}
@@ -129,7 +129,7 @@
orderVerifier.verifyNoMoreInteractions();
}
- private TestActivityDisplay createTestActivityDisplayInContainer() {
- return new TestActivityDisplay.Builder(mService, 1000, 1500).build();
+ private TestDisplayContent createTestDisplayContentInContainer() {
+ return new TestDisplayContent.Builder(mService, 1000, 1500).build();
}
}
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 72baedb..6d23b2e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -32,7 +32,9 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -61,6 +63,7 @@
import android.graphics.Insets;
import android.graphics.Matrix;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.Size;
@@ -81,7 +84,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.LinkedList;
+import java.util.List;
/**
* Tests for the {@link WindowState} class.
@@ -222,8 +227,7 @@
final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow");
- // Setting FLAG_NOT_FOCUSABLE without FLAG_ALT_FOCUSABLE_IM prevents the window from being
- // an IME target.
+ // Setting FLAG_NOT_FOCUSABLE prevents the window from being an IME target.
appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
@@ -231,7 +235,7 @@
appWindow.setHasSurface(true);
imeWindow.setHasSurface(true);
- // Windows without flags (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM) can't be IME targets
+ // Windows with FLAG_NOT_FOCUSABLE can't be IME targets
assertFalse(appWindow.canBeImeTarget());
assertFalse(imeWindow.canBeImeTarget());
@@ -239,11 +243,22 @@
appWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
imeWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
- // Visible app window with flags can be IME target while an IME window can never be an IME
- // target regardless of its visibility or flags.
- assertTrue(appWindow.canBeImeTarget());
+ // Visible app window with flags FLAG_NOT_FOCUSABLE or FLAG_ALT_FOCUSABLE_IM can't be IME
+ // target while an IME window can never be an IME target regardless of its visibility
+ // or flags.
+ assertFalse(appWindow.canBeImeTarget());
assertFalse(imeWindow.canBeImeTarget());
+ // b/145812508: special legacy use-case for transparent/translucent windows.
+ appWindow.mAttrs.format = PixelFormat.TRANSPARENT;
+ assertTrue(appWindow.canBeImeTarget());
+
+ appWindow.mAttrs.format = PixelFormat.OPAQUE;
+ appWindow.mAttrs.flags &= ~FLAG_ALT_FOCUSABLE_IM;
+ assertFalse(appWindow.canBeImeTarget());
+ appWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
+ assertTrue(appWindow.canBeImeTarget());
+
// Make windows invisible
appWindow.hideLw(false /* doAnimation */);
imeWindow.hideLw(false /* doAnimation */);
@@ -521,6 +536,31 @@
}
@Test
+ public void testRequestDrawIfNeeded() {
+ final WindowState startingApp = createWindow(null /* parent */,
+ TYPE_BASE_APPLICATION, "startingApp");
+ final WindowState startingWindow = createWindow(null /* parent */,
+ TYPE_APPLICATION_STARTING, startingApp.mToken, "starting");
+ startingApp.mActivityRecord.startingWindow = startingWindow;
+ final WindowState keyguardHostWindow = mStatusBarWindow;
+ final WindowState allDrawnApp = mAppWindow;
+ allDrawnApp.mActivityRecord.allDrawn = true;
+
+ // The waiting list is used to ensure the content is ready when turning on screen.
+ final List<WindowState> outWaitingForDrawn = mDisplayContent.mWaitingForDrawn;
+ final List<WindowState> visibleWindows = Arrays.asList(mChildAppWindowAbove,
+ keyguardHostWindow, allDrawnApp, startingApp, startingWindow);
+ visibleWindows.forEach(w -> {
+ w.mHasSurface = true;
+ w.requestDrawIfNeeded(outWaitingForDrawn);
+ });
+
+ // Keyguard host window should be always contained. The drawn app or app with starting
+ // window are unnecessary to draw.
+ assertEquals(Arrays.asList(keyguardHostWindow, startingWindow), outWaitingForDrawn);
+ }
+
+ @Test
public void testGetTransformationMatrix() {
final int PARENT_WINDOW_OFFSET = 1;
final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2;
@@ -556,7 +596,7 @@
// Mock active recents animation
RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
- when(recentsController.isAnimatingTask(win0.mActivityRecord.getTask())).thenReturn(true);
+ when(recentsController.shouldApplyInputConsumer(win0.mActivityRecord)).thenReturn(true);
mWm.setRecentsAnimationController(recentsController);
assertTrue(win0.cantReceiveTouchInput());
}
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 22ba90b..6f53428 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -340,8 +340,8 @@
/** Creates a {@link DisplayContent} and adds it to the system. */
DisplayContent createNewDisplay(DisplayInfo info) {
- final ActivityDisplay display =
- new TestActivityDisplay.Builder(mWm.mAtmService, info).build();
+ final DisplayContent display =
+ new TestDisplayContent.Builder(mWm.mAtmService, info).build();
return display.mDisplayContent;
}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 0f3050f..531a931 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -19,6 +19,9 @@
import static com.android.internal.util.ArrayUtils.defeatNullable;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.usage.ExternalStorageStats;
import android.app.usage.IStorageStatsManager;
@@ -30,6 +33,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageStats;
+import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Binder;
@@ -44,10 +48,13 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.CrateInfo;
+import android.os.storage.CrateMetadata;
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.provider.Settings;
+import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.DataUnit;
@@ -67,6 +74,9 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
public class StorageStatsService extends IStorageStatsManager.Stub {
private static final String TAG = "StorageStatsService";
@@ -139,7 +149,7 @@
}
}
- private void enforcePermission(int callingUid, String callingPackage) {
+ private void enforceStatsPermission(int callingUid, String callingPackage) {
final int mode = mAppOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS,
callingUid, callingPackage);
switch (mode) {
@@ -222,7 +232,7 @@
@Override
public long getCacheBytes(String volumeUuid, String callingPackage) {
- enforcePermission(Binder.getCallingUid(), callingPackage);
+ enforceStatsPermission(Binder.getCallingUid(), callingPackage);
long cacheBytes = 0;
for (UserInfo user : mUser.getUsers()) {
@@ -234,7 +244,7 @@
@Override
public long getCacheQuotaBytes(String volumeUuid, int uid, String callingPackage) {
- enforcePermission(Binder.getCallingUid(), callingPackage);
+ enforceStatsPermission(Binder.getCallingUid(), callingPackage);
if (mCacheQuotas.containsKey(volumeUuid)) {
final SparseLongArray uidMap = mCacheQuotas.get(volumeUuid);
@@ -263,7 +273,7 @@
if (Binder.getCallingUid() == appInfo.uid) {
// No permissions required when asking about themselves
} else {
- enforcePermission(Binder.getCallingUid(), callingPackage);
+ enforceStatsPermission(Binder.getCallingUid(), callingPackage);
}
if (defeatNullable(mPackage.getPackagesForUid(appInfo.uid)).length == 1) {
@@ -307,7 +317,7 @@
if (Binder.getCallingUid() == uid) {
// No permissions required when asking about themselves
} else {
- enforcePermission(Binder.getCallingUid(), callingPackage);
+ enforceStatsPermission(Binder.getCallingUid(), callingPackage);
}
final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid));
@@ -354,7 +364,7 @@
}
// Always require permission to see user-level stats
- enforcePermission(Binder.getCallingUid(), callingPackage);
+ enforceStatsPermission(Binder.getCallingUid(), callingPackage);
final int[] appIds = getAppIds(userId);
final PackageStats stats = new PackageStats(TAG);
@@ -381,7 +391,7 @@
}
// Always require permission to see user-level stats
- enforcePermission(Binder.getCallingUid(), callingPackage);
+ enforceStatsPermission(Binder.getCallingUid(), callingPackage);
final int[] appIds = getAppIds(userId);
final long[] stats;
@@ -556,4 +566,143 @@
mContext.getContentResolver().notifyChange(
Uri.parse("content://com.android.externalstorage.documents/"), null, false);
}
+
+ /**
+ * To enforce the calling or self to have the {@link android.Manifest.permission#MANAGE_CRATES}
+ * permission.
+ * @param callingUid the calling uid
+ * @param callingPackage the calling package name
+ */
+ private void enforceCratesPermission(int callingUid, String callingPackage) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_CRATES,
+ callingPackage);
+ }
+
+ /**
+ * To copy from CrateMetadata instances into CrateInfo instances.
+ */
+ @NonNull
+ private static List<CrateInfo> convertCrateInfoFrom(@Nullable CrateMetadata[] crateMetadatas) {
+ if (ArrayUtils.isEmpty(crateMetadatas)) {
+ return Collections.EMPTY_LIST;
+ }
+
+ ArrayList<CrateInfo> crateInfos = new ArrayList<>();
+ for (CrateMetadata crateMetadata : crateMetadatas) {
+ if (crateMetadata == null || TextUtils.isEmpty(crateMetadata.id)
+ || TextUtils.isEmpty(crateMetadata.packageName)) {
+ continue;
+ }
+
+ CrateInfo crateInfo = CrateInfo.copyFrom(crateMetadata.uid,
+ crateMetadata.packageName, crateMetadata.id);
+ if (crateInfo == null) {
+ continue;
+ }
+
+ crateInfos.add(crateInfo);
+ }
+
+ return crateInfos;
+ }
+
+ @NonNull
+ private ParceledListSlice<CrateInfo> getAppCrates(String volumeUuid, String[] packageNames,
+ @UserIdInt int userId) {
+ try {
+ CrateMetadata[] crateMetadatas = mInstaller.getAppCrates(volumeUuid,
+ packageNames, userId);
+ return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas));
+ } catch (InstallerException e) {
+ throw new ParcelableException(new IOException(e.getMessage()));
+ }
+ }
+
+ @NonNull
+ @Override
+ public ParceledListSlice<CrateInfo> queryCratesForPackage(String volumeUuid,
+ @NonNull String packageName, @UserIdInt int userId, @NonNull String callingPackage) {
+ if (userId != UserHandle.getCallingUserId()) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
+ }
+
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = mPackage.getApplicationInfoAsUser(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ } catch (NameNotFoundException e) {
+ throw new ParcelableException(e);
+ }
+
+ if (Binder.getCallingUid() == appInfo.uid) {
+ // No permissions required when asking about themselves
+ } else {
+ enforceCratesPermission(Binder.getCallingUid(), callingPackage);
+ }
+
+ final String[] packageNames = new String[] { packageName };
+ return getAppCrates(volumeUuid, packageNames, userId);
+ }
+
+ @NonNull
+ @Override
+ public ParceledListSlice<CrateInfo> queryCratesForUid(String volumeUuid, int uid,
+ @NonNull String callingPackage) {
+ final int userId = UserHandle.getUserId(uid);
+ if (userId != UserHandle.getCallingUserId()) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
+ }
+
+ if (Binder.getCallingUid() == uid) {
+ // No permissions required when asking about themselves
+ } else {
+ enforceCratesPermission(Binder.getCallingUid(), callingPackage);
+ }
+
+ final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid));
+ String[] validatedPackageNames = new String[0];
+
+ for (String packageName : packageNames) {
+ if (TextUtils.isEmpty(packageName)) {
+ continue;
+ }
+
+ try {
+ final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ if (appInfo == null) {
+ continue;
+ }
+
+ validatedPackageNames = ArrayUtils.appendElement(String.class,
+ validatedPackageNames, packageName);
+ } catch (NameNotFoundException e) {
+ throw new ParcelableException(e);
+ }
+ }
+
+ return getAppCrates(volumeUuid, validatedPackageNames, userId);
+ }
+
+ @NonNull
+ @Override
+ public ParceledListSlice<CrateInfo> queryCratesForUser(String volumeUuid, int userId,
+ @NonNull String callingPackage) {
+ if (userId != UserHandle.getCallingUserId()) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
+ }
+
+ // Always require permission to see user-level stats
+ enforceCratesPermission(Binder.getCallingUid(), callingPackage);
+
+ try {
+ CrateMetadata[] crateMetadatas = mInstaller.getUserCrates(volumeUuid, userId);
+ return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas));
+ } catch (InstallerException e) {
+ throw new ParcelableException(new IOException(e.getMessage()));
+ }
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 1996dd4..ef9a73b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -229,8 +229,8 @@
}
try {
- IntervalStats stats = new IntervalStats();
for (int i = start; i < fileCount - 1; i++) {
+ final IntervalStats stats = new IntervalStats();
readLocked(files.valueAt(i), stats);
if (!checkinAction.checkin(stats)) {
return false;
@@ -848,10 +848,10 @@
}
}
- final IntervalStats stats = new IntervalStats();
final ArrayList<T> results = new ArrayList<>();
for (int i = startIndex; i <= endIndex; i++) {
final AtomicFile f = intervalStats.valueAt(i);
+ final IntervalStats stats = new IntervalStats();
if (DEBUG) {
Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath());
@@ -1061,6 +1061,11 @@
}
}
+ /**
+ * Note: the data read from the given file will add to the IntervalStats object passed into this
+ * method. It is up to the caller to ensure that this is the desired behavior - if not, the
+ * caller should ensure that the data in the reused object is being cleared.
+ */
private void readLocked(AtomicFile file, IntervalStats statsOut)
throws IOException, RuntimeException {
if (mCurrentVersion <= 3) {
@@ -1072,6 +1077,10 @@
/**
* Returns {@code true} if any stats were omitted while reading, {@code false} otherwise.
+ * <p/>
+ * Note: the data read from the given file will add to the IntervalStats object passed into this
+ * method. It is up to the caller to ensure that this is the desired behavior - if not, the
+ * caller should ensure that the data in the reused object is being cleared.
*/
private static boolean readLocked(AtomicFile file, IntervalStats statsOut, int version,
PackagesTokenData packagesTokenData) throws IOException, RuntimeException {
@@ -1098,6 +1107,10 @@
/**
* Returns {@code true} if any stats were omitted while reading, {@code false} otherwise.
+ * <p/>
+ * Note: the data read from the given file will add to the IntervalStats object passed into this
+ * method. It is up to the caller to ensure that this is the desired behavior - if not, the
+ * caller should ensure that the data in the reused object is being cleared.
*/
private static boolean readLocked(InputStream in, IntervalStats statsOut, int version,
PackagesTokenData packagesTokenData) throws RuntimeException {
diff --git a/services/usb/java/com/android/server/usb/UsbHandlerManager.java b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
index 1730d8f..f311274 100644
--- a/services/usb/java/com/android/server/usb/UsbHandlerManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
@@ -59,8 +60,9 @@
if (uri != null && uri.length() > 0) {
// display URI to user
Intent dialogIntent = createDialogIntent();
- dialogIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbAccessoryUriActivity");
+ dialogIntent.setComponent(ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_usbAccessoryUriActivity)));
dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
dialogIntent.putExtra("uri", uri);
try {
@@ -84,8 +86,9 @@
@Nullable UsbAccessory accessory) {
Intent resolverIntent = createDialogIntent();
// start UsbConfirmActivity if there is only one choice
- resolverIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbConfirmActivity");
+ resolverIntent.setComponent(ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_usbConfirmActivity)));
resolverIntent.putExtra("rinfo", rInfo);
UserHandle user =
UserHandle.getUserHandleForUid(rInfo.activityInfo.applicationInfo.uid);
@@ -115,8 +118,9 @@
void selectUsbHandler(@NonNull ArrayList<ResolveInfo> matches,
@NonNull UserHandle user, @NonNull Intent intent) {
Intent resolverIntent = createDialogIntent();
- resolverIntent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbResolverActivity");
+ resolverIntent.setComponent(ComponentName.unflattenFromString(
+ mContext.getResources().getString(
+ com.android.internal.R.string.config_usbResolverActivity)));
resolverIntent.putParcelableArrayListExtra("rlist", matches);
resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 749258e..c3e2013 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -34,6 +34,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -225,8 +226,8 @@
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbContaminantActivity");
+ intent.setComponent(ComponentName.unflattenFromString(r.getString(
+ com.android.internal.R.string.config_usbContaminantActivity)));
intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort));
PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 86016bb..095e8e9 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -75,12 +75,14 @@
if (uid != Process.SYSTEM_UID) {
enforcePackageBelongsToUid(uid, packageName);
+ UserHandle user = Binder.getCallingUserHandle();
int packageTargetSdkVersion;
long token = Binder.clearCallingIdentity();
try {
PackageInfo pkg;
try {
- pkg = mContext.getPackageManager().getPackageInfo(packageName, 0);
+ pkg = mContext.getPackageManager()
+ .getPackageInfoAsUser(packageName, 0, user.getIdentifier());
} catch (PackageManager.NameNotFoundException e) {
throw new RemoteException("package " + packageName + " cannot be found");
}
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 58f5484..2a94393 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -515,8 +516,8 @@
intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault);
intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName);
- intent.setClassName("com.android.systemui",
- "com.android.systemui.usb.UsbPermissionActivity");
+ intent.setComponent(ComponentName.unflattenFromString(userContext.getResources().getString(
+ com.android.internal.R.string.config_usbPermissionActivity)));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 735b9a1..198b4c3 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -16,11 +16,14 @@
package com.android.server.soundtrigger;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
@@ -28,6 +31,7 @@
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;
@@ -605,6 +609,67 @@
return STATUS_ERROR;
}
+ 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);
+ }
+ }
+
+ int getParameter(@NonNull UUID modelId, @ModelParams int modelParam)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ 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);
+ }
+ }
+
+ @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);
+ }
+ }
+
//---- SoundTrigger.StatusListener methods
@Override
public void onRecognition(RecognitionEvent event) {
@@ -653,15 +718,15 @@
}
ModelData model = getModelDataForLocked(event.soundModelHandle);
if (model == null || !model.isGenericModel()) {
- Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
- event.soundModelHandle);
+ Slog.w(TAG, "Generic recognition event: Model does not exist for handle: "
+ + event.soundModelHandle);
return;
}
IRecognitionStatusCallback callback = model.getCallback();
if (callback == null) {
- Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
- event.soundModelHandle);
+ Slog.w(TAG, "Generic recognition event: Null callback for model handle: "
+ + event.soundModelHandle);
return;
}
@@ -678,8 +743,8 @@
RecognitionConfig config = model.getRecognitionConfig();
if (config == null) {
- Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
- event.soundModelHandle);
+ Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: "
+ + event.soundModelHandle);
return;
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 1dd3972..96d2df1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -22,7 +22,9 @@
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.GET_SERVICES;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_NO_INIT;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
@@ -39,9 +41,11 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
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.SoundModel;
@@ -683,6 +687,101 @@
return properties;
}
}
+
+ @Override
+ public int setParameter(ParcelUuid soundModelId,
+ @ModelParams int modelParam, int value) {
+ enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+ if (!isInitialized()) return STATUS_NO_INIT;
+ if (DEBUG) {
+ Slog.d(TAG, "setParameter(): id=" + soundModelId
+ + ", param=" + modelParam
+ + ", value=" + value);
+ }
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(
+ "setParameter(): id=" + soundModelId
+ + ", param=" + modelParam
+ + ", value=" + value));
+
+ synchronized (mLock) {
+ SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+ if (soundModel == null) {
+ Slog.e(TAG, soundModelId + " is not loaded. Loaded models: "
+ + mLoadedModels.toString());
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent("setParameter(): "
+ + soundModelId + " is not loaded"));
+
+ return STATUS_BAD_VALUE;
+ }
+
+ return mSoundTriggerHelper.setParameter(soundModel.uuid, modelParam, value);
+ }
+ }
+
+ @Override
+ public int getParameter(@NonNull ParcelUuid soundModelId,
+ @ModelParams int modelParam)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+ if (!isInitialized()) {
+ throw new UnsupportedOperationException("SoundTriggerHelper not initialized");
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "getParameter(): id=" + soundModelId
+ + ", param=" + modelParam);
+ }
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(
+ "getParameter(): id=" + soundModelId
+ + ", param=" + modelParam));
+
+ synchronized (mLock) {
+ SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+ if (soundModel == null) {
+ Slog.e(TAG, soundModelId + " is not loaded");
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent("getParameter(): "
+ + soundModelId + " is not loaded"));
+
+ throw new IllegalArgumentException("sound model is not loaded");
+ }
+
+ return mSoundTriggerHelper.getParameter(soundModel.uuid, modelParam);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParamRange queryParameter(@NonNull ParcelUuid soundModelId,
+ @ModelParams int modelParam) {
+ enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+ if (!isInitialized()) return null;
+ if (DEBUG) {
+ Slog.d(TAG, "queryParameter(): id=" + soundModelId
+ + ", param=" + modelParam);
+ }
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(
+ "queryParameter(): id=" + soundModelId
+ + ", param=" + modelParam));
+
+ synchronized (mLock) {
+ SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
+ if (soundModel == null) {
+ Slog.e(TAG, soundModelId + " is not loaded");
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(
+ "queryParameter(): "
+ + soundModelId + " is not loaded"));
+
+ return null;
+ }
+
+ return mSoundTriggerHelper.queryParameter(soundModel.uuid, modelParam);
+ }
+ }
}
/**
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 6132cc2..404346f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1291,6 +1291,7 @@
pw.println(" mCurUser: " + mCurUser);
pw.println(" mCurUserUnlocked: " + mCurUserUnlocked);
pw.println(" mCurUserSupported: " + mCurUserSupported);
+ dumpSupportedUsers(pw, " ");
if (mImpl == null) {
pw.println(" (No active implementation)");
return;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index c063279..0becaf2 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -695,6 +695,15 @@
public static final String EVENT_CALL_HOLD_FAILED = "android.telecom.event.CALL_HOLD_FAILED";
/**
+ * Connection event used to inform Telecom when a switch operation on a call has failed.
+ * <p>
+ * Sent via {@link #sendConnectionEvent(String, Bundle)}. The {@link Bundle} parameter is
+ * expected to be null when this connection event is used.
+ */
+ public static final String EVENT_CALL_SWITCH_FAILED =
+ "android.telecom.event.CALL_SWITCH_FAILED";
+
+ /**
* Connection event used to inform {@link InCallService}s when the process of merging a
* Connection into a conference has begun.
* <p>
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 39d741c..af3c55a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -485,6 +485,103 @@
"android.telecom.extra.START_CALL_WITH_RTT";
/**
+ * Start an activity indicating that the completion of an outgoing call or an incoming call
+ * which was not blocked by the {@link CallScreeningService}, and which was NOT terminated
+ * while the call was in {@link Call#STATE_AUDIO_PROCESSING}.
+ *
+ * The {@link Uri} extra {@link #EXTRA_HANDLE} will contain the uri handle(phone number) for the
+ * call which completed.
+ *
+ * The integer extra {@link #EXTRA_DISCONNECT_CAUSE} will indicate the reason for the call
+ * disconnection. See {@link #EXTRA_DISCONNECT_CAUSE} for more information.
+ *
+ * The integer extra {@link #EXTRA_CALL_DURATION} will indicate the duration of the call. See
+ * {@link #EXTRA_CALL_DURATION} for more information.
+ */
+ public static final String ACTION_POST_CALL = "android.telecom.action.POST_CALL";
+
+ /**
+ * A {@link Uri} extra, which when set on the {@link #ACTION_POST_CALL} intent, indicates the
+ * uri handle(phone number) of the completed call.
+ */
+ public static final String EXTRA_HANDLE = "android.telecom.extra.HANDLE";
+
+ /**
+ * A integer value provided for completed calls to indicate the reason for the call
+ * disconnection.
+ * <p>
+ * Allowed values:
+ * <ul>
+ * <li>{@link DisconnectCause#UNKNOWN}</li>
+ * <li>{@link DisconnectCause#LOCAL}</li>
+ * <li>{@link DisconnectCause#REMOTE}</li>
+ * <li>{@link DisconnectCause#REJECTED}</li>
+ * <li>{@link DisconnectCause#MISSED}</li>
+ * </ul>
+ * </p>
+ */
+ public static final String EXTRA_DISCONNECT_CAUSE = "android.telecom.extra.DISCONNECT_CAUSE";
+
+ /**
+ * A integer value provided for completed calls to indicate the duration of the call.
+ * <p>
+ * Allowed values:
+ * <ul>
+ * <li>{@link #DURATION_VERY_SHORT}</li>
+ * <li>{@link #DURATION_SHORT}</li>
+ * <li>{@link #DURATION_MEDIUM}</li>
+ * <li>{@link #DURATION_LONG}</li>
+ * </ul>
+ * </p>
+ */
+ public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
+
+ /**
+ * A integer value for {@link #EXTRA_CALL_DURATION}, indicates the duration of the completed
+ * call was < 3 seconds.
+ */
+ public static final int DURATION_VERY_SHORT = 0;
+
+ /**
+ * A integer value for {@link #EXTRA_CALL_DURATION}, indicates the duration of the completed
+ * call was >= 3 seconds and < 60 seconds.
+ */
+ public static final int DURATION_SHORT = 1;
+
+ /**
+ * A integer value for {@link #EXTRA_CALL_DURATION}, indicates the duration of the completed
+ * call was >= 60 seconds and < 120 seconds.
+ */
+ public static final int DURATION_MEDIUM = 2;
+
+ /**
+ * A integer value for {@link #EXTRA_CALL_DURATION}, indicates the duration of the completed
+ * call was >= 120 seconds.
+ */
+ public static final int DURATION_LONG = 3;
+
+ /**
+ * The threshold between {@link #DURATION_VERY_SHORT} calls and {@link #DURATION_SHORT} calls in
+ * milliseconds.
+ * @hide
+ */
+ public static final long VERY_SHORT_CALL_TIME_MS = 3000;
+
+ /**
+ * The threshold between {@link #DURATION_SHORT} calls and {@link #DURATION_MEDIUM} calls in
+ * milliseconds.
+ * @hide
+ */
+ public static final long SHORT_CALL_TIME_MS = 60000;
+
+ /**
+ * The threshold between {@link #DURATION_MEDIUM} calls and {@link #DURATION_LONG} calls in
+ * milliseconds.
+ * @hide
+ */
+ public static final long MEDIUM_CALL_TIME_MS = 120000;
+
+ /**
* A boolean meta-data value indicating whether an {@link InCallService} implements an
* in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which
* would also like to replace the in-call interface should set this meta-data to {@code true} in
diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
index 367aad1..06c08f5 100644
--- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
+++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
@@ -20,8 +20,8 @@
import android.database.Cursor;
import android.database.SQLException;
import android.os.Binder;
-import android.os.Build;
import android.os.PersistableBundle;
+import android.os.SystemProperties;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
import android.telephony.Rlog;
@@ -43,7 +43,7 @@
*/
public class SmsNumberUtils {
private static final String TAG = "SmsNumberUtils";
- private static final boolean DBG = Build.IS_DEBUGGABLE;
+ private static final boolean DBG = SystemProperties.getInt("ro.debuggable", 0) == 1;
private static final String PLUS_SIGN = "+";
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index bd22787..80a55b2 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -27,8 +27,6 @@
import android.os.Binder;
import android.os.Build;
import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
@@ -42,7 +40,6 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.function.Supplier;
/** Utility class for Telephony permission enforcement. */
public final class TelephonyPermissions {
@@ -50,9 +47,6 @@
private static final boolean DBG = false;
- private static final Supplier<ITelephony> TELEPHONY_SUPPLIER = () ->
- ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
-
/**
* Whether to disable the new device identifier access restrictions.
*/
@@ -138,49 +132,6 @@
public static boolean checkReadPhoneState(
Context context, int subId, int pid, int uid, String callingPackage,
@Nullable String callingFeatureId, String message) {
- return checkReadPhoneState(
- context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingFeatureId,
- message);
- }
-
- /**
- * Check whether the calling packages has carrier privileges for the passing subscription.
- * @return {@code true} if the caller has carrier privileges, {@false} otherwise.
- */
- public static boolean checkCarrierPrivilegeForSubId(int subId) {
- if (SubscriptionManager.isValidSubscriptionId(subId)
- && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, Binder.getCallingUid())
- == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- return true;
- }
- return false;
- }
-
- /**
- * Check whether the app with the given pid/uid can read phone state.
- *
- * <p>This method behaves in one of the following ways:
- * <ul>
- * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the
- * READ_PHONE_STATE runtime permission, or carrier privileges on the given subId.
- * <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for
- * apps which support runtime permissions, if the caller does not currently have any of
- * these permissions.
- * <li>return false: if the caller lacks all of these permissions and doesn't support runtime
- * permissions. This implies that the user revoked the ability to read phone state
- * manually (via AppOps). In this case we can't throw as it would break app compatibility,
- * so we return false to indicate that the calling function should return dummy data.
- * </ul>
- *
- * <p>Note: for simplicity, this method always returns false for callers using legacy
- * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged.
- * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+
- * devices.
- */
- @VisibleForTesting
- public static boolean checkReadPhoneState(
- Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
- String callingPackage, @Nullable String callingFeatureId, String message) {
try {
context.enforcePermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message);
@@ -195,7 +146,7 @@
// If we don't have the runtime permission, but do have carrier privileges, that
// suffices for reading phone state.
if (SubscriptionManager.isValidSubscriptionId(subId)) {
- enforceCarrierPrivilege(telephonySupplier, subId, uid, message);
+ enforceCarrierPrivilege(context, subId, uid, message);
return true;
}
throw phoneStateException;
@@ -210,6 +161,19 @@
}
/**
+ * Check whether the calling packages has carrier privileges for the passing subscription.
+ * @return {@code true} if the caller has carrier privileges, {@false} otherwise.
+ */
+ public static boolean checkCarrierPrivilegeForSubId(Context context, int subId) {
+ if (SubscriptionManager.isValidSubscriptionId(subId)
+ && getCarrierPrivilegeStatus(context, subId, Binder.getCallingUid())
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Check whether the app with the given pid/uid can read phone state, or has carrier
* privileges on any active subscription.
*
@@ -225,28 +189,6 @@
*/
public static boolean checkReadPhoneStateOnAnyActiveSub(Context context, int pid, int uid,
String callingPackage, @Nullable String callingFeatureId, String message) {
- return checkReadPhoneStateOnAnyActiveSub(context, TELEPHONY_SUPPLIER, pid, uid,
- callingPackage, callingFeatureId, message);
- }
-
- /**
- * Check whether the app with the given pid/uid can read phone state, or has carrier
- * privileges on any active subscription.
- *
- * <p>If the app does not have carrier privilege, this method will return {@code false} instead
- * of throwing a SecurityException. Therefore, the callers cannot tell the difference
- * between M+ apps which declare the runtime permission but do not have it, and pre-M apps
- * which declare the static permission but had access revoked via AppOps. Apps in the former
- * category expect SecurityExceptions; apps in the latter don't. So this method is suitable for
- * use only if the behavior in both scenarios is meant to be identical.
- *
- * @return {@code true} if the app can read phone state or has carrier privilege;
- * {@code false} otherwise.
- */
- @VisibleForTesting
- public static boolean checkReadPhoneStateOnAnyActiveSub(
- Context context, Supplier<ITelephony> telephonySupplier, int pid, int uid,
- String callingPackage, @Nullable String callingFeatureId, String message) {
try {
context.enforcePermission(
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message);
@@ -260,7 +202,7 @@
} catch (SecurityException phoneStateException) {
// If we don't have the runtime permission, but do have carrier privileges, that
// suffices for reading phone state.
- return checkCarrierPrivilegeForAnySubId(context, telephonySupplier, uid);
+ return checkCarrierPrivilegeForAnySubId(context, uid);
}
}
@@ -375,12 +317,11 @@
}
// If the calling package has carrier privileges for specified sub, then allow access.
- if (checkCarrierPrivilegeForSubId(subId)) return true;
+ if (checkCarrierPrivilegeForSubId(context, subId)) return true;
// If the calling package has carrier privileges for any subscription
// and allowCarrierPrivilegeOnAnySub is set true, then allow access.
- if (allowCarrierPrivilegeOnAnySub && checkCarrierPrivilegeForAnySubId(
- context, TELEPHONY_SUPPLIER, uid)) {
+ if (allowCarrierPrivilegeOnAnySub && checkCarrierPrivilegeForAnySubId(context, uid)) {
return true;
}
@@ -468,7 +409,7 @@
uid) == PackageManager.PERMISSION_GRANTED) {
return false;
}
- if (checkCarrierPrivilegeForSubId(subId)) {
+ if (checkCarrierPrivilegeForSubId(context, subId)) {
return false;
}
}
@@ -484,26 +425,12 @@
public static boolean checkReadCallLog(
Context context, int subId, int pid, int uid, String callingPackage,
@Nullable String callingPackageName) {
- return checkReadCallLog(
- context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, callingPackageName);
- }
-
- /**
- * Check whether the app with the given pid/uid can read the call log.
- * @return {@code true} if the specified app has the read call log permission and AppOpp granted
- * to it, {@code false} otherwise.
- */
- @VisibleForTesting
- public static boolean checkReadCallLog(
- Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
- String callingPackage, @Nullable String callingFeatureId) {
-
if (context.checkPermission(Manifest.permission.READ_CALL_LOG, pid, uid)
!= PERMISSION_GRANTED) {
// If we don't have the runtime permission, but do have carrier privileges, that
// suffices for being able to see the call phone numbers.
if (SubscriptionManager.isValidSubscriptionId(subId)) {
- enforceCarrierPrivilege(telephonySupplier, subId, uid, "readCallLog");
+ enforceCarrierPrivilege(context, subId, uid, "readCallLog");
return true;
}
return false;
@@ -513,7 +440,7 @@
// revoked.
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
return appOps.noteOp(AppOpsManager.OPSTR_READ_CALL_LOG, uid, callingPackage,
- callingFeatureId, null) == AppOpsManager.MODE_ALLOWED;
+ callingPackageName, null) == AppOpsManager.MODE_ALLOWED;
}
/**
@@ -526,7 +453,7 @@
Context context, int subId, String callingPackage, @Nullable String callingFeatureId,
String message) {
return checkReadPhoneNumber(
- context, TELEPHONY_SUPPLIER, subId, Binder.getCallingPid(), Binder.getCallingUid(),
+ context, subId, Binder.getCallingPid(), Binder.getCallingUid(),
callingPackage, callingFeatureId, message);
}
@@ -538,7 +465,7 @@
*/
@VisibleForTesting
public static boolean checkReadPhoneNumber(
- Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
+ Context context, int subId, int pid, int uid,
String callingPackage, @Nullable String callingFeatureId, String message) {
// Default SMS app can always read it.
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
@@ -553,7 +480,7 @@
// First, check if we can read the phone state.
try {
return checkReadPhoneState(
- context, telephonySupplier, subId, pid, uid, callingPackage, callingFeatureId,
+ context, subId, pid, uid, callingPackage, callingFeatureId,
message);
} catch (SecurityException readPhoneStateSecurityException) {
}
@@ -595,7 +522,7 @@
}
if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next.");
- enforceCallingOrSelfCarrierPrivilege(subId, message);
+ enforceCallingOrSelfCarrierPrivilege(context, subId, message);
}
/**
@@ -615,7 +542,7 @@
Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
}
- enforceCallingOrSelfCarrierPrivilege(subId, message);
+ enforceCallingOrSelfCarrierPrivilege(context, subId, message);
}
/**
@@ -636,7 +563,7 @@
+ "check carrier privilege next.");
}
- enforceCallingOrSelfCarrierPrivilege(subId, message);
+ enforceCallingOrSelfCarrierPrivilege(context, subId, message);
}
/**
@@ -644,21 +571,18 @@
*
* @throws SecurityException if the caller does not have the required privileges
*/
- public static void enforceCallingOrSelfCarrierPrivilege(int subId, String message) {
+ public static void enforceCallingOrSelfCarrierPrivilege(
+ Context context, int subId, String message) {
// NOTE: It's critical that we explicitly pass the calling UID here rather than call
// TelephonyManager#hasCarrierPrivileges directly, as the latter only works when called from
// the phone process. When called from another process, it will check whether that process
// has carrier privileges instead.
- enforceCarrierPrivilege(subId, Binder.getCallingUid(), message);
- }
-
- private static void enforceCarrierPrivilege(int subId, int uid, String message) {
- enforceCarrierPrivilege(TELEPHONY_SUPPLIER, subId, uid, message);
+ enforceCarrierPrivilege(context, subId, Binder.getCallingUid(), message);
}
private static void enforceCarrierPrivilege(
- Supplier<ITelephony> telephonySupplier, int subId, int uid, String message) {
- if (getCarrierPrivilegeStatus(telephonySupplier, subId, uid)
+ 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.");
throw new SecurityException(message);
@@ -666,13 +590,12 @@
}
/** Returns whether the provided uid has carrier privileges for any active subscription ID. */
- private static boolean checkCarrierPrivilegeForAnySubId(
- Context context, Supplier<ITelephony> telephonySupplier, int uid) {
+ private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) {
SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false);
for (int activeSubId : activeSubIds) {
- if (getCarrierPrivilegeStatus(telephonySupplier, activeSubId, uid)
+ if (getCarrierPrivilegeStatus(context, activeSubId, uid)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
return true;
}
@@ -680,18 +603,15 @@
return false;
}
- private static int getCarrierPrivilegeStatus(
- Supplier<ITelephony> telephonySupplier, int subId, int uid) {
- ITelephony telephony = telephonySupplier.get();
+ private static int getCarrierPrivilegeStatus(Context context, int subId, int uid) {
+ final long identity = Binder.clearCallingIdentity();
try {
- if (telephony != null) {
- return telephony.getCarrierPrivilegeStatusForUid(subId, uid);
- }
- } catch (RemoteException e) {
- // Fallback below.
+ TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ return telephonyManager.createForSubscriptionId(subId).getCarrierPrivilegeStatus(uid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
- Rlog.e(LOG_TAG, "Phone process is down, cannot check carrier privileges");
- return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
}
/**
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index f89bbc7..9b9997f 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -116,7 +116,8 @@
ApnSetting.TYPE_CBS,
ApnSetting.TYPE_IA,
ApnSetting.TYPE_EMERGENCY,
- ApnSetting.TYPE_MCX
+ ApnSetting.TYPE_MCX,
+ ApnSetting.TYPE_XCAP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApnType {
@@ -463,9 +464,7 @@
DataFailCause.UNKNOWN,
DataFailCause.RADIO_NOT_AVAILABLE,
DataFailCause.UNACCEPTABLE_NETWORK_PARAMETER,
- DataFailCause.CONNECTION_TO_DATACONNECTIONAC_BROKEN,
DataFailCause.LOST_CONNECTION,
- DataFailCause.RESET_BY_FRAMEWORK
})
@Retention(RetentionPolicy.SOURCE)
public @interface DataFailureCause {
@@ -486,6 +485,86 @@
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING})
public @interface PreciseCallStates {}
+ @IntDef(value = {
+ DisconnectCause.NOT_VALID,
+ DisconnectCause.NOT_DISCONNECTED,
+ DisconnectCause.INCOMING_MISSED,
+ DisconnectCause.NORMAL,
+ DisconnectCause.LOCAL,
+ DisconnectCause.BUSY,
+ DisconnectCause.CONGESTION,
+ DisconnectCause.MMI,
+ DisconnectCause.INVALID_NUMBER,
+ DisconnectCause.NUMBER_UNREACHABLE,
+ DisconnectCause.SERVER_UNREACHABLE,
+ DisconnectCause.INVALID_CREDENTIALS,
+ DisconnectCause.OUT_OF_NETWORK,
+ DisconnectCause.SERVER_ERROR,
+ DisconnectCause.TIMED_OUT,
+ DisconnectCause.LOST_SIGNAL,
+ DisconnectCause.LIMIT_EXCEEDED,
+ DisconnectCause.INCOMING_REJECTED,
+ DisconnectCause.POWER_OFF,
+ DisconnectCause.OUT_OF_SERVICE,
+ DisconnectCause.ICC_ERROR,
+ DisconnectCause.CALL_BARRED,
+ DisconnectCause.FDN_BLOCKED,
+ DisconnectCause.CS_RESTRICTED,
+ DisconnectCause.CS_RESTRICTED_NORMAL,
+ DisconnectCause.CS_RESTRICTED_EMERGENCY,
+ DisconnectCause.UNOBTAINABLE_NUMBER,
+ DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
+ DisconnectCause.CDMA_DROP,
+ DisconnectCause.CDMA_INTERCEPT,
+ DisconnectCause.CDMA_REORDER,
+ DisconnectCause.CDMA_SO_REJECT,
+ DisconnectCause.CDMA_RETRY_ORDER,
+ DisconnectCause.CDMA_ACCESS_FAILURE,
+ DisconnectCause.CDMA_PREEMPTED,
+ DisconnectCause.CDMA_NOT_EMERGENCY,
+ DisconnectCause.CDMA_ACCESS_BLOCKED,
+ DisconnectCause.ERROR_UNSPECIFIED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisconnectCauses {
+ }
+
+ @IntDef(value = {
+ PreciseDisconnectCause.NOT_VALID,
+ PreciseDisconnectCause.NO_DISCONNECT_CAUSE_AVAILABLE,
+ PreciseDisconnectCause.UNOBTAINABLE_NUMBER,
+ PreciseDisconnectCause.NORMAL,
+ PreciseDisconnectCause.BUSY,
+ PreciseDisconnectCause.NUMBER_CHANGED,
+ PreciseDisconnectCause.STATUS_ENQUIRY,
+ PreciseDisconnectCause.NORMAL_UNSPECIFIED,
+ PreciseDisconnectCause.NO_CIRCUIT_AVAIL,
+ PreciseDisconnectCause.TEMPORARY_FAILURE,
+ PreciseDisconnectCause.SWITCHING_CONGESTION,
+ PreciseDisconnectCause.CHANNEL_NOT_AVAIL,
+ PreciseDisconnectCause.QOS_NOT_AVAIL,
+ PreciseDisconnectCause.BEARER_NOT_AVAIL,
+ PreciseDisconnectCause.ACM_LIMIT_EXCEEDED,
+ PreciseDisconnectCause.CALL_BARRED,
+ PreciseDisconnectCause.FDN_BLOCKED,
+ PreciseDisconnectCause.IMSI_UNKNOWN_IN_VLR,
+ PreciseDisconnectCause.IMEI_NOT_ACCEPTED,
+ PreciseDisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE,
+ PreciseDisconnectCause.CDMA_DROP,
+ PreciseDisconnectCause.CDMA_INTERCEPT,
+ PreciseDisconnectCause.CDMA_REORDER,
+ PreciseDisconnectCause.CDMA_SO_REJECT,
+ PreciseDisconnectCause.CDMA_RETRY_ORDER,
+ PreciseDisconnectCause.CDMA_ACCESS_FAILURE,
+ PreciseDisconnectCause.CDMA_PREEMPTED,
+ PreciseDisconnectCause.CDMA_NOT_EMERGENCY,
+ PreciseDisconnectCause.CDMA_ACCESS_BLOCKED,
+ PreciseDisconnectCause.ERROR_UNSPECIFIED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PreciseDisconnectCauses {
+ }
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"RIL_RADIO_TECHNOLOGY_" }, value = {
ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2e04730..4bb237f 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -419,12 +419,33 @@
KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
/**
- * Override the device's configuration for the ImsService to use for this SIM card.
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support both IMS MMTEL and RCS feature functionality instead of the device default
+ * ImsService for this subscription.
+ * @deprecated Use {@link #KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING} and
+ * {@link #KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING} instead to configure these values
+ * separately. If any of those values are not empty, they will override this value.
*/
public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
"config_ims_package_override_string";
/**
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support IMS MMTEL feature functionality instead of the device default ImsService for this
+ * subscription.
+ */
+ public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING =
+ "config_ims_mmtel_package_override_string";
+
+ /**
+ * The package name containing the ImsService that will be bound to the telephony framework to
+ * support IMS RCS feature functionality instead of the device default ImsService for this
+ * subscription.
+ */
+ public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING =
+ "config_ims_rcs_package_override_string";
+
+ /**
* Override the package that will manage {@link SubscriptionPlan}
* information instead of the {@link CarrierService} that defines this
* value.
@@ -1052,9 +1073,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.
- *
- * @deprecated -- this flag no longer does anything. Remove once the new behavior is verified.
- *
* @hide
*/
public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL =
@@ -1837,6 +1855,14 @@
*/
public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
+ /**
+ * Determines whether 1X voice calls is supported for some CDMA carriers.
+ * Default value is true.
+ * @hide
+ */
+ @SystemApi
+ public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL =
+ "support_cdma_1x_voice_calls_bool";
/**
* Boolean indicating if support is provided for directly dialing FDN number from FDN list.
@@ -1949,6 +1975,12 @@
"allow_add_call_during_video_call";
/**
+ * When false, indicates that holding a video call is disabled
+ */
+ public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL =
+ "allow_holding_video_call";
+
+ /**
* When true, indicates that the HD audio icon in the in-call screen should not be shown for
* VoWifi calls.
* @hide
@@ -2092,12 +2124,6 @@
"allow_metered_network_for_cert_download_bool";
/**
- * Carrier specified WiFi networks.
- * @hide
- */
- public static final String KEY_CARRIER_WIFI_STRING_ARRAY = "carrier_wifi_string_array";
-
- /**
* Time delay (in ms) after which we show the notification to switch the preferred
* network.
* @hide
@@ -2185,7 +2211,7 @@
* the start of the next month.
* <p>
* This setting may be still overridden by explicit user choice. By default,
- * the platform value will be used.
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT =
"monthly_data_cycle_day_int";
@@ -2194,10 +2220,7 @@
* When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG},
* or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default
* value will be used for that key.
- *
- * @hide
*/
- @Deprecated
public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1;
/**
@@ -2221,8 +2244,8 @@
* If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will
* be disabled.
* <p>
- * This setting may be overridden by explicit user choice. By default, the platform value
- * will be used.
+ * This setting may be overridden by explicit user choice. By default,
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG =
"data_warning_threshold_bytes_long";
@@ -2230,8 +2253,7 @@
/**
* Controls if the device should automatically notify the user as they reach
* their cellular data warning. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL =
"data_warning_notification_bool";
@@ -2253,8 +2275,8 @@
* phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be
* disabled.
* <p>
- * This setting may be overridden by explicit user choice. By default, the platform value
- * will be used.
+ * This setting may be overridden by explicit user choice. By default,
+ * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
*/
public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG =
"data_limit_threshold_bytes_long";
@@ -2262,8 +2284,7 @@
/**
* Controls if the device should automatically notify the user as they reach
* their cellular data limit. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL =
"data_limit_notification_bool";
@@ -2271,8 +2292,7 @@
/**
* Controls if the device should automatically notify the user when rapid
* cellular data usage is observed. When set to {@code false} the carrier is
- * expected to have implemented their own notification mechanism.
- * @hide
+ * expected to have implemented their own notification mechanism. {@code true} by default.
*/
public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL =
"data_rapid_notification_bool";
@@ -3036,10 +3056,11 @@
"data_switch_validation_timeout_long";
/**
- * GPS configs. See android.hardware.gnss@1.0 IGnssConfiguration.
- * @hide
+ * GPS configs. See the GNSS HAL documentation for more details.
*/
public static final class Gps {
+ private Gps() {}
+
/** Prefix of all Gps.KEY_* constants. */
public static final String KEY_PREFIX = "gps.";
@@ -3079,10 +3100,14 @@
/**
* SUPL server host for SET Initiated & non-ES Network-Initiated SUPL requests.
* Default to supl.google.com
+ * @hide
*/
public static final String KEY_SUPL_HOST_STRING = KEY_PREFIX + "supl_host";
- /** SUPL server port. Default to 7275. */
+ /**
+ * SUPL server port. Default to 7275.
+ * @hide
+ */
public static final String KEY_SUPL_PORT_STRING = KEY_PREFIX + "supl_port";
/**
@@ -3090,6 +3115,7 @@
* with bits 0:7 representing a service indicator field, bits 8:15
* representing the minor version and bits 16:23 representing the
* major version. Default to 0x20000.
+ * @hide
*/
public static final String KEY_SUPL_VER_STRING = KEY_PREFIX + "supl_ver";
@@ -3097,6 +3123,7 @@
* SUPL_MODE configuration bit mask
* 1 - Mobile Station Based. This is default.
* 2 - Mobile Station Assisted.
+ * @hide
*/
public static final String KEY_SUPL_MODE_STRING = KEY_PREFIX + "supl_mode";
@@ -3105,6 +3132,7 @@
* (e.g. E911), and SUPL non-ES requests to only outside of non user emergency sessions.
* 0 - no.
* 1 - yes. This is default.
+ * @hide
*/
public static final String KEY_SUPL_ES_STRING = KEY_PREFIX + "supl_es";
@@ -3113,6 +3141,7 @@
* 0 - Radio Resource Location Protocol in user plane and control plane. This is default.
* 1 - Enable LTE Positioning Protocol in user plane.
* 2 - Enable LTE Positioning Protocol in control plane.
+ * @hide
*/
public static final String KEY_LPP_PROFILE_STRING = KEY_PREFIX + "lpp_profile";
@@ -3120,6 +3149,7 @@
* Determine whether to use emergency PDN for emergency SUPL.
* 0 - no.
* 1 - yes. This is default.
+ * @hide
*/
public static final String KEY_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL_STRING =
KEY_PREFIX + "use_emergency_pdn_for_emergency_supl";
@@ -3130,6 +3160,7 @@
* 1 - Use A-GLONASS in Radio Resource Control(RRC) control-plane.
* 2 - Use A-GLONASS in Radio Resource Location user-plane.
* 4 - Use A-GLONASS in LTE Positioning Protocol User plane.
+ * @hide
*/
public static final String KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING =
KEY_PREFIX + "a_glonass_pos_protocol_select";
@@ -3141,11 +3172,13 @@
* "1" - Lock Mobile Originated GPS functionalities.
* "2" - Lock Network initiated GPS functionalities.
* "3" - Lock both. This is default.
+ * @hide
*/
public static final String KEY_GPS_LOCK_STRING = KEY_PREFIX + "gps_lock";
/**
* Control Plane / SUPL NI emergency extension time in seconds. Default to "0".
+ * @hide
*/
public static final String KEY_ES_EXTENSION_SEC_STRING = KEY_PREFIX + "es_extension_sec";
@@ -3154,6 +3187,7 @@
* the non-framework entities requesting location directly from GNSS without involving
* the framework, as managed by IGnssVisibilityControl.hal. For example,
* "com.example.mdt com.example.ims".
+ * @hide
*/
public static final String KEY_NFW_PROXY_APPS_STRING = KEY_PREFIX + "nfw_proxy_apps";
@@ -3174,6 +3208,18 @@
public static final String KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT = KEY_PREFIX
+ "es_supl_control_plane_support_int";
+ /**
+ * A list of roaming PLMNs where SUPL ES mode does not support a control-plane mechanism to
+ * get a user's location in the event that data plane SUPL fails or is otherwise
+ * unavailable.
+ * <p>
+ * A string array of PLMNs that do not support a control-plane mechanism for getting a
+ * user's location for SUPL ES.
+ * @hide
+ */
+ public static final String KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY =
+ KEY_PREFIX + "es_supl_data_plane_only_roaming_plmn_string_array";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, true);
@@ -3190,63 +3236,11 @@
defaults.putString(KEY_NFW_PROXY_APPS_STRING, "");
defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
SUPL_EMERGENCY_MODE_TYPE_CP_ONLY);
+ defaults.putStringArray(KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY, null);
return defaults;
}
}
- /**
- * Wi-Fi configs used in Carrier Wi-Fi application.
- *
- * @hide
- */
- @SystemApi
- public static final class Wifi {
- /** Prefix of all Wifi.KEY_* constants. */
- public static final String KEY_PREFIX = "wifi.";
-
- /**
- * Whenever any information under wifi namespace is changed, the version should be
- * incremented by 1 so that the device is able to figure out the latest profiles based on
- * the version.
- */
- public static final String KEY_CARRIER_PROFILES_VERSION_INT =
- KEY_PREFIX + "carrier_profiles_version_int";
-
- /**
- * It contains the package name of connection manager that the carrier owns.
- *
- * <P>Once it is installed, the profiles installed by Carrier Wi-Fi Application
- * will be deleted.
- * Once it is uninstalled, Carrier Wi-Fi Application will re-install the latest profiles.
- */
- public static final String KEY_CARRIER_CONNECTION_MANAGER_PACKAGE_STRING =
- KEY_PREFIX + "carrier_connection_manager_package_string";
- /**
- * It is to have the list of wifi networks profiles which contain the information about
- * the wifi-networks to which carrier wants the device to connect.
- */
- public static final String KEY_NETWORK_PROFILES_STRING_ARRAY =
- KEY_PREFIX + "network_profiles_string_array";
-
- /**
- * It is to have the list of Passpoint profiles which contain the information about
- * the Passpoint networks to which carrier wants the device to connect.
- */
- public static final String KEY_PASSPOINT_PROFILES_STRING_ARRAY =
- KEY_PREFIX + "passpoint_profiles_string_array";
-
- private static PersistableBundle getDefaults() {
- PersistableBundle defaults = new PersistableBundle();
- defaults.putInt(KEY_CARRIER_PROFILES_VERSION_INT, -1);
- defaults.putString(KEY_CARRIER_CONNECTION_MANAGER_PACKAGE_STRING, null);
- defaults.putStringArray(KEY_NETWORK_PROFILES_STRING_ARRAY, null);
- defaults.putStringArray(KEY_PASSPOINT_PROFILES_STRING_ARRAY, null);
- return defaults;
- }
-
- private Wifi() {}
- }
-
/**
* An int array containing CDMA enhanced roaming indicator values for Home (non-roaming) network.
* The default values come from 3GPP2 C.R1001 table 8.1-1.
@@ -3540,6 +3534,8 @@
sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+ sDefaults.putString(KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING, null);
+ sDefaults.putString(KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING, null);
sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
@@ -3548,7 +3544,7 @@
sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
- sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, true);
+ sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
@@ -3634,6 +3630,7 @@
sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false);
sDefaults.putInt(
KEY_CDMA_ROAMING_MODE_INT, TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
+ sDefaults.putBoolean(KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL, true);
sDefaults.putString(KEY_RCS_CONFIG_SERVER_URL_STRING, "");
// Carrier Signalling Receivers
@@ -3683,6 +3680,7 @@
sDefaults.putBoolean(KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL, true);
sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true);
+ sDefaults.putBoolean(KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL, true);
sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true);
sDefaults.putBoolean(KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO, false);
@@ -3699,7 +3697,6 @@
sDefaults.putBoolean(KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_DIGITS_HELPER_TEXT_ON_STK_INPUT_SCREEN_BOOL, true);
- sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null);
sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1);
sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1);
sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
@@ -3828,7 +3825,6 @@
/* Default value is 60 seconds. */
sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG, 60000);
sDefaults.putAll(Gps.getDefaults());
- sDefaults.putAll(Wifi.getDefaults());
sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
new int[] {
1 /* Roaming Indicator Off */
@@ -3913,6 +3909,34 @@
@SystemApi
@TestApi
public void overrideConfig(int subscriptionId, @Nullable PersistableBundle overrideValues) {
+ overrideConfig(subscriptionId, overrideValues, false);
+ }
+
+ /**
+ * Overrides the carrier config of the provided subscription ID with the provided values.
+ *
+ * Any further queries to carrier config from any process will return the overridden values
+ * after this method returns. The overrides are effective until the user passes in {@code null}
+ * for {@code overrideValues}. This removes all previous overrides and sets the carrier config
+ * back to production values.
+ *
+ * The overrides is stored persistently and will survive a reboot if {@code persistent} is true.
+ *
+ * May throw an {@link IllegalArgumentException} if {@code overrideValues} contains invalid
+ * values for the specified config keys.
+ *
+ * NOTE: This API is meant for testing purposes only.
+ *
+ * @param subscriptionId The subscription ID for which the override should be done.
+ * @param overrideValues Key-value pairs of the values that are to be overridden. If set to
+ * {@code null}, this will remove all previous overrides and set the
+ * carrier configuration back to production values.
+ * @param persistent Determines whether the override should be persistent.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void overrideConfig(int subscriptionId, @Nullable PersistableBundle overrideValues,
+ boolean persistent) {
try {
ICarrierConfigLoader loader = getICarrierConfigLoader();
if (loader == null) {
@@ -3920,7 +3944,7 @@
+ " ICarrierConfigLoader is null");
return;
}
- loader.overrideConfig(subscriptionId, overrideValues);
+ loader.overrideConfig(subscriptionId, overrideValues, persistent);
} catch (RemoteException ex) {
Rlog.e(TAG, "Error setting config for subId " + subscriptionId + ": "
+ ex.toString());
@@ -4026,13 +4050,28 @@
}
}
- /** {@hide} */
+ /**
+ * Gets the package name for a default carrier service.
+ * @return the package name for a default carrier service; empty string if not available.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDefaultCarrierServicePackageName() {
try {
- return getICarrierConfigLoader().getDefaultCarrierServicePackageName();
- } catch (Throwable t) {
- return null;
+ ICarrierConfigLoader loader = getICarrierConfigLoader();
+ if (loader == null) {
+ Rlog.w(TAG, "getDefaultCarrierServicePackageName ICarrierConfigLoader is null");
+ return "";
+ }
+ return loader.getDefaultCarrierServicePackageName();
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getDefaultCarrierServicePackageName ICarrierConfigLoader is null"
+ + ex.toString());
}
+ return "";
}
/**
diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java
index ce5e3f3..84be4e8 100644
--- a/telephony/java/android/telephony/CbGeoUtils.java
+++ b/telephony/java/android/telephony/CbGeoUtils.java
@@ -18,19 +18,24 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.os.Build;
import android.text.TextUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
+
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
-
/**
- * This utils class is specifically used for geo-targeting of CellBroadcast messages.
+ * This utils class is used for geo-fencing of CellBroadcast messages and is used by the cell
+ * broadcast module.
+ *
* The coordinates used by this utils class are latitude and longitude, but some algorithms in this
* class only use them as coordinates on plane, so the calculation will be inaccurate. So don't use
* this class for anything other then geo-targeting of cellbroadcast messages.
+ *
+ * More information regarding cell broadcast geo-fencing logic is laid out in 3GPP TS 23.041 and
+ * ATIS-0700041.
* @hide
*/
@SystemApi
@@ -81,7 +86,7 @@
/** @hide */
private static final String POLYGON_SYMBOL = "polygon";
- /** Point represent by (latitude, longitude). */
+ /** A point represented by (latitude, longitude). */
public static class LatLng {
public final double lat;
public final double lng;
@@ -97,8 +102,8 @@
}
/**
- * @param p the point use to calculate the subtraction result.
- * @return the result of this point subtract the given point {@code p}.
+ * @param p the point to subtract
+ * @return the result of the subtraction
*/
@NonNull
public LatLng subtract(@NonNull LatLng p) {
@@ -106,9 +111,9 @@
}
/**
- * Calculate the distance in meter between this point and the given point {@code p}.
- * @param p the point use to calculate the distance.
- * @return the distance in meter.
+ * Calculate the distance in meters between this point and the given point {@code p}.
+ * @param p the point used to calculate the distance.
+ * @return the distance in meters.
*/
public double distance(@NonNull LatLng p) {
double dlat = Math.sin(0.5 * Math.toRadians(lat - p.lat));
@@ -125,8 +130,9 @@
}
/**
- * The class represents a simple polygon with at least 3 points.
- * @hide
+ * A class representing a simple polygon with at least 3 points. This is used for geo-fencing
+ * logic with cell broadcasts. More information regarding cell broadcast geo-fencing logic is
+ * laid out in 3GPP TS 23.041 and ATIS-0700041.
*/
public static class Polygon implements Geometry {
/**
@@ -145,7 +151,7 @@
* connected to form an edge of the polygon. The polygon has at least 3 vertices, and the
* last vertices and the first vertices must be adjacent.
*
- * The longitude difference in the vertices should be less than 180 degree.
+ * The longitude difference in the vertices should be less than 180 degrees.
*/
public Polygon(@NonNull List<LatLng> vertices) {
mVertices = vertices;
@@ -164,19 +170,24 @@
.collect(Collectors.toList());
}
- public List<LatLng> getVertices() {
+ /**
+ * Return the list of vertices which compose the polygon.
+ */
+ public @NonNull List<LatLng> getVertices() {
return mVertices;
}
/**
- * Check if the given point {@code p} is inside the polygon. This method counts the number
- * of times the polygon winds around the point P, A.K.A "winding number". The point is
- * outside only when this "winding number" is 0.
+ * Check if the given LatLng is inside the polygon.
*
- * If a point is on the edge of the polygon, it is also considered to be inside the polygon.
+ * If a LatLng is on the edge of the polygon, it is also considered to be inside the
+ * polygon.
*/
@Override
- public boolean contains(LatLng latLng) {
+ public boolean contains(@NonNull LatLng latLng) {
+ // This method counts the number of times the polygon winds around the point P, A.K.A
+ // "winding number". The point is outside only when this "winding number" is 0.
+
Point p = convertAndScaleLatLng(latLng);
int n = mScaledVertices.size();
@@ -245,6 +256,7 @@
return a.x * b.y - a.y * b.x;
}
+ /** @hide */
static final class Point {
public final double x;
public final double y;
@@ -262,7 +274,7 @@
@Override
public String toString() {
String str = "Polygon: ";
- if (Build.IS_DEBUGGABLE) {
+ if (TelephonyUtils.IS_DEBUGGABLE) {
str += mVertices;
}
return str;
@@ -270,35 +282,53 @@
}
/**
- * The class represents a circle.
- * @hide
+ * A class represents a {@link Geometry} in the shape of a Circle. This is used for handling
+ * geo-fenced cell broadcasts. More information regarding cell broadcast geo-fencing logic is
+ * laid out in 3GPP TS 23.041 and ATIS-0700041.
*/
public static class Circle implements Geometry {
private final LatLng mCenter;
private final double mRadiusMeter;
- public Circle(LatLng center, double radiusMeter) {
+ /**
+ * Construct a Circle given a center point and a radius in meters.
+ *
+ * @param center the latitude and longitude of the center of the circle
+ * @param radiusInMeters the radius of the circle in meters
+ */
+ public Circle(@NonNull LatLng center, double radiusInMeters) {
this.mCenter = center;
- this.mRadiusMeter = radiusMeter;
+ this.mRadiusMeter = radiusInMeters;
}
- public LatLng getCenter() {
+ /**
+ * Return the latitude and longitude of the center of the circle;
+ */
+ public @NonNull LatLng getCenter() {
return mCenter;
}
+ /**
+ * Return the radius of the circle in meters.
+ */
public double getRadius() {
return mRadiusMeter;
}
+ /**
+ * Check if the given LatLng is inside the circle.
+ *
+ * If a LatLng is on the edge of the circle, it is also considered to be inside the circle.
+ */
@Override
- public boolean contains(LatLng p) {
- return mCenter.distance(p) <= mRadiusMeter;
+ public boolean contains(@NonNull LatLng latLng) {
+ return mCenter.distance(latLng) <= mRadiusMeter;
}
@Override
public String toString() {
String str = "Circle: ";
- if (Build.IS_DEBUGGABLE) {
+ if (TelephonyUtils.IS_DEBUGGABLE) {
str += mCenter + ", radius = " + mRadiusMeter;
}
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index b7dab16..e523fba 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -19,6 +19,7 @@
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -181,7 +182,8 @@
* @return a CellLocation object for this CellIdentity
* @hide
*/
- public abstract CellLocation asCellLocation();
+ @SystemApi
+ public abstract @NonNull CellLocation asCellLocation();
@Override
public boolean equals(Object other) {
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index 880d3db..54236b42 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -16,6 +16,7 @@
package android.telephony;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.telephony.cdma.CdmaCellLocation;
@@ -198,6 +199,7 @@
}
/** @hide */
+ @NonNull
@Override
public CdmaCellLocation asCellLocation() {
CdmaCellLocation cl = new CdmaCellLocation();
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 25c6577..4e4454d 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -16,6 +16,7 @@
package android.telephony;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
@@ -200,6 +201,7 @@
}
/** @hide */
+ @NonNull
@Override
public GsmCellLocation asCellLocation() {
GsmCellLocation cl = new GsmCellLocation();
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 997b19f..c3fc73b 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -16,6 +16,7 @@
package android.telephony;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -232,6 +233,7 @@
*
* @hide
*/
+ @NonNull
@Override
public GsmCellLocation asCellLocation() {
GsmCellLocation cl = new GsmCellLocation();
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index edc838c..e3fec7b 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -17,6 +17,7 @@
package android.telephony;
import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
@@ -77,6 +78,7 @@
* @return a CellLocation object for this CellIdentity.
* @hide
*/
+ @NonNull
@Override
public CellLocation asCellLocation() {
return new GsmCellLocation();
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 558e346..8f812b6 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -171,6 +171,7 @@
}
/** @hide */
+ @NonNull
@Override
public GsmCellLocation asCellLocation() {
GsmCellLocation cl = new GsmCellLocation();
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 031fed1..556bc32 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -16,6 +16,7 @@
package android.telephony;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
@@ -196,6 +197,7 @@
}
/** @hide */
+ @NonNull
@Override
public GsmCellLocation asCellLocation() {
GsmCellLocation cl = new GsmCellLocation();
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 7bdf1f5..e1c4bef 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -950,14 +950,10 @@
public static final int UNKNOWN = 0x10000;
/** Data fail due to radio not unavailable. */
public static final int RADIO_NOT_AVAILABLE = 0x10001; /* no retry */
- /** @hide */
+ /** Data fail due to unacceptable network parameter. */
public static final int UNACCEPTABLE_NETWORK_PARAMETER = 0x10002; /* no retry */
- /** @hide */
- public static final int CONNECTION_TO_DATACONNECTIONAC_BROKEN = 0x10003;
/** Data connection was lost. */
public static final int LOST_CONNECTION = 0x10004;
- /** @hide */
- public static final int RESET_BY_FRAMEWORK = 0x10005;
/**
* Data handover failed.
@@ -1361,10 +1357,7 @@
sFailCauseMap.put(RADIO_NOT_AVAILABLE, "RADIO_NOT_AVAILABLE");
sFailCauseMap.put(UNACCEPTABLE_NETWORK_PARAMETER,
"UNACCEPTABLE_NETWORK_PARAMETER");
- sFailCauseMap.put(CONNECTION_TO_DATACONNECTIONAC_BROKEN,
- "CONNECTION_TO_DATACONNECTIONAC_BROKEN");
sFailCauseMap.put(LOST_CONNECTION, "LOST_CONNECTION");
- sFailCauseMap.put(RESET_BY_FRAMEWORK, "RESET_BY_FRAMEWORK");
}
private DataFailCause() {
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 02d8be3..39af34c 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -16,7 +16,10 @@
package android.telephony.ims;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.Context;
import android.telephony.SubscriptionManager;
@@ -25,12 +28,15 @@
*
* @hide
*/
+@SystemApi
+@TestApi
@SystemService(Context.TELEPHONY_IMS_SERVICE)
public class ImsManager {
private Context mContext;
- public ImsManager(Context context) {
+ /** @hide */
+ public ImsManager(@NonNull Context context) {
mContext = context;
}
@@ -41,6 +47,7 @@
* @throws IllegalArgumentException if the subscription is invalid.
* @return a ImsRcsManager instance with the specific subscription ID.
*/
+ @NonNull
public ImsRcsManager getImsRcsManager(int subscriptionId) {
if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId);
@@ -56,6 +63,7 @@
* @throws IllegalArgumentException if the subscription is invalid.
* @return a ImsMmTelManager instance with the specific subscription ID.
*/
+ @NonNull
public ImsMmTelManager getImsMmTelManager(int subscriptionId) {
if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId);
diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java
index fe273b2..95aa101 100644
--- a/telephony/java/android/telephony/LocationAccessPolicy.java
+++ b/telephony/java/android/telephony/LocationAccessPolicy.java
@@ -34,6 +34,8 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.internal.telephony.util.TelephonyUtils;
+
import java.util.List;
/**
@@ -174,7 +176,7 @@
}
Log.e(TAG, errorMsg);
try {
- if (Build.IS_DEBUGGABLE) {
+ if (TelephonyUtils.IS_DEBUGGABLE) {
Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show();
}
} catch (Throwable t) {
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index 16fbae2..aebe780 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -205,10 +205,10 @@
}
/**
+ * Indicate if the ModemActivityInfo is invalid due to modem's invalid reporting.
+ *
* @return {@code true} if this {@link ModemActivityInfo} record is valid,
* {@code false} otherwise.
- *
- * @hide
*/
public boolean isValid() {
for (TransmitPower powerInfo : getTransmitPowerInfo()) {
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index 465b6aa..c8b8ffb 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -20,10 +20,10 @@
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
/**
* Defines a request to peform a network scan.
@@ -221,9 +221,15 @@
private NetworkScanRequest(Parcel in) {
mScanType = in.readInt();
- mSpecifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
- Object.class.getClassLoader(),
- RadioAccessSpecifier.class);
+ Parcelable[] tempSpecifiers = in.readParcelableArray(Object.class.getClassLoader());
+ if (tempSpecifiers != null) {
+ mSpecifiers = new RadioAccessSpecifier[tempSpecifiers.length];
+ for (int i = 0; i < tempSpecifiers.length; i++) {
+ mSpecifiers[i] = (RadioAccessSpecifier) tempSpecifiers[i];
+ }
+ } else {
+ mSpecifiers = null;
+ }
mSearchPeriodicity = in.readInt();
mMaxSearchTime = in.readInt();
mIncrementalResults = in.readBoolean();
diff --git a/telephony/java/android/telephony/PhoneNumberRange.java b/telephony/java/android/telephony/PhoneNumberRange.java
index e6f107e..2b199d2 100644
--- a/telephony/java/android/telephony/PhoneNumberRange.java
+++ b/telephony/java/android/telephony/PhoneNumberRange.java
@@ -85,18 +85,18 @@
}
private PhoneNumberRange(Parcel in) {
- mCountryCode = in.readStringNoHelper();
- mPrefix = in.readStringNoHelper();
- mLowerBound = in.readStringNoHelper();
- mUpperBound = in.readStringNoHelper();
+ mCountryCode = in.readString();
+ mPrefix = in.readString();
+ mLowerBound = in.readString();
+ mUpperBound = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeStringNoHelper(mCountryCode);
- dest.writeStringNoHelper(mPrefix);
- dest.writeStringNoHelper(mLowerBound);
- dest.writeStringNoHelper(mUpperBound);
+ dest.writeString(mCountryCode);
+ dest.writeString(mPrefix);
+ dest.writeString(mLowerBound);
+ dest.writeString(mUpperBound);
}
@Override
diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java
index 9f75332..bfa6326 100644
--- a/telephony/java/android/telephony/PreciseCallState.java
+++ b/telephony/java/android/telephony/PreciseCallState.java
@@ -16,19 +16,18 @@
package android.telephony;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.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.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -73,19 +72,26 @@
private @PreciseCallStates int mRingingCallState = PRECISE_CALL_STATE_NOT_VALID;
private @PreciseCallStates int mForegroundCallState = PRECISE_CALL_STATE_NOT_VALID;
private @PreciseCallStates int mBackgroundCallState = PRECISE_CALL_STATE_NOT_VALID;
- private int mDisconnectCause = DisconnectCause.NOT_VALID;
- private int mPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID;
+ private @DisconnectCauses int mDisconnectCause = DisconnectCause.NOT_VALID;
+ private @PreciseDisconnectCauses int mPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID;
/**
- * Constructor
+ * Construct PreciseCallState with parameters
+ *
+ * @param ringingCall ring call state
+ * @param foregroundCall foreground call state
+ * @param backgroundCall background call state
+ * @param disconnectCause disconnect cause
+ * @param preciseDisconnectCause precise disconnect cause
*
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public PreciseCallState(@PreciseCallStates int ringingCall,
@PreciseCallStates int foregroundCall,
- @PreciseCallStates int backgroundCall, int disconnectCause,
- int preciseDisconnectCause) {
+ @PreciseCallStates int backgroundCall,
+ @DisconnectCauses int disconnectCause,
+ @PreciseDisconnectCauses int preciseDisconnectCause) {
mRingingCallState = ringingCall;
mForegroundCallState = foregroundCall;
mBackgroundCallState = backgroundCall;
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 257d634..78ad5c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -19,8 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.net.LinkProperties;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.ApnType;
@@ -29,6 +31,8 @@
import android.telephony.Annotation.NetworkType;
import android.telephony.data.ApnSetting;
+import dalvik.system.VMRuntime;
+
import java.util.Objects;
@@ -46,35 +50,62 @@
* <li>Data connection fail cause.
* </ul>
*
- * @hide
*/
-@SystemApi
public final class PreciseDataConnectionState implements Parcelable {
private @DataState int mState = TelephonyManager.DATA_UNKNOWN;
private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
private @DataFailureCause int mFailCause = DataFailCause.NONE;
- private @ApnType int mAPNTypes = ApnSetting.TYPE_NONE;
- private String mAPN = "";
+ private @ApnType int mApnTypes = ApnSetting.TYPE_NONE;
+ private String mApn = "";
private LinkProperties mLinkProperties = null;
+ private ApnSetting mApnSetting = null;
/**
* Constructor
*
+ * @deprecated this constructor has been superseded and should not be used.
* @hide
*/
- @UnsupportedAppUsage
+ @TestApi
+ @Deprecated
+ @UnsupportedAppUsage // (maxTargetSdk = Build.VERSION_CODES.Q)
+ // FIXME: figure out how to remove the UnsupportedAppUsage and delete this constructor
public PreciseDataConnectionState(@DataState int state,
@NetworkType int networkType,
- @ApnType int apnTypes, String apn,
- LinkProperties linkProperties,
+ @ApnType int apnTypes, @NonNull String apn,
+ @Nullable LinkProperties linkProperties,
@DataFailureCause int failCause) {
+ this(state, networkType, apnTypes, apn, linkProperties, failCause, null);
+ }
+
+
+ /**
+ * Constructor
+ *
+ * @param state the state of the data connection
+ * @param networkType the access network that is/would carry this data connection
+ * @param apnTypes the APN types that this data connection carries
+ * @param apnSetting if there is a valid APN for this Data Connection, then the APN Settings;
+ * if there is no valid APN setting for the specific type, then this will be null
+ * @param linkProperties if the data connection is connected, the properties of the connection
+ * @param failCause in case a procedure related to this data connection fails, a non-zero error
+ * code indicating the cause of the failure.
+ * @hide
+ */
+ public PreciseDataConnectionState(@DataState int state,
+ @NetworkType int networkType,
+ @ApnType int apnTypes, @NonNull String apn,
+ @Nullable LinkProperties linkProperties,
+ @DataFailureCause int failCause,
+ @Nullable ApnSetting apnSetting) {
mState = state;
mNetworkType = networkType;
- mAPNTypes = apnTypes;
- mAPN = apn;
+ mApnTypes = apnTypes;
+ mApn = apn;
mLinkProperties = linkProperties;
mFailCause = failCause;
+ mApnSetting = apnSetting;
}
/**
@@ -93,76 +124,160 @@
private PreciseDataConnectionState(Parcel in) {
mState = in.readInt();
mNetworkType = in.readInt();
- mAPNTypes = in.readInt();
- mAPN = in.readString();
- mLinkProperties = (LinkProperties)in.readParcelable(null);
+ mApnTypes = in.readInt();
+ mApn = in.readString();
+ mLinkProperties = (LinkProperties) in.readParcelable(null);
mFailCause = in.readInt();
+ mApnSetting = (ApnSetting) in.readParcelable(null);
}
/**
* Returns the state of data connection that supported the apn types returned by
* {@link #getDataConnectionApnTypeBitMask()}
+ *
+ * @deprecated use {@link #getState()}
+ * @hide
*/
+ @Deprecated
+ @SystemApi
public @DataState int getDataConnectionState() {
+ if (mState == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ return TelephonyManager.DATA_CONNECTED;
+ }
+
+ return mState;
+ }
+
+ /**
+ * Returns the high-level state of this data connection.
+ */
+ public @DataState int getState() {
return mState;
}
/**
* Returns the network type associated with this data connection.
+ *
+ * @deprecated use {@link getNetworkType()}
* @hide
*/
+ @Deprecated
+ @SystemApi
public @NetworkType int getDataConnectionNetworkType() {
return mNetworkType;
}
/**
- * Returns the data connection APN types supported by this connection and triggers
- * {@link PreciseDataConnectionState} change.
+ * Returns the network type associated with this data connection.
+ *
+ * Return the current/latest (radio) bearer technology that carries this data connection.
+ * For a variety of reasons, the network type can change during the life of the data
+ * connection, and this information is not reliable unless the physical link is currently
+ * active; (there is currently no mechanism to know whether the physical link is active at
+ * any given moment). Thus, this value is generally correct but may not be relied-upon to
+ * represent the status of the radio bearer at any given moment.
*/
- public @ApnType int getDataConnectionApnTypeBitMask() {
- return mAPNTypes;
+ public @NetworkType int getNetworkType() {
+ return mNetworkType;
}
/**
- * Returns APN {@link ApnSetting} of this data connection.
+ * Returns the APN types mapped to this data connection.
+ *
+ * @deprecated use {@link #getApnSetting()}
+ * @hide
*/
- @Nullable
+ @Deprecated
+ @SystemApi
+ public @ApnType int getDataConnectionApnTypeBitMask() {
+ return mApnTypes;
+ }
+
+ /**
+ * Returns APN of this data connection.
+ *
+ * @deprecated use {@link #getApnSetting()}
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @Deprecated
public String getDataConnectionApn() {
- return mAPN;
+ return mApn;
}
/**
* Get the properties of the network link {@link LinkProperties}.
+ *
+ * @deprecated use {@link #getLinkProperties()}
* @hide
*/
- @UnsupportedAppUsage
+ @Deprecated
+ @SystemApi
+ @Nullable
public LinkProperties getDataConnectionLinkProperties() {
return mLinkProperties;
}
/**
- * Returns data connection fail cause, in case there was a failure.
+ * Get the properties of the network link {@link LinkProperties}.
*/
- public @Annotation.DataFailureCause int getDataConnectionFailCause() {
+ @Nullable
+ public LinkProperties getLinkProperties() {
+ return mLinkProperties;
+ }
+
+ /**
+ * Returns the cause code generated by the most recent state change.
+ *
+ * @deprecated use {@link #getLastCauseCode()}
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ public int getDataConnectionFailCause() {
return mFailCause;
}
+ /**
+ * Returns the cause code generated by the most recent state change.
+ *
+ * Return the cause code for the most recent change in {@link #getState}. In the event of an
+ * error, this cause code will be non-zero.
+ */
+ // FIXME(b144774287): some of these cause codes should have a prescribed meaning.
+ public int getLastCauseCode() {
+ return mFailCause;
+ }
+
+ /**
+ * Return the APN Settings for this data connection.
+ *
+ * Returns the ApnSetting that was used to configure this data connection.
+ */
+ // FIXME: This shouldn't be nullable; update once the ApnSetting is supplied correctly
+ @Nullable ApnSetting getApnSetting() {
+ return mApnSetting;
+ }
+
@Override
public int describeContents() {
return 0;
}
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(mState);
out.writeInt(mNetworkType);
- out.writeInt(mAPNTypes);
- out.writeString(mAPN);
+ out.writeInt(mApnTypes);
+ out.writeString(mApn);
out.writeParcelable(mLinkProperties, flags);
out.writeInt(mFailCause);
+ out.writeParcelable(mApnSetting, flags);
}
- public static final @android.annotation.NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
+ public static final @NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
= new Parcelable.Creator<PreciseDataConnectionState>() {
public PreciseDataConnectionState createFromParcel(Parcel in) {
@@ -176,8 +291,8 @@
@Override
public int hashCode() {
- return Objects.hash(mState, mNetworkType, mAPNTypes, mAPN, mLinkProperties,
- mFailCause);
+ return Objects.hash(mState, mNetworkType, mApnTypes, mApn, mLinkProperties,
+ mFailCause, mApnSetting);
}
@Override
@@ -188,11 +303,12 @@
}
PreciseDataConnectionState other = (PreciseDataConnectionState) obj;
- return Objects.equals(mAPN, other.mAPN) && mAPNTypes == other.mAPNTypes
+ return Objects.equals(mApn, other.mApn) && mApnTypes == other.mApnTypes
&& mFailCause == other.mFailCause
&& Objects.equals(mLinkProperties, other.mLinkProperties)
&& mNetworkType == other.mNetworkType
- && mState == other.mState;
+ && mState == other.mState
+ && Objects.equals(mApnSetting, other.mApnSetting);
}
@NonNull
@@ -202,10 +318,11 @@
sb.append("Data Connection state: " + mState);
sb.append(", Network type: " + mNetworkType);
- sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mAPNTypes));
- sb.append(", APN: " + mAPN);
+ sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mApnTypes));
+ sb.append(", APN: " + mApn);
sb.append(", Link properties: " + mLinkProperties);
sb.append(", Fail cause: " + DataFailCause.toString(mFailCause));
+ sb.append(", Apn Setting: " + mApnSetting);
return sb.toString();
}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index d2c8517..3f065f8 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -65,6 +65,13 @@
static final boolean DBG = false;
static final boolean VDBG = false; // STOPSHIP if true
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "STATE_",
+ value = {STATE_IN_SERVICE, STATE_OUT_OF_SERVICE, STATE_EMERGENCY_ONLY,
+ STATE_POWER_OFF})
+ public @interface RegState {}
+
/**
* Normal operation condition, the phone is registered
* with an operator either in home network or in roaming.
@@ -83,6 +90,7 @@
/**
* The phone is registered and locked. Only emergency numbers are allowed. {@more}
*/
+ //TODO: This state is not used anymore. It should be deprecated in a future release.
public static final int STATE_EMERGENCY_ONLY =
TelephonyProtoEnums.SERVICE_STATE_EMERGENCY_ONLY; // 2
@@ -501,13 +509,15 @@
}
/**
- * Get current data service state
+ * Get current data registration state.
*
* @see #STATE_IN_SERVICE
* @see #STATE_OUT_OF_SERVICE
* @see #STATE_EMERGENCY_ONLY
* @see #STATE_POWER_OFF
*
+ * @return current data registration state {@link RegState}
+ *
* @hide
*/
@UnsupportedAppUsage
@@ -516,6 +526,23 @@
}
/**
+ * Get current data registration state.
+ *
+ * @see #STATE_IN_SERVICE
+ * @see #STATE_OUT_OF_SERVICE
+ * @see #STATE_EMERGENCY_ONLY
+ * @see #STATE_POWER_OFF
+ *
+ * @return current data registration state {@link RegState}
+ *
+ * @hide
+ */
+ @SystemApi
+ public @RegState int getDataRegistrationState() {
+ return getDataRegState();
+ }
+
+ /**
* Get the current duplex mode
*
* @see #DUPLEX_MODE_UNKNOWN
@@ -941,7 +968,7 @@
rtString = "LTE_CA";
break;
case RIL_RADIO_TECHNOLOGY_NR:
- rtString = "LTE_NR";
+ rtString = "NR_SA";
break;
default:
rtString = "Unexpected";
@@ -1408,7 +1435,15 @@
return getRilDataRadioTechnology();
}
- /** @hide */
+ /**
+ * Transform RIL radio technology {@link RilRadioTechnology} value to Network
+ * type {@link NetworkType}.
+ *
+ * @param rat The RIL radio technology {@link RilRadioTechnology}.
+ * @return The network type {@link NetworkType}.
+ *
+ * @hide
+ */
public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rat) {
switch(rat) {
case RIL_RADIO_TECHNOLOGY_GPRS:
@@ -1479,8 +1514,9 @@
return AccessNetworkType.CDMA2000;
case RIL_RADIO_TECHNOLOGY_LTE:
case RIL_RADIO_TECHNOLOGY_LTE_CA:
- case RIL_RADIO_TECHNOLOGY_NR:
return AccessNetworkType.EUTRAN;
+ case RIL_RADIO_TECHNOLOGY_NR:
+ return AccessNetworkType.NGRAN;
case RIL_RADIO_TECHNOLOGY_IWLAN:
return AccessNetworkType.IWLAN;
case RIL_RADIO_TECHNOLOGY_UNKNOWN:
@@ -1489,7 +1525,15 @@
}
}
- /** @hide */
+ /**
+ * Transform network type {@link NetworkType} value to RIL radio technology
+ * {@link RilRadioTechnology}.
+ *
+ * @param networkType The network type {@link NetworkType}.
+ * @return The RIL radio technology {@link RilRadioTechnology}.
+ *
+ * @hide
+ */
public static int networkTypeToRilRadioTechnology(int networkType) {
switch(networkType) {
case TelephonyManager.NETWORK_TYPE_GPRS:
@@ -1690,7 +1734,14 @@
return bearerBitmask;
}
- /** @hide */
+ /**
+ * Convert network type bitmask to bearer bitmask.
+ *
+ * @param networkTypeBitmask The network type bitmask value
+ * @return The bearer bitmask value.
+ *
+ * @hide
+ */
public static int convertNetworkTypeBitmaskToBearerBitmask(int networkTypeBitmask) {
if (networkTypeBitmask == 0) {
return 0;
@@ -1704,7 +1755,14 @@
return bearerBitmask;
}
- /** @hide */
+ /**
+ * Convert bearer bitmask to network type bitmask.
+ *
+ * @param bearerBitmask The bearer bitmask value.
+ * @return The network type bitmask value.
+ *
+ * @hide
+ */
public static int convertBearerBitmaskToNetworkTypeBitmask(int bearerBitmask) {
if (bearerBitmask == 0) {
return 0;
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 37f3388..cab5286 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1998,6 +1998,27 @@
}
}
+ /**
+ * Gets the total capacity of SMS storage on RUIM and SIM cards
+ *
+ * @return the total capacity count of SMS on RUIM and SIM cards
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getSmsCapacityOnIcc() {
+ int ret = 0;
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ ret = iccISms.getSmsCapacityOnIccForSubscriber(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ //ignore it
+ }
+ return ret;
+ }
+
// see SmsMessage.getStatusOnIcc
/** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index b705d71..392d3eb 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -344,10 +344,12 @@
* @param use7bitOnly if true, characters that are not part of the radio-specific 7-bit encoding
* are counted as single space chars. If false, and if the messageBody contains non-7-bit
* encodable characters, length is calculated using a 16-bit encoding.
- * @return an int[4] with int[0] being the number of SMS's required, int[1] the number of code
+ * @return an int[6] with int[0] being the number of SMS's required, int[1] the number of code
* units used, and int[2] is the number of code units remaining until the next message.
* int[3] is an indicator of the encoding code unit size (see the ENCODING_* definitions in
- * SmsConstants).
+ * SmsConstants). int[4] is the GSM national language table to use, or 0 for the default
+ * 7-bit alphabet. int[5] The GSM national language shift table to use, or 0 for the default
+ * 7-bit extension table.
*/
public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) {
return calculateLength(msgBody, use7bitOnly, SmsManager.getDefaultSmsSubscriptionId());
@@ -362,10 +364,12 @@
* are counted as single space chars. If false, and if the messageBody contains non-7-bit
* encodable characters, length is calculated using a 16-bit encoding.
* @param subId Subscription to take SMS format.
- * @return an int[4] with int[0] being the number of SMS's required, int[1] the number of code
+ * @return an int[6] with int[0] being the number of SMS's required, int[1] the number of code
* units used, and int[2] is the number of code units remaining until the next message.
* int[3] is an indicator of the encoding code unit size (see the ENCODING_* definitions in
- * SmsConstants).
+ * SmsConstants). int[4] is the GSM national language table to use, or 0 for the default
+ * 7-bit alphabet. int[5] The GSM national language shift table to use, or 0 for the default
+ * 7-bit extension table.
* @hide
*/
public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly, int subId) {
@@ -376,11 +380,13 @@
msgBody, use7bitOnly, true)
: com.android.internal.telephony.gsm.SmsMessage.calculateLength(
msgBody, use7bitOnly);
- int ret[] = new int[4];
+ int[] ret = new int[6];
ret[0] = ted.msgCount;
ret[1] = ted.codeUnitCount;
ret[2] = ted.codeUnitsRemaining;
ret[3] = ted.codeUnitSize;
+ ret[4] = ted.languageTable;
+ ret[5] = ted.languageShiftTable;
return ret;
}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index ebb5175..94085e9 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -30,7 +30,6 @@
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Typeface;
-import android.os.Build;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
@@ -38,6 +37,8 @@
import android.util.DisplayMetrics;
import android.util.Log;
+import com.android.internal.telephony.util.TelephonyUtils;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -209,6 +210,12 @@
private int mSubscriptionType;
/**
+ * Whether uicc applications are configured to enable or disable.
+ * By default it's true.
+ */
+ private boolean mAreUiccApplicationsEnabled = true;
+
+ /**
* @hide
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -219,7 +226,7 @@
roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, -1,
false, null, false, TelephonyManager.UNKNOWN_CARRIER_ID,
SubscriptionManager.PROFILE_CLASS_DEFAULT,
- SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
+ SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null, true);
}
/**
@@ -233,7 +240,7 @@
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, -1,
isOpportunistic, groupUUID, false, carrierId, profileClass,
- SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
+ SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null, true);
}
/**
@@ -245,7 +252,8 @@
@Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
int carrierId, int profileClass, int subType, @Nullable String groupOwner,
- @Nullable UiccAccessRule[] carrierConfigAccessRules) {
+ @Nullable UiccAccessRule[] carrierConfigAccessRules,
+ boolean areUiccApplicationsEnabled) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -271,6 +279,7 @@
this.mSubscriptionType = subType;
this.mGroupOwner = groupOwner;
this.mCarrierConfigAccessRules = carrierConfigAccessRules;
+ this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
}
/**
@@ -659,14 +668,23 @@
return mIsGroupDisabled;
}
+ /**
+ * Return whether uicc applications are set to be enabled or disabled.
+ * @hide
+ */
+ @SystemApi
+ public boolean areUiccApplicationsEnabled() {
+ return mAreUiccApplicationsEnabled;
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<SubscriptionInfo> CREATOR = new Parcelable.Creator<SubscriptionInfo>() {
@Override
public SubscriptionInfo createFromParcel(Parcel source) {
int id = source.readInt();
String iccId = source.readString();
int simSlotIndex = source.readInt();
- CharSequence displayName = source.readCharSequence();
- CharSequence carrierName = source.readCharSequence();
+ CharSequence displayName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ CharSequence carrierName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
int nameSource = source.readInt();
int iconTint = source.readInt();
String number = source.readString();
@@ -685,17 +703,18 @@
int carrierid = source.readInt();
int profileClass = source.readInt();
int subType = source.readInt();
- String[] ehplmns = source.readStringArray();
- String[] hplmns = source.readStringArray();
+ String[] ehplmns = source.createStringArray();
+ String[] hplmns = source.createStringArray();
String groupOwner = source.readString();
UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
UiccAccessRule.CREATOR);
+ boolean areUiccApplicationsEnabled = source.readBoolean();
SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc,
countryIso, isEmbedded, nativeAccessRules, cardString, cardId, isOpportunistic,
groupUUID, isGroupDisabled, carrierid, profileClass, subType, groupOwner,
- carrierConfigAccessRules);
+ carrierConfigAccessRules, areUiccApplicationsEnabled);
info.setAssociatedPlmns(ehplmns, hplmns);
return info;
}
@@ -711,8 +730,8 @@
dest.writeInt(mId);
dest.writeString(mIccId);
dest.writeInt(mSimSlotIndex);
- dest.writeCharSequence(mDisplayName);
- dest.writeCharSequence(mCarrierName);
+ TextUtils.writeToParcel(mDisplayName, dest, 0);
+ TextUtils.writeToParcel(mCarrierName, dest, 0);
dest.writeInt(mNameSource);
dest.writeInt(mIconTint);
dest.writeString(mNumber);
@@ -735,6 +754,7 @@
dest.writeStringArray(mHplmns);
dest.writeString(mGroupOwner);
dest.writeTypedArray(mCarrierConfigAccessRules, flags);
+ dest.writeBoolean(mAreUiccApplicationsEnabled);
}
@Override
@@ -748,7 +768,7 @@
public static String givePrintableIccid(String iccId) {
String iccIdToPrint = null;
if (iccId != null) {
- if (iccId.length() > 9 && !Build.IS_DEBUGGABLE) {
+ if (iccId.length() > 9 && !TelephonyUtils.IS_DEBUGGABLE) {
iccIdToPrint = iccId.substring(0, 9) + Rlog.pii(false, iccId.substring(9));
} else {
iccIdToPrint = iccId;
@@ -764,7 +784,8 @@
return "{id=" + mId + " iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
+ " carrierId=" + mCarrierId + " displayName=" + mDisplayName
+ " carrierName=" + mCarrierName + " nameSource=" + mNameSource
- + " iconTint=" + mIconTint + " mNumber=" + Rlog.pii(Build.IS_DEBUGGABLE, mNumber)
+ + " iconTint=" + mIconTint
+ + " mNumber=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
+ " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
+ " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
+ " nativeAccessRules " + Arrays.toString(mNativeAccessRules)
@@ -776,15 +797,16 @@
+ " hplmns=" + Arrays.toString(mHplmns)
+ " subscriptionType=" + mSubscriptionType
+ " mGroupOwner=" + mGroupOwner
- + " carrierConfigAccessRules=" + mCarrierConfigAccessRules + "}";
+ + " carrierConfigAccessRules=" + mCarrierConfigAccessRules
+ + " mAreUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}";
}
@Override
public int hashCode() {
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
- mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc,
- mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mNativeAccessRules,
- mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner);
+ mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
+ mCardId, mDisplayName, mCarrierName, mNativeAccessRules, mIsGroupDisabled,
+ mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled);
}
@Override
@@ -807,6 +829,7 @@
&& mIsEmbedded == toCompare.mIsEmbedded
&& mIsOpportunistic == toCompare.mIsOpportunistic
&& mIsGroupDisabled == toCompare.mIsGroupDisabled
+ && mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled
&& mCarrierId == toCompare.mCarrierId
&& Objects.equals(mGroupUUID, toCompare.mGroupUUID)
&& Objects.equals(mIccId, toCompare.mIccId)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index cf705f4..f08e1ec 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -48,7 +48,6 @@
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.Process;
@@ -64,6 +63,7 @@
import com.android.internal.telephony.ISetOpportunisticDataCallback;
import com.android.internal.telephony.ISub;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.util.HandlerExecutor;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -835,6 +835,12 @@
public static final String IMSI = "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";
+
+ /**
* Broadcast Action: The user has changed one of the default subs related to
* data, phone calls, or sms</p>
*
@@ -906,9 +912,9 @@
* <p>
* Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to indicate which subscription
* changed.
- *
* @hide
*/
+ @SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS)
public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED
diff --git a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
index b75d533..0d2a8bc 100644
--- a/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
+++ b/telephony/java/android/telephony/TelephonyFrameworkInitializer.java
@@ -16,11 +16,16 @@
package android.telephony;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
import android.content.Context;
+import android.os.TelephonyServiceManager;
import android.telephony.euicc.EuiccCardManager;
import android.telephony.euicc.EuiccManager;
+import android.telephony.ims.ImsManager;
+
+import com.android.internal.util.Preconditions;
/**
@@ -34,6 +39,23 @@
private TelephonyFrameworkInitializer() {
}
+ private static volatile TelephonyServiceManager sTelephonyServiceManager;
+
+ /**
+ * Sets an instance of {@link TelephonyServiceManager} that allows
+ * the telephony mainline module to register/obtain telephony binder services. This is called
+ * by the platform during the system initialization.
+ *
+ * @param telephonyServiceManager instance of {@link TelephonyServiceManager} that allows
+ * the telephony mainline module to register/obtain telephony binder services.
+ */
+ public static void setTelephonyServiceManager(
+ @NonNull TelephonyServiceManager telephonyServiceManager) {
+ Preconditions.checkState(sTelephonyServiceManager == null,
+ "setTelephonyServiceManager called twice!");
+ sTelephonyServiceManager = Preconditions.checkNotNull(telephonyServiceManager);
+ }
+
/**
* Called by {@link SystemServiceRegistry}'s static initializer and registers all telephony
* services to {@link Context}, so that {@link Context#getSystemService} can return them.
@@ -67,5 +89,15 @@
EuiccCardManager.class,
context -> new EuiccCardManager(context)
);
+ SystemServiceRegistry.registerContextAwareService(
+ Context.TELEPHONY_IMS_SERVICE,
+ ImsManager.class,
+ context -> new ImsManager(context)
+ );
+ }
+
+ /** @hide */
+ public static TelephonyServiceManager getTelephonyServiceManager() {
+ return sTelephonyServiceManager;
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e3981f6..39e57b7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -38,6 +38,9 @@
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.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -142,6 +145,14 @@
private static final String TAG = "TelephonyManager";
/**
+ * To expand the error codes for {@link TelephonyManager#updateAvailableNetworks} and
+ * {@link TelephonyManager#setPreferredOpportunisticDataSubscription}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
+
+ /**
* The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
* into the ResultReceiver Bundle.
* @hide
@@ -430,14 +441,14 @@
int modemCount = 1;
switch (getMultiSimConfiguration()) {
case UNKNOWN:
- ConnectivityManager cm = mContext == null ? null : (ConnectivityManager) mContext
- .getSystemService(Context.CONNECTIVITY_SERVICE);
+ modemCount = MODEM_COUNT_SINGLE_MODEM;
// check for voice and data support, 0 if not supported
- if (!isVoiceCapable() && !isSmsCapable() && cm != null
- && !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
- modemCount = MODEM_COUNT_NO_MODEM;
- } else {
- modemCount = MODEM_COUNT_SINGLE_MODEM;
+ 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;
+ }
}
break;
case DSDS:
@@ -605,6 +616,7 @@
*
* @hide
*/
+ @SystemApi
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_EMERGENCY_ASSISTANCE =
"android.telephony.action.EMERGENCY_ASSISTANCE";
@@ -716,31 +728,6 @@
public static final String EXTRA_INCOMING_NUMBER = "incoming_number";
/**
- * Broadcast intent action indicating that a precise call state
- * (cellular) on the device has changed.
- *
- * <p>
- * The {@link #EXTRA_RINGING_CALL_STATE} extra indicates the ringing call state.
- * The {@link #EXTRA_FOREGROUND_CALL_STATE} extra indicates the foreground call state.
- * The {@link #EXTRA_BACKGROUND_CALL_STATE} extra indicates the background call state.
- *
- * <p class="note">
- * Requires the READ_PRECISE_PHONE_STATE permission.
- *
- * @see #EXTRA_RINGING_CALL_STATE
- * @see #EXTRA_FOREGROUND_CALL_STATE
- * @see #EXTRA_BACKGROUND_CALL_STATE
- *
- * <p class="note">
- * Requires the READ_PRECISE_PHONE_STATE permission.
- * @deprecated use {@link PhoneStateListener#LISTEN_PRECISE_CALL_STATE} instead
- * @hide
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_PRECISE_CALL_STATE_CHANGED =
- "android.intent.action.PRECISE_CALL_STATE";
-
- /**
* Broadcast intent action indicating that call disconnect cause has changed.
*
* <p>
@@ -762,78 +749,6 @@
/**
* The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
* {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
- * containing the state of the current ringing call.
- *
- * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID
- * @see PreciseCallState#PRECISE_CALL_STATE_IDLE
- * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE
- * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING
- * @see PreciseCallState#PRECISE_CALL_STATE_DIALING
- * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING
- * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING
- * @see PreciseCallState#PRECISE_CALL_STATE_WAITING
- * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED
- * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}.
- *
- * @hide
- */
- public static final String EXTRA_RINGING_CALL_STATE = "ringing_state";
-
- /**
- * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
- * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
- * containing the state of the current foreground call.
- *
- * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID
- * @see PreciseCallState#PRECISE_CALL_STATE_IDLE
- * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE
- * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING
- * @see PreciseCallState#PRECISE_CALL_STATE_DIALING
- * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING
- * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING
- * @see PreciseCallState#PRECISE_CALL_STATE_WAITING
- * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED
- * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}.
- *
- * @hide
- */
- public static final String EXTRA_FOREGROUND_CALL_STATE = "foreground_state";
-
- /**
- * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
- * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
- * containing the state of the current background call.
- *
- * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID
- * @see PreciseCallState#PRECISE_CALL_STATE_IDLE
- * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE
- * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING
- * @see PreciseCallState#PRECISE_CALL_STATE_DIALING
- * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING
- * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING
- * @see PreciseCallState#PRECISE_CALL_STATE_WAITING
- * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED
- * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}.
- *
- * @hide
- */
- public static final String EXTRA_BACKGROUND_CALL_STATE = "background_state";
-
- /**
- * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
- * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
* containing the disconnect cause.
*
* @see DisconnectCause
@@ -842,8 +757,10 @@
* Retrieve with
* {@link android.content.Intent#getIntExtra(String name, int defaultValue)}.
*
+ * @deprecated Should use the {@link TelecomManager#EXTRA_DISCONNECT_CAUSE} instead.
* @hide
*/
+ @Deprecated
public static final String EXTRA_DISCONNECT_CAUSE = "disconnect_cause";
/**
@@ -862,88 +779,6 @@
public static final String EXTRA_PRECISE_DISCONNECT_CAUSE = "precise_disconnect_cause";
/**
- * Broadcast intent action indicating a data connection has changed,
- * providing precise information about the connection.
- *
- * <p>
- * The {@link #EXTRA_DATA_STATE} extra indicates the connection state.
- * The {@link #EXTRA_DATA_NETWORK_TYPE} extra indicates the connection network type.
- * The {@link #EXTRA_DATA_APN_TYPE} extra indicates the APN type.
- * The {@link #EXTRA_DATA_APN} extra indicates the APN.
- * The {@link #EXTRA_DATA_IFACE_PROPERTIES} extra indicates the connection interface.
- * The {@link #EXTRA_DATA_FAILURE_CAUSE} extra indicates the connection fail cause.
- *
- * <p class="note">
- * Requires the READ_PRECISE_PHONE_STATE permission.
- *
- * @see #EXTRA_DATA_STATE
- * @see #EXTRA_DATA_NETWORK_TYPE
- * @see #EXTRA_DATA_APN_TYPE
- * @see #EXTRA_DATA_APN
- * @see #EXTRA_DATA_IFACE
- * @see #EXTRA_DATA_FAILURE_CAUSE
- * @hide
- *
- * @deprecated If the app is running in the background, it won't be able to receive this
- * broadcast. Apps should use ConnectivityManager {@link #registerNetworkCallback(
- * android.net.NetworkRequest, ConnectivityManager.NetworkCallback)} to listen for network
- * changes.
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- @Deprecated
- @UnsupportedAppUsage
- public static final String ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED =
- "android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED";
-
- /**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for an integer containing the state of the current data connection.
- *
- * @see TelephonyManager#DATA_UNKNOWN
- * @see TelephonyManager#DATA_DISCONNECTED
- * @see TelephonyManager#DATA_CONNECTING
- * @see TelephonyManager#DATA_CONNECTED
- * @see TelephonyManager#DATA_SUSPENDED
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_STATE = PhoneConstants.STATE_KEY;
-
- /**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for an integer containing the network type.
- *
- * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
- * @see TelephonyManager#NETWORK_TYPE_GPRS
- * @see TelephonyManager#NETWORK_TYPE_EDGE
- * @see TelephonyManager#NETWORK_TYPE_UMTS
- * @see TelephonyManager#NETWORK_TYPE_CDMA
- * @see TelephonyManager#NETWORK_TYPE_EVDO_0
- * @see TelephonyManager#NETWORK_TYPE_EVDO_A
- * @see TelephonyManager#NETWORK_TYPE_1xRTT
- * @see TelephonyManager#NETWORK_TYPE_HSDPA
- * @see TelephonyManager#NETWORK_TYPE_HSUPA
- * @see TelephonyManager#NETWORK_TYPE_HSPA
- * @see TelephonyManager#NETWORK_TYPE_IDEN
- * @see TelephonyManager#NETWORK_TYPE_EVDO_B
- * @see TelephonyManager#NETWORK_TYPE_LTE
- * @see TelephonyManager#NETWORK_TYPE_EHRPD
- * @see TelephonyManager#NETWORK_TYPE_HSPAP
- * @see TelephonyManager#NETWORK_TYPE_NR
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_NETWORK_TYPE = PhoneConstants.DATA_NETWORK_TYPE_KEY;
-
- /**
* The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
* for an String containing the data APN type.
*
@@ -968,30 +803,6 @@
public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY;
/**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for an String representation of the data interface.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getParcelableExtra(String name)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_LINK_PROPERTIES_KEY = PhoneConstants.DATA_LINK_PROPERTIES_KEY;
-
- /**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for the data connection fail cause.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getStringExtra(String name)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_FAILURE_CAUSE = PhoneConstants.DATA_FAILURE_CAUSE_KEY;
-
- /**
* Broadcast intent action for letting the default dialer to know to show voicemail
* notification.
*
@@ -1652,6 +1463,80 @@
*/
public static final String EXTRA_SIM_COMBINATION_NAMES =
"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>
+ * </ul>
+ * <p class="note">
+ * You can <em>not</em> receive this through components declared
+ * in manifests, only by explicitly registering for it with
+ * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
+ * android.content.IntentFilter) Context.registerReceiver()}.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
+ = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+
+ /**
+ * <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>
+ * </ul>
+ * <p class="note">
+ * You can <em>not</em> receive this through components declared
+ * in manifests, only by explicitly registering for it with
+ * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
+ * android.content.IntentFilter) Context.registerReceiver()}.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
+ = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
+
+ /**
+ * <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms
+ * <p class="note">.
+ * This is to pop up a notice to show user that the phone is in emergency callback mode
+ * and data calls and outgoing sms are blocked.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
+ = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
+
//
//
// Device Info
@@ -2065,14 +1950,9 @@
return null;
}
- Bundle bundle = telephony.getCellLocation(mContext.getOpPackageName(),
+ CellIdentity cellIdentity = telephony.getCellLocation(mContext.getOpPackageName(),
mContext.getFeatureId());
- if (bundle == null || bundle.isEmpty()) {
- Rlog.d(TAG, "getCellLocation returning null because CellLocation is unavailable");
- return null;
- }
-
- CellLocation cl = CellLocation.newFromBundle(bundle);
+ CellLocation cl = cellIdentity.asCellLocation();
if (cl == null || cl.isEmpty()) {
Rlog.d(TAG, "getCellLocation returning null because CellLocation is empty or"
+ " phone type doesn't match CellLocation type");
@@ -2632,7 +2512,7 @@
/*
* When adding a network type to the list below, make sure to add the correct icon to
- * MobileSignalController.mapIconSets().
+ * MobileSignalController.mapIconSets() as well as NETWORK_TYPES
* Do not add negative types.
*/
/** Network type is unknown */
@@ -2680,8 +2560,36 @@
/** Current network is NR(New Radio) 5G. */
public static final int NETWORK_TYPE_NR = TelephonyProtoEnums.NETWORK_TYPE_NR; // 20.
- /** Max network type number. Update as new types are added. Don't add negative types. {@hide} */
- public static final int MAX_NETWORK_TYPE = NETWORK_TYPE_NR;
+ private static final @NetworkType int[] NETWORK_TYPES = {
+ NETWORK_TYPE_GPRS,
+ NETWORK_TYPE_EDGE,
+ NETWORK_TYPE_UMTS,
+ NETWORK_TYPE_CDMA,
+ NETWORK_TYPE_EVDO_0,
+ NETWORK_TYPE_EVDO_A,
+ NETWORK_TYPE_1xRTT,
+ NETWORK_TYPE_HSDPA,
+ NETWORK_TYPE_HSUPA,
+ NETWORK_TYPE_HSPA,
+ NETWORK_TYPE_IDEN,
+ NETWORK_TYPE_EVDO_B,
+ NETWORK_TYPE_LTE,
+ NETWORK_TYPE_EHRPD,
+ NETWORK_TYPE_HSPAP,
+ NETWORK_TYPE_GSM,
+ NETWORK_TYPE_TD_SCDMA,
+ NETWORK_TYPE_IWLAN,
+ NETWORK_TYPE_LTE_CA,
+ NETWORK_TYPE_NR
+ };
+
+ /**
+ * Return a collection of all network types
+ * @return network types
+ */
+ public static @NonNull @NetworkType int[] getAllNetworkTypes() {
+ return NETWORK_TYPES;
+ }
/**
* Return the current data network type.
@@ -3026,6 +2934,24 @@
//
//
+ /** @hide */
+ @IntDef(prefix = {"SIM_STATE_"},
+ value = {
+ SIM_STATE_UNKNOWN,
+ SIM_STATE_ABSENT,
+ SIM_STATE_PIN_REQUIRED,
+ SIM_STATE_PUK_REQUIRED,
+ SIM_STATE_NETWORK_LOCKED,
+ SIM_STATE_READY,
+ SIM_STATE_NOT_READY,
+ SIM_STATE_PERM_DISABLED,
+ SIM_STATE_CARD_IO_ERROR,
+ SIM_STATE_CARD_RESTRICTED,
+ SIM_STATE_LOADED,
+ SIM_STATE_PRESENT,
+ })
+ public @interface SimState {}
+
/**
* SIM card state: Unknown. Signifies that the SIM is in transition
* between states. For example, when the user inputs the SIM pin
@@ -3231,7 +3157,7 @@
* @see #SIM_STATE_CARD_IO_ERROR
* @see #SIM_STATE_CARD_RESTRICTED
*/
- public int getSimState() {
+ public @SimState int getSimState() {
int simState = getSimStateIncludingLoaded();
if (simState == SIM_STATE_LOADED) {
simState = SIM_STATE_READY;
@@ -3239,7 +3165,7 @@
return simState;
}
- private int getSimStateIncludingLoaded() {
+ private @SimState int getSimStateIncludingLoaded() {
int slotIndex = getSlotIndex();
// slotIndex may be invalid due to sim being absent. In that case query all slots to get
// sim state
@@ -3273,7 +3199,7 @@
* @hide
*/
@SystemApi
- public int getSimCardState() {
+ public @SimState int getSimCardState() {
int simState = getSimState();
return getSimCardStateFromSimState(simState);
}
@@ -3293,7 +3219,7 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public int getSimCardState(int physicalSlotIndex) {
+ public @SimState int getSimCardState(int physicalSlotIndex) {
int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex));
return getSimCardStateFromSimState(simState);
}
@@ -3303,7 +3229,7 @@
* @param simState
* @return SIM card state
*/
- private int getSimCardStateFromSimState(int simState) {
+ private @SimState int getSimCardStateFromSimState(int simState) {
switch (simState) {
case SIM_STATE_UNKNOWN:
case SIM_STATE_ABSENT:
@@ -3343,7 +3269,7 @@
* @hide
*/
@SystemApi
- public int getSimApplicationState() {
+ public @SimState int getSimApplicationState() {
int simState = getSimStateIncludingLoaded();
return getSimApplicationStateFromSimState(simState);
}
@@ -3366,7 +3292,7 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public int getSimApplicationState(int physicalSlotIndex) {
+ public @SimState int getSimApplicationState(int physicalSlotIndex) {
int simState =
SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex));
return getSimApplicationStateFromSimState(simState);
@@ -3377,7 +3303,7 @@
* @param simState
* @return SIM application state
*/
- private int getSimApplicationStateFromSimState(int simState) {
+ private @SimState int getSimApplicationStateFromSimState(int simState) {
switch (simState) {
case SIM_STATE_UNKNOWN:
case SIM_STATE_ABSENT:
@@ -3434,7 +3360,7 @@
* @see #SIM_STATE_CARD_IO_ERROR
* @see #SIM_STATE_CARD_RESTRICTED
*/
- public int getSimState(int slotIndex) {
+ public @SimState int getSimState(int slotIndex) {
int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex);
if (simState == SIM_STATE_LOADED) {
simState = SIM_STATE_READY;
@@ -4317,7 +4243,7 @@
* The returned set of subscriber IDs will include the subscriber ID corresponding to this
* TelephonyManager's subId.
*
- * This is deprecated and {@link #getMergedSubscriberIdsFromGroup()} should be used for data
+ * This is deprecated and {@link #getMergedImsisFromGroup()} should be used for data
* usage merging purpose.
* TODO: remove this API.
*
@@ -4338,25 +4264,27 @@
}
/**
- * Return the set of subscriber IDs that should be considered "merged together" for data usage
- * purposes. Unlike {@link #getMergedSubscriberIds()} this API merge subscriberIds based on
- * subscription grouping: subscriberId of those in the same group will all be returned.
+ * Return the set of IMSIs that should be considered "merged together" for data usage
+ * purposes. Unlike {@link #getMergedSubscriberIds()} this API merge IMSIs based on
+ * subscription grouping: IMSI of those in the same group will all be returned.
+ * Return the current IMSI if there is no subscription group.
*
* <p>Requires the calling app to have READ_PRIVILEGED_PHONE_STATE permission.
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public @Nullable String[] getMergedSubscriberIdsFromGroup() {
+ public @NonNull String[] getMergedImsisFromGroup() {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getMergedSubscriberIdsFromGroup(getSubId(), getOpPackageName());
+ return telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName());
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
}
- return null;
+ return new String[0];
}
/**
@@ -5262,6 +5190,7 @@
DATA_CONNECTING,
DATA_CONNECTED,
DATA_SUSPENDED,
+ DATA_DISCONNECTING,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DataState{}
@@ -5278,6 +5207,12 @@
* traffic is temporarily unavailable. For example, in a 2G network,
* data activity may be suspended when a voice call arrives. */
public static final int DATA_SUSPENDED = 3;
+ /**
+ * Data connection state: Disconnecting.
+ *
+ * IP traffic may be available but will cease working imminently.
+ */
+ public static final int DATA_DISCONNECTING = 4;
/**
* Returns a constant indicating the current data connection state
@@ -5287,14 +5222,21 @@
* @see #DATA_CONNECTING
* @see #DATA_CONNECTED
* @see #DATA_SUSPENDED
+ * @see #DATA_DISCONNECTING
*/
public int getDataState() {
try {
ITelephony telephony = getITelephony();
if (telephony == null)
return DATA_DISCONNECTED;
- return telephony.getDataStateForSubId(
+ int state = telephony.getDataStateForSubId(
getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
+ if (state == TelephonyManager.DATA_DISCONNECTING
+ && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ return TelephonyManager.DATA_CONNECTED;
+ }
+
+ return state;
} catch (RemoteException ex) {
// the phone process is restarting.
return DATA_DISCONNECTED;
@@ -5315,6 +5257,7 @@
case DATA_CONNECTING: return "CONNECTING";
case DATA_CONNECTED: return "CONNECTED";
case DATA_SUSPENDED: return "SUSPENDED";
+ case DATA_DISCONNECTING: return "DISCONNECTING";
}
return "UNKNOWN(" + state + ")";
}
@@ -5324,7 +5267,8 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private ITelephony getITelephony() {
- return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ return ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
+ .getTelephonyServiceManager().getTelephonyServiceRegisterer().get());
}
private ITelephonyRegistry getTelephonyRegistry() {
@@ -6629,75 +6573,6 @@
}
/**
- * Sets a per-phone telephony property with the value specified.
- *
- * @hide
- */
- @UnsupportedAppUsage
- public static void setTelephonyProperty(int phoneId, String property, String value) {
- String propVal = "";
- String p[] = null;
- String prop = SystemProperties.get(property);
-
- if (value == null) {
- value = "";
- }
- value.replace(',', ' ');
- if (prop != null) {
- p = prop.split(",");
- }
-
- if (!SubscriptionManager.isValidPhoneId(phoneId)) {
- Rlog.d(TAG, "setTelephonyProperty: invalid phoneId=" + phoneId +
- " property=" + property + " value: " + value + " prop=" + prop);
- return;
- }
-
- for (int i = 0; i < phoneId; i++) {
- String str = "";
- if ((p != null) && (i < p.length)) {
- str = p[i];
- }
- propVal = propVal + str + ",";
- }
-
- propVal = propVal + value;
- if (p != null) {
- for (int i = phoneId + 1; i < p.length; i++) {
- propVal = propVal + "," + p[i];
- }
- }
-
- int propValLen = propVal.length();
- try {
- propValLen = propVal.getBytes("utf-8").length;
- } catch (java.io.UnsupportedEncodingException e) {
- Rlog.d(TAG, "setTelephonyProperty: utf-8 not supported");
- }
- if (propValLen > SystemProperties.PROP_VALUE_MAX) {
- Rlog.d(TAG, "setTelephonyProperty: property too long phoneId=" + phoneId +
- " property=" + property + " value: " + value + " propVal=" + propVal);
- return;
- }
-
- SystemProperties.set(property, propVal);
- }
-
- /**
- * Sets a global telephony property with the value specified.
- *
- * @hide
- */
- public static void setTelephonyProperty(String property, String value) {
- if (value == null) {
- value = "";
- }
- Rlog.d(TAG, "setTelephonyProperty: success" + " property=" +
- property + " value: " + value);
- SystemProperties.set(property, value);
- }
-
- /**
* Inserts or updates a list property. Expands the list if its length is not enough.
*/
private static <T> List<T> updateTelephonyProperty(List<T> prop, int phoneId, T value) {
@@ -7034,7 +6909,8 @@
* If the list is longer than the size of EFfplmn, then the file will be written from the
* beginning of the list up to the file size.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * <p>Requires Permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE
+ * MODIFY_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
* @param fplmns a list of PLMNs to be forbidden.
@@ -7048,7 +6924,7 @@
public int setForbiddenPlmns(@NonNull List<String> fplmns) {
try {
ITelephony telephony = getITelephony();
- if (telephony == null) return 0;
+ if (telephony == null) return -1;
return telephony.setForbiddenPlmns(
getSubId(), APPTYPE_USIM, fplmns, getOpPackageName(), getFeatureId());
} catch (RemoteException ex) {
@@ -7057,7 +6933,7 @@
// This could happen before phone starts
Rlog.e(TAG, "setForbiddenPlmns NullPointerException: " + ex.getMessage());
}
- return 0;
+ return -1;
}
/**
@@ -8549,6 +8425,44 @@
}
/**
+ * Shut down all the live radios over all the slot index.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void shutdownAllRadios() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.shutdownMobileRadios();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#shutdownMobileRadios", e);
+ }
+ }
+
+ /**
+ * Check if any radio is on over all the slot indexes.
+ *
+ * @return {@code true} if any radio is on over any slot index.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isAnyRadioPoweredOn() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.needMobileRadioShutdown();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#needMobileRadioShutdown", e);
+ }
+ return false;
+ }
+
+ /**
* Radio explicitly powered off (e.g, airplane mode).
* @hide
*/
@@ -9802,7 +9716,8 @@
* {@link android.telephony.ModemActivityInfo} object.
* @hide
*/
- public void requestModemActivityInfo(ResultReceiver result) {
+ @SystemApi
+ public void requestModemActivityInfo(@NonNull ResultReceiver result) {
try {
ITelephony service = getITelephony();
if (service != null) {
@@ -10457,19 +10372,25 @@
}
/**
- * Action set from carrier signalling broadcast receivers to enable/disable radio
- * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
- * @param subId the subscription ID that this action applies to.
+ * Carrier action to enable or disable the radio.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ *
* @param enabled control enable or disable radio.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void carrierActionSetRadioEnabled(int subId, boolean enabled) {
+ public void setRadioEnabled(boolean enabled) {
try {
ITelephony service = getITelephony();
if (service != null) {
- service.carrierActionSetRadioEnabled(subId, enabled);
+ service.carrierActionSetRadioEnabled(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#carrierActionSetRadioEnabled", e);
@@ -10477,20 +10398,25 @@
}
/**
- * Action set from carrier signalling broadcast receivers to start/stop reporting default
- * network available events
- * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
- * @param subId the subscription ID that this action applies to.
+ * Carrier action to start or stop reporting default network available events.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+ *
* @param report control start/stop reporting network status.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) {
+ public void reportDefaultNetworkStatus(boolean report) {
try {
ITelephony service = getITelephony();
if (service != null) {
- service.carrierActionReportDefaultNetworkStatus(subId, report);
+ service.carrierActionReportDefaultNetworkStatus(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), report);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#carrierActionReportDefaultNetworkStatus", e);
@@ -10498,18 +10424,24 @@
}
/**
- * Action set from carrier signalling broadcast receivers to reset all carrier actions
- * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
- * @param subId the subscription ID that this action applies to.
+ * Reset all carrier actions previously set by {@link #setRadioEnabled},
+ * {@link #reportDefaultNetworkStatus} and {@link #setCarrierDataEnabled}.
+ *
+ * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public void carrierActionResetAll(int subId) {
+ public void resetAllCarrierActions() {
try {
ITelephony service = getITelephony();
if (service != null) {
- service.carrierActionResetAll(subId);
+ service.carrierActionResetAll(
+ getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#carrierActionResetAll", e);
@@ -11522,7 +11454,11 @@
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> {
- callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+ if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+ callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+ } else {
+ callback.accept(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+ }
});
} finally {
Binder.restoreCallingIdentity(identity);
@@ -11619,7 +11555,11 @@
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(() -> {
- callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
+ if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+ callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
+ } else {
+ callback.accept(UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE);
+ }
});
} finally {
Binder.restoreCallingIdentity(identity);
@@ -11893,6 +11833,32 @@
}
/**
+ * Get the calling application status about carrier privileges for the subscription created
+ * in TelephonyManager. Used by Telephony Module for permission checking.
+ *
+ * @param uid Uid to check.
+ * @return any value of {@link #CARRIER_PRIVILEGE_STATUS_HAS_ACCESS},
+ * {@link #CARRIER_PRIVILEGE_STATUS_NO_ACCESS},
+ * {@link #CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED}, or
+ * {@link #CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES}
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public int getCarrierPrivilegeStatus(int uid) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getCarrierPrivilegeStatusForUid(getSubId(), uid);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getCarrierPrivilegeStatus RemoteException", ex);
+ }
+ return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+ }
+
+ /**
* Return whether data is enabled for certain APN type. This will tell if framework will accept
* corresponding network requests on a subId.
*
@@ -11935,6 +11901,8 @@
*
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public boolean isApnMetered(@ApnType int apnType) {
try {
ITelephony service = getITelephony();
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 60774e7..034fc22 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.content.ContentValues;
import android.database.Cursor;
-import android.hardware.radio.V1_4.ApnTypes;
+import android.hardware.radio.V1_5.ApnTypes;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -110,6 +110,8 @@
public static final int TYPE_EMERGENCY = ApnTypes.EMERGENCY;
/** APN type for MCX (Mission Critical Service) where X can be PTT/Video/Data */
public static final int TYPE_MCX = ApnTypes.MCX;
+ /** APN type for XCAP. */
+ public static final int TYPE_XCAP = ApnTypes.XCAP;
// Possible values for authentication types.
/** No authentication type. */
@@ -198,6 +200,7 @@
APN_TYPE_STRING_MAP.put("ia", TYPE_IA);
APN_TYPE_STRING_MAP.put("emergency", TYPE_EMERGENCY);
APN_TYPE_STRING_MAP.put("mcx", TYPE_MCX);
+ APN_TYPE_STRING_MAP.put("xcap", TYPE_XCAP);
APN_TYPE_INT_MAP = new ArrayMap<Integer, String>();
APN_TYPE_INT_MAP.put(TYPE_DEFAULT, "default");
APN_TYPE_INT_MAP.put(TYPE_MMS, "mms");
@@ -210,6 +213,7 @@
APN_TYPE_INT_MAP.put(TYPE_IA, "ia");
APN_TYPE_INT_MAP.put(TYPE_EMERGENCY, "emergency");
APN_TYPE_INT_MAP.put(TYPE_MCX, "mcx");
+ APN_TYPE_INT_MAP.put(TYPE_XCAP, "xcap");
PROTOCOL_STRING_MAP = new ArrayMap<String, Integer>();
PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
@@ -1944,8 +1948,9 @@
* {@link ApnSetting} built from this builder otherwise.
*/
public ApnSetting build() {
- if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI |
- TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX)) == 0
+ if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI
+ | TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX
+ | TYPE_XCAP)) == 0
|| TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) {
return null;
}
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 30c209b..96a5a81 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -22,7 +22,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.ApnType;
@@ -31,6 +30,7 @@
import android.text.TextUtils;
import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.util.TelephonyUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -261,7 +261,7 @@
@Override
public String toString() {
return "DataProfile=" + mProfileId + "/" + mProtocolType + "/" + mAuthType
- + "/" + (Build.IS_USER ? "***/***/***" :
+ + "/" + (TelephonyUtils.IS_USER ? "***/***/***" :
(mApn + "/" + mUserName + "/" + mPassword)) + "/" + mType + "/"
+ mMaxConnectionsTime + "/" + mMaxConnections + "/"
+ mWaitTime + "/" + mEnabled + "/" + mSupportedApnTypesBitmask + "/"
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index b0ff5dc..87e5391 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -32,6 +32,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.util.TelephonyUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -833,7 +834,7 @@
}
int startSize = extras.size();
- Bundle filtered = extras.filterValues();
+ Bundle filtered = TelephonyUtils.filterValues(extras);
int endSize = filtered.size();
if (startSize != endSize) {
Log.i(TAG, "maybeCleanseExtras: " + (startSize - endSize) + " extra values were "
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index 47c4681..5adc99e 100644
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -1173,18 +1173,8 @@
public void callSessionMergeComplete(IImsCallSession newSession) {
if (mListener != null) {
if (newSession != null) {
- // Check if the active session is the same session that was
- // active before the merge request was sent.
- ImsCallSession validActiveSession = ImsCallSession.this;
- try {
- if (!Objects.equals(miSession.getCallId(), newSession.getCallId())) {
- // New session created after conference
- validActiveSession = new ImsCallSession(newSession);
- }
- } catch (RemoteException rex) {
- Log.e(TAG, "callSessionMergeComplete: exception for getCallId!");
- }
- mListener.callSessionMergeComplete(validActiveSession);
+ // New session created after conference
+ mListener.callSessionMergeComplete(new ImsCallSession(newSession));
} else {
// Session already exists. Hence no need to pass
mListener.callSessionMergeComplete(null);
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index b37d7c7..d3fb37f 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -20,6 +20,8 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -30,6 +32,7 @@
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
import java.util.concurrent.Executor;
@@ -42,6 +45,8 @@
* Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this manager.
* @hide
*/
+@SystemApi
+@TestApi
public class ImsRcsManager implements RegistrationManager {
private static final String TAG = "ImsRcsManager";
@@ -105,7 +110,7 @@
*
* @param capabilities The new availability of the capabilities.
*/
- public void onAvailabilityChanged(RcsFeature.RcsImsCapabilities capabilities) {
+ public void onAvailabilityChanged(@NonNull RcsFeature.RcsImsCapabilities capabilities) {
}
/**@hide*/
@@ -142,6 +147,7 @@
/**{@inheritDoc}*/
@Override
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerImsRegistrationCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull RegistrationCallback c)
@@ -159,6 +165,7 @@
/**{@inheritDoc}*/
@Override
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void unregisterImsRegistrationCallback(
@NonNull RegistrationManager.RegistrationCallback c) {
if (c == null) {
@@ -170,6 +177,7 @@
/**{@inheritDoc}*/
@Override
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
@NonNull @ImsRegistrationState Consumer<Integer> stateCallback) {
if (stateCallback == null) {
@@ -184,6 +192,7 @@
/**{@inheritDoc}*/
@Override
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
@NonNull @AccessNetworkConstants.TransportType
Consumer<Integer> transportTypeCallback) {
@@ -219,7 +228,7 @@
* becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public void registerRcsAvailabilityCallback(@CallbackExecutor Executor executor,
+ public void registerRcsAvailabilityCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull AvailabilityCallback c) throws ImsException {
if (c == null) {
throw new IllegalArgumentException("Must include a non-null AvailabilityCallback.");
@@ -231,13 +240,13 @@
IImsRcsController imsRcsController = getIImsRcsController();
if (imsRcsController == null) {
Log.e(TAG, "Register availability callback: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
+ throw new ImsException("Cannot find remote IMS service",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
c.setExecutor(executor);
try {
- imsRcsController.registerRcsAvailabilityCallback(c.getBinder());
+ imsRcsController.registerRcsAvailabilityCallback(mSubId, c.getBinder());
} catch (RemoteException e) {
Log.e(TAG, "Error calling IImsRcsController#registerRcsAvailabilityCallback", e);
throw new ImsException("Remote IMS Service is not available",
@@ -267,12 +276,12 @@
IImsRcsController imsRcsController = getIImsRcsController();
if (imsRcsController == null) {
Log.e(TAG, "Unregister availability callback: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
+ throw new ImsException("Cannot find remote IMS service",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
try {
- imsRcsController.unregisterRcsAvailabilityCallback(c.getBinder());
+ imsRcsController.unregisterRcsAvailabilityCallback(mSubId, c.getBinder());
} catch (RemoteException e) {
Log.e(TAG, "Error calling IImsRcsController#unregisterRcsAvailabilityCallback", e);
throw new ImsException("Remote IMS Service is not available",
@@ -287,6 +296,9 @@
* RCS capabilities provided over-the-top by applications.
*
* @param capability The RCS capability to query.
+ * @param radioTech The radio tech that this capability failed for, defined as
+ * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or
+ * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}.
* @return true if the RCS capability is capable for this subscription, false otherwise. This
* does not necessarily mean that we are registered for IMS and the capability is available, but
* rather the subscription is capable of this service over IMS.
@@ -297,17 +309,17 @@
* See {@link ImsException#getCode()} for more information on the error codes.
*/
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public boolean isCapable(@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability)
- throws ImsException {
+ public boolean isCapable(@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) throws ImsException {
IImsRcsController imsRcsController = getIImsRcsController();
if (imsRcsController == null) {
Log.e(TAG, "isCapable: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
+ throw new ImsException("Cannot find remote IMS service",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
try {
- return imsRcsController.isCapable(mSubId, capability);
+ return imsRcsController.isCapable(mSubId, capability, radioTech);
} catch (RemoteException e) {
Log.e(TAG, "Error calling IImsRcsController#isCapable", e);
throw new ImsException("Remote IMS Service is not available",
@@ -336,7 +348,7 @@
IImsRcsController imsRcsController = getIImsRcsController();
if (imsRcsController == null) {
Log.e(TAG, "isAvailable: IImsRcsController is null");
- throw new ImsException("Can not find remote IMS service",
+ throw new ImsException("Cannot find remote IMS service",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index b379bd0..e81bac0 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -26,9 +26,9 @@
* {@hide}
*/
interface IImsRcsController {
- void registerRcsAvailabilityCallback(IImsCapabilityCallback c);
- void unregisterRcsAvailabilityCallback(IImsCapabilityCallback c);
- boolean isCapable(int subId, int capability);
+ void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
+ void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
+ boolean isCapable(int subId, int capability, int radioTech);
boolean isAvailable(int subId, int capability);
// ImsUceAdapter specific
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 72390d0..4cb8575 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -22,7 +22,6 @@
import android.annotation.TestApi;
import android.content.Context;
import android.os.IInterface;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.SubscriptionManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
@@ -31,6 +30,7 @@
import com.android.ims.internal.IImsFeatureStatusCallback;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.RemoteCallbackListExt;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -310,12 +310,12 @@
/** @hide */
protected final Object mLock = new Object();
- private final RemoteCallbackList<IImsFeatureStatusCallback> mStatusCallbacks =
- new RemoteCallbackList<>();
+ private final RemoteCallbackListExt<IImsFeatureStatusCallback> mStatusCallbacks =
+ new RemoteCallbackListExt<>();
private @ImsState int mState = STATE_UNAVAILABLE;
private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
- private final RemoteCallbackList<IImsCapabilityCallback> mCapabilityCallbacks =
- new RemoteCallbackList<>();
+ private final RemoteCallbackListExt<IImsCapabilityCallback> mCapabilityCallbacks =
+ new RemoteCallbackListExt<>();
private Capabilities mCapabilityStatus = new Capabilities();
/**
@@ -391,7 +391,7 @@
* Internal method called by ImsFeature when setFeatureState has changed.
*/
private void notifyFeatureState(@ImsState int state) {
- mStatusCallbacks.broadcast((c) -> {
+ mStatusCallbacks.broadcastAction((c) -> {
try {
c.notifyImsFeatureStatus(state);
} catch (RemoteException e) {
@@ -470,7 +470,7 @@
synchronized (mLock) {
mCapabilityStatus = caps.copy();
}
- mCapabilityCallbacks.broadcast((callback) -> {
+ mCapabilityCallbacks.broadcastAction((callback) -> {
try {
callback.onCapabilitiesStatusChanged(caps.mCapabilities);
} catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 0eaf8dc..884a0bc 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -193,7 +193,6 @@
* of the capability and notify the capability status as true using
* {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the
* framework that the capability is available for usage.
- * @hide
*/
public static class RcsImsCapabilities extends Capabilities {
/** @hide*/
@@ -207,7 +206,6 @@
/**
* Undefined capability type for initialization
- * @hide
*/
public static final int CAPABILITY_TYPE_NONE = 0;
@@ -215,7 +213,6 @@
* This carrier supports User Capability Exchange using SIP OPTIONS as defined by the
* framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
* If not set, this RcsFeature should not service capability requests.
- * @hide
*/
public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1 << 0;
@@ -224,33 +221,27 @@
* framework. If set, the RcsFeature should support capability exchange using a presence
* server. If not set, this RcsFeature should not publish capabilities or service capability
* requests using presence.
- * @hide
*/
public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1;
- /**@hide*/
public RcsImsCapabilities(@RcsImsCapabilityFlag int capabilities) {
super(capabilities);
}
- /**@hide*/
private RcsImsCapabilities(Capabilities c) {
super(c.getMask());
}
- /**@hide*/
@Override
public void addCapabilities(@RcsImsCapabilityFlag int capabilities) {
super.addCapabilities(capabilities);
}
- /**@hide*/
@Override
public void removeCapabilities(@RcsImsCapabilityFlag int capabilities) {
super.removeCapabilities(capabilities);
}
- /**@hide*/
@Override
public boolean isCapable(@RcsImsCapabilityFlag int capabilities) {
return super.isCapable(capabilities);
@@ -295,10 +286,9 @@
* set, the {@link RcsFeature} has brought up the capability and is ready for framework
* requests. To change the status of the capabilities
* {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called.
- * @hide
*/
@Override
- public final RcsImsCapabilities queryCapabilityStatus() {
+ public @NonNull final RcsImsCapabilities queryCapabilityStatus() {
return new RcsImsCapabilities(super.queryCapabilityStatus());
}
@@ -306,7 +296,6 @@
* Notify the framework that the capabilities status has changed. If a capability is enabled,
* this signals to the framework that the capability has been initialized and is ready.
* Call {@link #queryCapabilityStatus()} to return the current capability status.
- * @hide
*/
public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities c) {
if (c == null) {
@@ -321,7 +310,6 @@
* {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} to
* enable or disable capability A, this method should return the correct configuration for
* capability A afterwards (until it has changed).
- * @hide
*/
public boolean queryCapabilityConfiguration(
@RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@@ -343,11 +331,10 @@
* If for some reason one or more of these capabilities can not be enabled/disabled,
* {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError(int, int, int)} should
* be called for each capability change that resulted in an error.
- * @hide
*/
@Override
- public void changeEnabledCapabilities(CapabilityChangeRequest request,
- CapabilityCallbackProxy c) {
+ public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request,
+ @NonNull CapabilityCallbackProxy c) {
// Base Implementation - Override to provide functionality
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 4c0de7f..d18e93c 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -21,7 +21,6 @@
import android.annotation.TestApi;
import android.content.Context;
import android.os.PersistableBundle;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
@@ -29,6 +28,7 @@
import com.android.ims.ImsConfig;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.RemoteCallbackListExt;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -248,7 +248,8 @@
})
public @interface SetConfigResult {}
- private final RemoteCallbackList<IImsConfigCallback> mCallbacks = new RemoteCallbackList<>();
+ private final RemoteCallbackListExt<IImsConfigCallback> mCallbacks =
+ new RemoteCallbackListExt<>();
ImsConfigStub mImsConfigStub;
/**
@@ -289,7 +290,7 @@
if (mCallbacks == null) {
return;
}
- mCallbacks.broadcast(c -> {
+ mCallbacks.broadcastAction(c -> {
try {
c.onIntConfigChanged(item, value);
} catch (RemoteException e) {
@@ -303,7 +304,7 @@
if (mCallbacks == null) {
return;
}
- mCallbacks.broadcast(c -> {
+ mCallbacks.broadcastAction(c -> {
try {
c.onStringConfigChanged(item, value);
} catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index c0f16e5..14a64d2 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -20,7 +20,6 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.net.Uri;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.RegistrationManager;
@@ -29,6 +28,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.RemoteCallbackListExt;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -94,8 +94,8 @@
}
};
- private final RemoteCallbackList<IImsRegistrationCallback> mCallbacks
- = new RemoteCallbackList<>();
+ private final RemoteCallbackListExt<IImsRegistrationCallback> mCallbacks =
+ new RemoteCallbackListExt<>();
private final Object mLock = new Object();
// Locked on mLock
private @ImsRegistrationTech
@@ -129,7 +129,7 @@
*/
public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERED);
- mCallbacks.broadcast((c) -> {
+ mCallbacks.broadcastAction((c) -> {
try {
c.onRegistered(imsRadioTech);
} catch (RemoteException e) {
@@ -147,7 +147,7 @@
*/
public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERING);
- mCallbacks.broadcast((c) -> {
+ mCallbacks.broadcastAction((c) -> {
try {
c.onRegistering(imsRadioTech);
} catch (RemoteException e) {
@@ -175,7 +175,7 @@
*/
public final void onDeregistered(ImsReasonInfo info) {
updateToDisconnectedState(info);
- mCallbacks.broadcast((c) -> {
+ mCallbacks.broadcastAction((c) -> {
try {
c.onDeregistered(info);
} catch (RemoteException e) {
@@ -194,7 +194,7 @@
*/
public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
ImsReasonInfo info) {
- mCallbacks.broadcast((c) -> {
+ mCallbacks.broadcastAction((c) -> {
try {
c.onTechnologyChangeFailed(imsRadioTech, info);
} catch (RemoteException e) {
@@ -210,7 +210,7 @@
* @param uris
*/
public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
- mCallbacks.broadcast((c) -> {
+ mCallbacks.broadcastAction((c) -> {
try {
c.onSubscriberAssociatedUriChanged(uris);
} catch (RemoteException e) {
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index 4fc6a19..cfc803c 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -17,7 +17,6 @@
package com.android.ims;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.RemoteException;
import android.telephony.Rlog;
@@ -26,6 +25,8 @@
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
+import com.android.internal.telephony.util.HandlerExecutor;
+
import java.util.concurrent.Executor;
/**
diff --git a/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl b/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
index ee09c1c..76ebc0f 100644
--- a/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
+++ b/telephony/java/com/android/internal/telephony/ICarrierConfigLoader.aidl
@@ -30,7 +30,7 @@
PersistableBundle getConfigForSubIdWithFeature(int subId, String callingPackage,
String callingFeatureId);
- void overrideConfig(int subId, in PersistableBundle overrides);
+ void overrideConfig(int subId, in PersistableBundle overrides, boolean persistent);
void notifyConfigChangedForSubId(int subId);
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index ac4c8ec..9f4d066 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -594,4 +594,12 @@
* @return true for success, false otherwise.
*/
boolean setSmscAddressOnIccEfForSubscriber(String smsc, int subId, String callingPackage);
+
+ /**
+ * Get the capacity count of sms on Icc card.
+ *
+ * @param subId for subId which getSmsCapacityOnIcc is queried.
+ * @return capacity of ICC
+ */
+ int getSmsCapacityOnIccForSubscriber(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 9865f76..2430d82 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -211,4 +211,9 @@
String smsc, int subId, String callingPackage) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public int getSmsCapacityOnIccForSubscriber(int subId) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ec54ec..b99fe904 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -30,6 +30,7 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.CarrierRestrictionRules;
+import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.ClientRequestStats;
import android.telephony.IccOpenLogicalChannelResponse;
@@ -305,7 +306,8 @@
*/
boolean isDataConnectivityPossible(int subId);
- Bundle getCellLocation(String callingPkg, String callingFeatureId);
+ // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
+ CellIdentity getCellLocation(String callingPkg, String callingFeatureId);
/**
* Returns the ISO country code equivalent of the current registered
@@ -888,12 +890,13 @@
/**
* @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
*/
- boolean setImsService(int slotId, boolean isCarrierImsService, String packageName);
+ boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+ in int[] featureTypes, in String packageName);
/**
* @return the package name of the carrier/device ImsService associated with this slot.
*/
- String getImsService(int slotId, boolean isCarrierImsService);
+ String getBoundImsServicePackage(int slotIndex, boolean isCarrierImsService, int featureType);
/**
* Get the MmTelFeature state attached to this subscription id.
@@ -1133,7 +1136,7 @@
/**
* @hide
*/
- String[] getMergedSubscriberIdsFromGroup(int subId, String callingPackage);
+ String[] getMergedImsisFromGroup(int subId, String callingPackage);
/**
* Override the operator branding for the current ICCID.
diff --git a/telephony/java/com/android/internal/telephony/IccCardConstants.java b/telephony/java/com/android/internal/telephony/IccCardConstants.java
index 6ff27b1..25f03c2 100644
--- a/telephony/java/com/android/internal/telephony/IccCardConstants.java
+++ b/telephony/java/com/android/internal/telephony/IccCardConstants.java
@@ -15,6 +15,7 @@
*/
package com.android.internal.telephony;
+import android.content.Intent;
import android.telephony.TelephonyManager;
import dalvik.annotation.compat.UnsupportedAppUsage;
@@ -25,37 +26,38 @@
public class IccCardConstants {
/* The extra data for broadcasting intent INTENT_ICC_STATE_CHANGE */
- public static final String INTENT_KEY_ICC_STATE = "ss";
+ public static final String INTENT_KEY_ICC_STATE = Intent.EXTRA_SIM_STATE;
/* UNKNOWN means the ICC state is unknown */
- public static final String INTENT_VALUE_ICC_UNKNOWN = "UNKNOWN";
+ public static final String INTENT_VALUE_ICC_UNKNOWN = Intent.SIM_STATE_UNKNOWN;
/* NOT_READY means the ICC interface is not ready (eg, radio is off or powering on) */
- public static final String INTENT_VALUE_ICC_NOT_READY = "NOT_READY";
+ public static final String INTENT_VALUE_ICC_NOT_READY = Intent.SIM_STATE_NOT_READY;
/* ABSENT means ICC is missing */
- public static final String INTENT_VALUE_ICC_ABSENT = "ABSENT";
+ public static final String INTENT_VALUE_ICC_ABSENT = Intent.SIM_STATE_ABSENT;
/* PRESENT means ICC is present */
- public static final String INTENT_VALUE_ICC_PRESENT = "PRESENT";
+ public static final String INTENT_VALUE_ICC_PRESENT = Intent.SIM_STATE_PRESENT;
/* CARD_IO_ERROR means for three consecutive times there was SIM IO error */
- static public final String INTENT_VALUE_ICC_CARD_IO_ERROR = "CARD_IO_ERROR";
+ static public final String INTENT_VALUE_ICC_CARD_IO_ERROR = Intent.SIM_STATE_CARD_IO_ERROR;
/* CARD_RESTRICTED means card is present but not usable due to carrier restrictions */
- static public final String INTENT_VALUE_ICC_CARD_RESTRICTED = "CARD_RESTRICTED";
+ static public final String INTENT_VALUE_ICC_CARD_RESTRICTED = Intent.SIM_STATE_CARD_RESTRICTED;
/* LOCKED means ICC is locked by pin or by network */
- public static final String INTENT_VALUE_ICC_LOCKED = "LOCKED";
+ public static final String INTENT_VALUE_ICC_LOCKED = Intent.SIM_STATE_LOCKED;
/* READY means ICC is ready to access */
- public static final String INTENT_VALUE_ICC_READY = "READY";
+ public static final String INTENT_VALUE_ICC_READY = Intent.SIM_STATE_READY;
/* IMSI means ICC IMSI is ready in property */
- public static final String INTENT_VALUE_ICC_IMSI = "IMSI";
+ public static final String INTENT_VALUE_ICC_IMSI = Intent.SIM_STATE_IMSI;
/* LOADED means all ICC records, including IMSI, are loaded */
- public static final String INTENT_VALUE_ICC_LOADED = "LOADED";
+ public static final String INTENT_VALUE_ICC_LOADED = Intent.SIM_STATE_LOADED;
/* The extra data for broadcasting intent INTENT_ICC_STATE_CHANGE */
- public static final String INTENT_KEY_LOCKED_REASON = "reason";
+ public static final String INTENT_KEY_LOCKED_REASON = Intent.EXTRA_SIM_LOCKED_REASON;
/* PIN means ICC is locked on PIN1 */
- public static final String INTENT_VALUE_LOCKED_ON_PIN = "PIN";
+ public static final String INTENT_VALUE_LOCKED_ON_PIN = Intent.SIM_LOCKED_ON_PIN;
/* PUK means ICC is locked on PUK1 */
- public static final String INTENT_VALUE_LOCKED_ON_PUK = "PUK";
+ public static final String INTENT_VALUE_LOCKED_ON_PUK = Intent.SIM_LOCKED_ON_PUK;
/* NETWORK means ICC is locked on NETWORK PERSONALIZATION */
- public static final String INTENT_VALUE_LOCKED_NETWORK = "NETWORK";
+ public static final String INTENT_VALUE_LOCKED_NETWORK = Intent.SIM_LOCKED_NETWORK;
/* PERM_DISABLED means ICC is permanently disabled due to puk fails */
- public static final String INTENT_VALUE_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
+ public static final String INTENT_VALUE_ABSENT_ON_PERM_DISABLED =
+ Intent.SIM_ABSENT_ON_PERM_DISABLED;
/**
* This is combination of IccCardStatus.CardState and IccCardApplicationStatus.AppState
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
index f7f0f29..8640acc 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
@@ -16,14 +16,10 @@
package com.android.internal.telephony;
-import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
-import android.telephony.PreciseCallState;
import com.android.internal.telephony.PhoneConstants;
-import java.util.List;
-
public class PhoneConstantConversions {
/**
* Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
@@ -67,6 +63,8 @@
return TelephonyManager.DATA_CONNECTED;
case SUSPENDED:
return TelephonyManager.DATA_SUSPENDED;
+ case DISCONNECTING:
+ return TelephonyManager.DATA_DISCONNECTING;
default:
return TelephonyManager.DATA_DISCONNECTED;
}
@@ -84,6 +82,8 @@
return PhoneConstants.DataState.CONNECTED;
case TelephonyManager.DATA_SUSPENDED:
return PhoneConstants.DataState.SUSPENDED;
+ case TelephonyManager.DATA_DISCONNECTING:
+ return PhoneConstants.DataState.DISCONNECTING;
default:
return PhoneConstants.DataState.DISCONNECTED;
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index c19ae7b..fadb573 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -46,6 +46,7 @@
* <ul>
* <li>CONNECTED = IP traffic should be available</li>
* <li>CONNECTING = Currently setting up data connection</li>
+ * <li>DISCONNECTING = IP temporarily available</li>
* <li>DISCONNECTED = IP not available</li>
* <li>SUSPENDED = connection is created but IP traffic is
* temperately not available. i.e. voice call is in place
@@ -55,10 +56,15 @@
@UnsupportedAppUsage(implicitMember =
"values()[Lcom/android/internal/telephony/PhoneConstants$DataState;")
public enum DataState {
- @UnsupportedAppUsage CONNECTED,
- @UnsupportedAppUsage CONNECTING,
- @UnsupportedAppUsage DISCONNECTED,
- @UnsupportedAppUsage SUSPENDED;
+ @UnsupportedAppUsage
+ CONNECTED,
+ @UnsupportedAppUsage
+ CONNECTING,
+ @UnsupportedAppUsage
+ DISCONNECTED,
+ @UnsupportedAppUsage
+ SUSPENDED,
+ DISCONNECTING;
};
public static final String STATE_KEY = "state";
@@ -91,20 +97,12 @@
public static final String PHONE_NAME_KEY = "phoneName";
public static final String DATA_NETWORK_TYPE_KEY = "networkType";
- public static final String DATA_FAILURE_CAUSE_KEY = "failCause";
public static final String DATA_APN_TYPE_KEY = "apnType";
public static final String DATA_APN_KEY = "apn";
- public static final String DATA_LINK_PROPERTIES_KEY = "linkProperties";
- public static final String DATA_NETWORK_CAPABILITIES_KEY = "networkCapabilities";
- public static final String DATA_IFACE_NAME_KEY = "iface";
- public static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
- public static final String DATA_NETWORK_ROAMING_KEY = "networkRoaming";
public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
- public static final String REASON_LINK_PROPERTIES_CHANGED = "linkPropertiesChanged";
-
/**
* Return codes for supplyPinReturnResult and
* supplyPukReturnResult APIs
@@ -154,6 +152,8 @@
public static final String APN_TYPE_EMERGENCY = "emergency";
/** APN type for Mission Critical Services */
public static final String APN_TYPE_MCX = "mcx";
+ /** APN type for XCAP */
+ public static final String APN_TYPE_XCAP = "xcap";
/** Array of all APN types */
public static final String[] APN_TYPES = {APN_TYPE_DEFAULT,
APN_TYPE_MMS,
@@ -165,7 +165,8 @@
APN_TYPE_CBS,
APN_TYPE_IA,
APN_TYPE_EMERGENCY,
- APN_TYPE_MCX
+ APN_TYPE_MCX,
+ APN_TYPE_XCAP,
};
public static final int RIL_CARD_MAX_APPS = 8;
@@ -182,10 +183,6 @@
public static final String SLOT_KEY = "slot";
- /** Fired when a subscriptions phone state changes. */
- public static final String ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED =
- "android.intent.action.SUBSCRIPTION_PHONE_STATE";
-
// FIXME: This is used to pass a subId via intents, we need to look at its usage, which is
// FIXME: extensive, and see if this should be an array of all active subId's or ...?
/**
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 22168c5..9cbcd7f 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -489,6 +489,8 @@
int RIL_REQUEST_EMERGENCY_DIAL = 205;
int RIL_REQUEST_GET_PHONE_CAPABILITY = 206;
int RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG = 207;
+ int RIL_REQUEST_ENABLE_UICC_APPLICATIONS = 208;
+ int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -552,4 +554,5 @@
int RIL_UNSOL_ICC_SLOT_STATUS = 1100;
int RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG = 1101;
int RIL_UNSOL_EMERGENCY_NUMBER_LIST = 1102;
+ int RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED = 1103;
}
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
index dcea9bb..d672642 100644
--- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -18,11 +18,11 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import android.os.Build;
import 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;
@@ -30,7 +30,7 @@
public class Sms7BitEncodingTranslator {
private static final String TAG = "Sms7BitEncodingTranslator";
@UnsupportedAppUsage
- private static final boolean DBG = Build.IS_DEBUGGABLE ;
+ private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE;
private static boolean mIs7BitTranslationTableLoaded = false;
private static SparseIntArray mTranslationTable = null;
@UnsupportedAppUsage
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 8b62872..024b640 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -18,6 +18,7 @@
import android.content.Intent;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
/**
* The intents that the telephony services broadcast.
@@ -99,7 +100,7 @@
* by the system.
*/
public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
- = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+ = TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED;
/**
* <p>Broadcast Action: The emergency call state is changed.
@@ -120,7 +121,7 @@
* by the system.
*/
public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
- = "android.intent.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
@@ -225,41 +226,6 @@
public static final String EXTRA_REBROADCAST_ON_UNLOCK= "rebroadcastOnUnlock";
/**
- * 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.
- */
- public static final String ACTION_NETWORK_SET_TIME = "android.intent.action.NETWORK_SET_TIME";
-
-
- /**
- * Broadcast Action: The timezone 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-zone</em> - The java.util.TimeZone.getID() value identifying the new time
- * zone.</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.
- */
- public static final String ACTION_NETWORK_SET_TIMEZONE
- = "android.intent.action.NETWORK_SET_TIMEZONE";
-
- /**
* <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms
* <p class="note">.
* This is to pop up a notice to show user that the phone is in emergency callback mode
@@ -269,7 +235,7 @@
* by the system.
*/
public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
- = "com.android.internal.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS";
+ = TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS;
/**
* <p>Broadcast Action: Indicates that the action is forbidden by network.
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
deleted file mode 100644
index c16eafb..0000000
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ /dev/null
@@ -1,521 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.gsm;
-
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
-import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.telephony.CbGeoUtils;
-import android.telephony.CbGeoUtils.Circle;
-import android.telephony.CbGeoUtils.Geometry;
-import android.telephony.CbGeoUtils.LatLng;
-import android.telephony.CbGeoUtils.Polygon;
-import android.telephony.Rlog;
-import android.telephony.SmsCbLocation;
-import android.telephony.SmsCbMessage;
-import android.telephony.SubscriptionManager;
-import android.util.Pair;
-
-import com.android.internal.R;
-import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.SmsConstants;
-import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
-import com.android.internal.telephony.gsm.SmsCbHeader.DataCodingScheme;
-
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
- * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
- */
-public class GsmSmsCbMessage {
- private static final String TAG = GsmSmsCbMessage.class.getSimpleName();
-
- private static final char CARRIAGE_RETURN = 0x0d;
-
- private static final int PDU_BODY_PAGE_LENGTH = 82;
-
- /** Utility class with only static methods. */
- private GsmSmsCbMessage() { }
-
- /**
- * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
- * so we have to show the pre-built messages to the user.
- *
- * @param context Device context
- * @param category ETWS message category defined in SmsCbConstants
- * @return ETWS text message in string. Return an empty string if no match.
- */
- private static String getEtwsPrimaryMessage(Context context, int category) {
- final Resources r = context.getResources();
- switch (category) {
- case ETWS_WARNING_TYPE_EARTHQUAKE:
- return r.getString(R.string.etws_primary_default_message_earthquake);
- case ETWS_WARNING_TYPE_TSUNAMI:
- return r.getString(R.string.etws_primary_default_message_tsunami);
- case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
- return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
- case ETWS_WARNING_TYPE_TEST_MESSAGE:
- return r.getString(R.string.etws_primary_default_message_test);
- case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
- return r.getString(R.string.etws_primary_default_message_others);
- default:
- return "";
- }
- }
-
- /**
- * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
- *
- * @param pdus PDU bytes
- * @slotIndex slotIndex for which received sms cb message
- */
- public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
- SmsCbLocation location, byte[][] pdus, int slotIndex)
- throws IllegalArgumentException {
- SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
- Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- int[] subIds = sm.getSubscriptionIds(slotIndex);
- if (subIds != null && subIds.length > 0) {
- subId = subIds[0];
- }
-
- long receivedTimeMillis = System.currentTimeMillis();
- if (header.isEtwsPrimaryNotification()) {
- // ETSI TS 23.041 ETWS Primary Notification message
- // ETWS primary message only contains 4 fields including serial number,
- // message identifier, warning type, and warning security information.
- // There is no field for the content/text so we get the text from the resources.
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
- header.getSerialNumber(), location, header.getServiceCategory(), null,
- getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
- SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
- header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis, slotIndex,
- subId);
- } else if (header.isUmtsFormat()) {
- // UMTS format has only 1 PDU
- byte[] pdu = pdus[0];
- Pair<String, String> cbData = parseUmtsBody(header, pdu);
- String language = cbData.first;
- String body = cbData.second;
- int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
- : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
- int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
- int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH
- + 1 // number of pages
- + (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data
-
- // Has Warning Area Coordinates information
- List<Geometry> geometries = null;
- int maximumWaitingTimeSec = 255;
- if (pdu.length > wacDataOffset) {
- try {
- Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
- wacDataOffset);
- maximumWaitingTimeSec = wac.first;
- geometries = wac.second;
- } catch (Exception ex) {
- // Catch the exception here, the message will be considered as having no WAC
- // information which means the message will be broadcasted directly.
- Rlog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString());
- }
- }
-
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(), location,
- header.getServiceCategory(), language, body, priority,
- header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
- receivedTimeMillis, slotIndex, subId);
- } else {
- String language = null;
- StringBuilder sb = new StringBuilder();
- for (byte[] pdu : pdus) {
- Pair<String, String> p = parseGsmBody(header, pdu);
- language = p.first;
- sb.append(p.second);
- }
- int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
- : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
-
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(), location,
- header.getServiceCategory(), language, sb.toString(), priority,
- header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
- receivedTimeMillis, slotIndex, subId);
- }
- }
-
- /**
- * Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message.
- *
- * WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network
- * to direct devices to perform a geo-fencing check on selected alerts.
- *
- * WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4
- * 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as
- * defined in TS 23.041.
- * 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced
- * WEA messages).
- * 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced
- * WEA message.
- * @param pdu cell broadcast pdu, including the header
- * @return {@link GeoFencingTriggerMessage} instance
- */
- public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) {
- try {
- // Header length + 1(number of page). ATIS-0700041 define the number of page of
- // geo-fencing trigger message is 1.
- int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1;
-
- BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset);
- int type = bitReader.read(4);
- int length = bitReader.read(7);
- // Skip the remained 5 bits
- bitReader.skip();
-
- int messageIdentifierCount = (length - 2) * 8 / 32;
- List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>();
- for (int i = 0; i < messageIdentifierCount; i++) {
- // Both messageIdentifier and serialNumber are 16 bits integers.
- // ATIS-0700041 Section 5.1.6
- int messageIdentifier = bitReader.read(16);
- int serialNumber = bitReader.read(16);
- cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber));
- }
- return new GeoFencingTriggerMessage(type, cbIdentifiers);
- } catch (Exception ex) {
- Rlog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString());
- return null;
- }
- }
-
- /**
- * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
- *
- * @param pdu Warning Area Coordinates TLV.
- * @param wacOffset the offset of Warning Area Coordinates TLV.
- * @return a pair with the first element is maximum wait time and the second is the broadcast
- * area. The default value of the maximum wait time is 255 which means use the device default
- * value.
- */
- private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
- byte[] pdu, int wacOffset) {
- // little-endian
- int wacDataLength = ((pdu[wacOffset + 1] & 0xff) << 8) | (pdu[wacOffset] & 0xff);
- int offset = wacOffset + 2;
-
- if (offset + wacDataLength > pdu.length) {
- throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at"
- + "least " + offset + wacDataLength + ", actual is " + pdu.length);
- }
-
- BitStreamReader bitReader = new BitStreamReader(pdu, offset);
-
- int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
-
- List<Geometry> geo = new ArrayList<>();
- int remainedBytes = wacDataLength;
- while (remainedBytes > 0) {
- int type = bitReader.read(4);
- int length = bitReader.read(10);
- remainedBytes -= length;
- // Skip the 2 remained bits
- bitReader.skip();
-
- switch (type) {
- case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
- maximumWaitTimeSec = bitReader.read(8);
- break;
- case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
- List<LatLng> latLngs = new ArrayList<>();
- // Each coordinate is represented by 44 bits integer.
- // ATIS-0700041 5.2.4 Coordinate coding
- int n = (length - 2) * 8 / 44;
- for (int i = 0; i < n; i++) {
- latLngs.add(getLatLng(bitReader));
- }
- // Skip the padding bits
- bitReader.skip();
- geo.add(new Polygon(latLngs));
- break;
- case CbGeoUtils.GEOMETRY_TYPE_CIRCLE:
- LatLng center = getLatLng(bitReader);
- // radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the
- // distance unit during geo-fencing.
- // ATIS-0700041 5.2.5 radius coding
- double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0;
- geo.add(new Circle(center, radius));
- break;
- default:
- throw new IllegalArgumentException("Unsupported geoType = " + type);
- }
- }
- return new Pair(maximumWaitTimeSec, geo);
- }
-
- /**
- * The coordinate is (latitude, longitude), represented by a 44 bits integer.
- * The coding is defined in ATIS-0700041 5.2.4
- * @param bitReader
- * @return coordinate (latitude, longitude)
- */
- private static LatLng getLatLng(BitStreamReader bitReader) {
- // wacLatitude = floor(((latitude + 90) / 180) * 2^22)
- // wacLongitude = floor(((longitude + 180) / 360) * 2^22)
- int wacLat = bitReader.read(22);
- int wacLng = bitReader.read(22);
-
- // latitude = wacLatitude * 180 / 2^22 - 90
- // longitude = wacLongitude * 360 / 2^22 -180
- return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180));
- }
-
- /**
- * Parse and unpack the UMTS body text according to the encoding in the data coding scheme.
- *
- * @param header the message header to use
- * @param pdu the PDU to decode
- * @return a pair of string containing the language and body of the message in order
- */
- private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) {
- // Payload may contain multiple pages
- int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
- String language = header.getDataCodingSchemeStructedData().language;
-
- if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
- * nrPages) {
- throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
- + nrPages + " pages");
- }
-
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < nrPages; i++) {
- // Each page is 82 bytes followed by a length octet indicating
- // the number of useful octets within those 82
- int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
- int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
-
- if (length > PDU_BODY_PAGE_LENGTH) {
- throw new IllegalArgumentException("Page length " + length
- + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
- }
-
- Pair<String, String> p = unpackBody(pdu, offset, length,
- header.getDataCodingSchemeStructedData());
- language = p.first;
- sb.append(p.second);
- }
- return new Pair(language, sb.toString());
-
- }
-
- /**
- * Parse and unpack the GSM body text according to the encoding in the data coding scheme.
- * @param header the message header to use
- * @param pdu the PDU to decode
- * @return a pair of string containing the language and body of the message in order
- */
- private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) {
- // Payload is one single page
- int offset = SmsCbHeader.PDU_HEADER_LENGTH;
- int length = pdu.length - offset;
- return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData());
- }
-
- /**
- * Unpack body text from the pdu using the given encoding, position and length within the pdu.
- *
- * @param pdu The pdu
- * @param offset Position of the first byte to unpack
- * @param length Number of bytes to unpack
- * @param dcs data coding scheme
- * @return a Pair of Strings containing the language and body of the message
- */
- private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length,
- DataCodingScheme dcs) {
- String body = null;
-
- String language = dcs.language;
- switch (dcs.encoding) {
- case SmsConstants.ENCODING_7BIT:
- body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
-
- if (dcs.hasLanguageIndicator && body != null && body.length() > 2) {
- // Language is two GSM characters followed by a CR.
- // The actual body text is offset by 3 characters.
- language = body.substring(0, 2);
- body = body.substring(3);
- }
- break;
-
- case SmsConstants.ENCODING_16BIT:
- if (dcs.hasLanguageIndicator && pdu.length >= offset + 2) {
- // Language is two GSM characters.
- // The actual body text is offset by 2 bytes.
- language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
- offset += 2;
- length -= 2;
- }
-
- try {
- body = new String(pdu, offset, (length & 0xfffe), "utf-16");
- } catch (UnsupportedEncodingException e) {
- // Apparently it wasn't valid UTF-16.
- throw new IllegalArgumentException("Error decoding UTF-16 message", e);
- }
- break;
-
- default:
- break;
- }
-
- if (body != null) {
- // Remove trailing carriage return
- for (int i = body.length() - 1; i >= 0; i--) {
- if (body.charAt(i) != CARRIAGE_RETURN) {
- body = body.substring(0, i + 1);
- break;
- }
- }
- } else {
- body = "";
- }
-
- return new Pair<String, String>(language, body);
- }
-
- /** A class use to facilitate the processing of bits stream data. */
- private static final class BitStreamReader {
- /** The bits stream represent by a bytes array. */
- private final byte[] mData;
-
- /** The offset of the current byte. */
- private int mCurrentOffset;
-
- /**
- * The remained bits of the current byte which have not been read. The most significant
- * will be read first, so the remained bits are always the least significant bits.
- */
- private int mRemainedBit;
-
- /**
- * Constructor
- * @param data bit stream data represent by byte array.
- * @param offset the offset of the first byte.
- */
- BitStreamReader(byte[] data, int offset) {
- mData = data;
- mCurrentOffset = offset;
- mRemainedBit = 8;
- }
-
- /**
- * Read the first {@code count} bits.
- * @param count the number of bits need to read
- * @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no
- * greater than 32.
- */
- public int read(int count) throws IndexOutOfBoundsException {
- int val = 0;
- while (count > 0) {
- if (count >= mRemainedBit) {
- val <<= mRemainedBit;
- val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1);
- count -= mRemainedBit;
- mRemainedBit = 8;
- ++mCurrentOffset;
- } else {
- val <<= count;
- val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1))
- >> (mRemainedBit - count);
- mRemainedBit -= count;
- count = 0;
- }
- }
- return val;
- }
-
- /**
- * Skip the current bytes if the remained bits is less than 8. This is useful when
- * processing the padding or reserved bits.
- */
- public void skip() {
- if (mRemainedBit < 8) {
- mRemainedBit = 8;
- ++mCurrentOffset;
- }
- }
- }
-
- /**
- * Part of a GSM SMS cell broadcast message which may trigger geo-fencing logic.
- * @hide
- */
- public static final class GeoFencingTriggerMessage {
- /**
- * Indicate the list of active alerts share their warning area coordinates which means the
- * broadcast area is the union of the broadcast areas of the active alerts in this list.
- */
- public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2;
-
- public final int type;
- public final List<CellBroadcastIdentity> cbIdentifiers;
-
- GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) {
- this.type = type;
- this.cbIdentifiers = cbIdentifiers;
- }
-
- /**
- * Whether the trigger message indicates that the broadcast areas are shared between all
- * active alerts.
- * @return true if broadcast areas are to be shared
- */
- boolean shouldShareBroadcastArea() {
- return type == TYPE_ACTIVE_ALERT_SHARE_WAC;
- }
-
- static final class CellBroadcastIdentity {
- public final int messageIdentifier;
- public final int serialNumber;
- CellBroadcastIdentity(int messageIdentifier, int serialNumber) {
- this.messageIdentifier = messageIdentifier;
- this.serialNumber = serialNumber;
- }
- }
-
- @Override
- public String toString() {
- String identifiers = cbIdentifiers.stream()
- .map(cbIdentifier ->String.format("(msgId = %d, serial = %d)",
- cbIdentifier.messageIdentifier, cbIdentifier.serialNumber))
- .collect(Collectors.joining(","));
- return "triggerType=" + type + " identifiers=" + identifiers;
- }
- }
-}
diff --git a/telephony/java/com/android/internal/telephony/util/HandlerExecutor.java b/telephony/java/com/android/internal/telephony/util/HandlerExecutor.java
new file mode 100644
index 0000000..8a25457
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/util/HandlerExecutor.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * @hide
+ */
+public class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(@NonNull Handler handler) {
+ if (handler == null) {
+ throw new NullPointerException();
+ }
+ mHandler = handler;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/util/RemoteCallbackListExt.java b/telephony/java/com/android/internal/telephony/util/RemoteCallbackListExt.java
new file mode 100644
index 0000000..d66bda9
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/util/RemoteCallbackListExt.java
@@ -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 com.android.internal.telephony.util;
+
+import android.os.IInterface;
+import android.os.RemoteCallbackList;
+
+import java.util.function.Consumer;
+
+/**
+ * Extension of RemoteCallbackList
+ * @param <E> defines the type of registered callbacks
+ */
+public class RemoteCallbackListExt<E extends IInterface> extends RemoteCallbackList<E> {
+ /**
+ * Performs {@code action} on each callback, calling
+ * {@link RemoteCallbackListExt#beginBroadcast()}
+ * /{@link RemoteCallbackListExt#finishBroadcast()} before/after looping
+ * @param action to be performed on each callback
+ *
+ */
+ public void broadcastAction(Consumer<E> action) {
+ int itemCount = beginBroadcast();
+ try {
+ for (int i = 0; i < itemCount; i++) {
+ action.accept(getBroadcastItem(i));
+ }
+ } finally {
+ finishBroadcast();
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java
index 306b9ee..2abcc76 100644
--- a/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java
@@ -22,6 +22,8 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Binder;
+import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -108,4 +110,31 @@
Binder.restoreCallingIdentity(callingIdentity);
}
}
+
+ /**
+ * Filter values in bundle to only basic types.
+ */
+ public static Bundle filterValues(Bundle bundle) {
+ Bundle ret = new Bundle(bundle);
+ for (String key : bundle.keySet()) {
+ Object value = bundle.get(key);
+ if ((value instanceof Integer) || (value instanceof Long)
+ || (value instanceof Double) || (value instanceof String)
+ || (value instanceof int[]) || (value instanceof long[])
+ || (value instanceof double[]) || (value instanceof String[])
+ || (value instanceof PersistableBundle) || (value == null)
+ || (value instanceof Boolean) || (value instanceof boolean[])) {
+ continue;
+ }
+ if (value instanceof Bundle) {
+ ret.putBundle(key, filterValues((Bundle) value));
+ continue;
+ }
+ if (value.getClass().getName().startsWith("android.")) {
+ continue;
+ }
+ ret.remove(key);
+ }
+ return ret;
+ }
}
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index aa4174a..248c117 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -28,8 +28,9 @@
":framework_native_aidl",
],
libs: [
- "framework-all",
+ "framework",
"app-compat-annotations",
+ "unsupportedappusage",
],
api_packages: [
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 0208c3a..9d913b9 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -16,6 +16,7 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
@@ -211,6 +212,15 @@
throw new UnsupportedOperationException();
}
+ /**
+ * {@inheritDoc Context#getCrateDir()}
+ * @hide
+ */
+ @Override
+ public File getCrateDir(@NonNull String crateId) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public File getNoBackupFilesDir() {
throw new UnsupportedOperationException();
diff --git a/tests/ActivityManagerPerfTests/stub-app/Android.bp b/tests/ActivityManagerPerfTests/stub-app/Android.bp
new file mode 100644
index 0000000..a3c1f5b
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/Android.bp
@@ -0,0 +1,68 @@
+// 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: "ActivityManagerPerfTestsStubApp1",
+ static_libs: ["ActivityManagerPerfTestsUtils"],
+ srcs: [
+ "src/**/*.java",
+ ],
+ resource_dirs: [
+ "app1/res",
+ "res",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ aaptflags: [
+ "--rename-manifest-package com.android.stubs.am1",
+ "--auto-add-overlay",
+ ],
+}
+
+android_test_helper_app {
+ name: "ActivityManagerPerfTestsStubApp2",
+ static_libs: ["ActivityManagerPerfTestsUtils"],
+ srcs: [
+ "src/**/*.java",
+ ],
+ resource_dirs: [
+ "app2/res",
+ "res",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ aaptflags: [
+ "--rename-manifest-package com.android.stubs.am2",
+ "--auto-add-overlay",
+ ],
+}
+
+android_test_helper_app {
+ name: "ActivityManagerPerfTestsStubApp3",
+ static_libs: ["ActivityManagerPerfTestsUtils"],
+ srcs: [
+ "src/**/*.java",
+ ],
+ resource_dirs: [
+ "app3/res",
+ "res",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ aaptflags: [
+ "--rename-manifest-package com.android.stubs.am3",
+ "--auto-add-overlay",
+ ],
+}
+
diff --git a/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml
new file mode 100644
index 0000000..a57f64c
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?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.stubs.am">
+
+ <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
+ <application android:label="Android TestCase" >
+ <provider
+ android:authorities="@string/authority"
+ android:name=".TestContentProvider"
+ android:exported="true" />
+ <receiver
+ android:name=".TestBroadcastReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.stubs.am.ACTION_BROADCAST_TEST" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </receiver>
+ <service
+ android:name=".InitService"
+ android:exported="true" />
+ <service
+ android:name=".TestService"
+ android:exported="true" />
+ <activity
+ android:name=".TestActivity"
+ android:excludeFromRecents="true"
+ android:turnScreenOn="true"
+ android:launchMode="singleTask">
+ <intent-filter>
+ <action android:name="com.android.stubs.am.ACTION_START_TEST_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
+
diff --git a/tests/utils/testutils/java/test/package-info.java b/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml
similarity index 73%
copy from tests/utils/testutils/java/test/package-info.java
copy to tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml
index c34d7b2..667472d 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/tests/ActivityManagerPerfTests/stub-app/app1/res/values/config.xml
@@ -1,4 +1,5 @@
-/*
+<?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");
@@ -12,10 +13,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
+-->
-/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="authority" translatable="false">com.android.stubs.am1.testapp</string>
+</resources>
diff --git a/tests/utils/testutils/java/test/package-info.java b/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml
similarity index 73%
copy from tests/utils/testutils/java/test/package-info.java
copy to tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml
index c34d7b2..0852735 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/tests/ActivityManagerPerfTests/stub-app/app2/res/values/config.xml
@@ -1,4 +1,5 @@
-/*
+<?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");
@@ -12,10 +13,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
+-->
-/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="authority" translatable="false">com.android.stubs.am2.testapp</string>
+</resources>
diff --git a/tests/utils/testutils/java/test/package-info.java b/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml
similarity index 73%
copy from tests/utils/testutils/java/test/package-info.java
copy to tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml
index c34d7b2..6895d72 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/tests/ActivityManagerPerfTests/stub-app/app3/res/values/config.xml
@@ -1,4 +1,5 @@
-/*
+<?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");
@@ -12,10 +13,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
+-->
-/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="authority" translatable="false">com.android.stubs.am3.testapp</string>
+</resources>
diff --git a/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml b/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.xml
new file mode 100644
index 0000000..f79f006
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/res/layout/activity_content.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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java
new file mode 100644
index 0000000..18fdc44
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/InitService.java
@@ -0,0 +1,301 @@
+/*
+ * 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.stubs.am;
+
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_ACQUIRE_CONTENT_PROVIDER;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_BIND_SERVICE;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_RELEASE_CONTENT_PROVIDER;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_SEND_BROADCAST;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_START_ACTIVITY;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_STOP_ACTIVITY;
+import static com.android.frameworks.perftests.am.util.Constants.COMMAND_UNBIND_SERVICE;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.ICommandReceiver;
+
+public class InitService extends Service {
+ private static final String TAG = "InitService";
+ public static final boolean VERBOSE = false;
+
+ private static class Stub extends ICommandReceiver.Stub {
+ private final Context mContext;
+ private final Messenger mCallback;
+ private final Handler mHandler;
+ private final Messenger mMessenger;
+ final ArrayMap<String, MyServiceConnection> mServices =
+ new ArrayMap<String, MyServiceConnection>();
+ final ArrayMap<Uri, IContentProvider> mProviders =
+ new ArrayMap<Uri, IContentProvider>();
+
+ Stub(Context context, Messenger callback) {
+ mContext = context;
+ mCallback = callback;
+ HandlerThread thread = new HandlerThread("result handler");
+ thread.start();
+ mHandler = new H(thread.getLooper());
+ mMessenger = new Messenger(mHandler);
+ }
+
+ private class H extends Handler {
+ H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == Constants.MSG_DEFAULT) {
+ if (VERBOSE) {
+ Log.i(TAG, "H: received seq=" + msg.arg1 + ", result=" + msg.arg2);
+ }
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, msg.arg1, msg.arg2, null);
+ } else if (msg.what == Constants.MSG_UNBIND_DONE) {
+ if (VERBOSE) {
+ Log.i(TAG, "H: received unbind=" + msg.obj);
+ }
+ synchronized (InitService.sStub) {
+ Bundle b = (Bundle) msg.obj;
+ String pkg = b.getString(Constants.EXTRA_SOURCE_PACKAGE, "");
+ MyServiceConnection c = mServices.remove(pkg);
+ if (c != null) {
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, c.mSeq,
+ Constants.RESULT_NO_ERROR, null);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void sendCommand(int command, int seq, String sourcePackage, String targetPackage,
+ int flags, Bundle bundle) {
+ if (VERBOSE) {
+ Log.i(TAG, "Received command=" + command + ", seq=" + seq + ", from="
+ + sourcePackage + ", to=" + targetPackage + ", flags=" + flags);
+ }
+ switch (command) {
+ case COMMAND_BIND_SERVICE:
+ handleBindService(seq, targetPackage, flags, bundle);
+ break;
+ case COMMAND_UNBIND_SERVICE:
+ handleUnbindService(seq, targetPackage);
+ break;
+ case COMMAND_ACQUIRE_CONTENT_PROVIDER:
+ acquireProvider(seq, bundle.getParcelable(Constants.EXTRA_URI));
+ break;
+ case COMMAND_RELEASE_CONTENT_PROVIDER:
+ releaseProvider(seq, bundle.getParcelable(Constants.EXTRA_URI));
+ break;
+ case COMMAND_SEND_BROADCAST:
+ sendBroadcast(seq, targetPackage);
+ break;
+ case COMMAND_START_ACTIVITY:
+ startActivity(seq, targetPackage);
+ break;
+ case COMMAND_STOP_ACTIVITY:
+ stopActivity(seq, targetPackage);
+ break;
+ }
+ }
+
+ private void handleBindService(int seq, String targetPackage, int flags, Bundle bundle) {
+ Intent intent = new Intent();
+ intent.setClassName(targetPackage, "com.android.stubs.am.TestService");
+ intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+ if (bundle != null) {
+ intent.putExtras(bundle);
+ }
+ synchronized (this) {
+ if (!mServices.containsKey(targetPackage)) {
+ MyServiceConnection c = new MyServiceConnection(mCallback);
+ c.mSeq = seq;
+ if (!mContext.bindService(intent, c, flags)) {
+ Log.e(TAG, "Unable to bind to service in " + targetPackage);
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+ Constants.RESULT_ERROR, null);
+ } else {
+ if (VERBOSE) {
+ Log.i(TAG, "Bind to service " + intent);
+ }
+ mServices.put(targetPackage, c);
+ }
+ } else {
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+ Constants.RESULT_NO_ERROR, null);
+ }
+ }
+ }
+
+ private void handleUnbindService(int seq, String target) {
+ MyServiceConnection c = null;
+ synchronized (this) {
+ c = mServices.get(target);
+ }
+ if (c != null) {
+ c.mSeq = seq;
+ mContext.unbindService(c);
+ }
+ }
+
+ private void acquireProvider(int seq, Uri uri) {
+ ContentResolver resolver = mContext.getContentResolver();
+ IContentProvider provider = resolver.acquireProvider(uri);
+ if (provider != null) {
+ synchronized (mProviders) {
+ mProviders.put(uri, provider);
+ }
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+ Constants.RESULT_NO_ERROR, null);
+ } else {
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+ Constants.RESULT_ERROR, null);
+ }
+ }
+
+ private void releaseProvider(int seq, Uri uri) {
+ ContentResolver resolver = mContext.getContentResolver();
+ IContentProvider provider;
+ synchronized (mProviders) {
+ provider = mProviders.get(uri);
+ }
+ if (provider != null) {
+ resolver.releaseProvider(provider);
+ synchronized (mProviders) {
+ mProviders.remove(uri);
+ }
+ }
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+ Constants.RESULT_NO_ERROR, null);
+ }
+
+ private void sendBroadcast(final int seq, String targetPackage) {
+ Intent intent = new Intent(Constants.STUB_ACTION_BROADCAST);
+ intent.setPackage(targetPackage);
+ mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, seq,
+ Constants.RESULT_NO_ERROR, null);
+ }
+ }, null, 0, null, null);
+ }
+
+ private void startActivity(int seq, String targetPackage) {
+ Intent intent = new Intent(Constants.STUB_ACTION_ACTIVITY);
+ intent.setClassName(targetPackage, "com.android.stubs.am.TestActivity");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ intent.putExtra(Constants.EXTRA_ARG1, seq);
+ intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+ mContext.startActivity(intent);
+ }
+
+ private void stopActivity(int seq, String targetPackage) {
+ Intent intent = new Intent(Constants.STUB_ACTION_ACTIVITY);
+ intent.setClassName(targetPackage, "com.android.stubs.am.TestActivity");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ intent.putExtra(Constants.EXTRA_REQ_FINISH_ACTIVITY, true);
+ intent.putExtra(Constants.EXTRA_ARG1, seq);
+ intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, mMessenger);
+ mContext.startActivity(intent);
+ }
+ };
+
+ private static void sendResult(Messenger callback, int what, int seq, int result, Object obj) {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = seq;
+ msg.arg2 = result;
+ msg.obj = obj;
+ try {
+ if (VERBOSE) {
+ Log.i(TAG, "Sending result seq=" + seq + ", result=" + result);
+ }
+ callback.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sending result back", e);
+ }
+ msg.recycle();
+ }
+
+ private static class MyServiceConnection implements ServiceConnection {
+ private Messenger mCallback;
+ int mSeq;
+
+ MyServiceConnection(Messenger callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, mSeq,
+ Constants.RESULT_NO_ERROR, null);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (sStub) {
+ MyServiceConnection c = sStub.mServices.remove(name.getPackageName());
+ if (c != null) {
+ sendResult(mCallback, Constants.REPLY_COMMAND_RESULT, c.mSeq,
+ Constants.RESULT_NO_ERROR, null);
+ }
+ }
+ }
+ }
+
+ private static Stub sStub = null;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new Binder();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Messenger callback = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+ if (sStub == null) {
+ sStub = new Stub(getApplicationContext(), callback);
+ }
+
+ Bundle extras = new Bundle();
+ extras.putString(Constants.EXTRA_SOURCE_PACKAGE, getPackageName());
+ extras.putBinder(Constants.EXTRA_RECEIVER_CALLBACK, sStub);
+ sendResult(callback, Constants.REPLY_PACKAGE_START_RESULT,
+ intent.getIntExtra(Constants.EXTRA_SEQ, -1), 0, extras);
+ return START_NOT_STICKY;
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.java
new file mode 100644
index 0000000..f7ea356
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestActivity.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 com.android.stubs.am;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+
+public class TestActivity extends Activity {
+ private static final String TAG = "TestActivity";
+ private static final boolean VERBOSE = InitService.VERBOSE;
+
+ private Messenger mResultTo;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ if (VERBOSE) {
+ Log.i(TAG, getPackageName() + " onCreate()");
+ }
+ setContentView(R.layout.activity_content);
+ mResultTo = getIntent().getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ mResultTo = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+ if (intent.getBooleanExtra(Constants.EXTRA_REQ_FINISH_ACTIVITY, false)) {
+ if (VERBOSE) {
+ Log.i(TAG, getPackageName() + " finishing activity");
+ }
+ finish();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (VERBOSE) {
+ Log.i(TAG, getPackageName() + " onResume()");
+ }
+ sendResult(Constants.RESULT_NO_ERROR);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (VERBOSE) {
+ Log.i(TAG, getPackageName() + " onDestroy()");
+ }
+ sendResult(Constants.RESULT_NO_ERROR);
+ }
+
+ private void sendResult(int result) {
+ Message msg = Message.obtain();
+ msg.arg1 = getIntent().getIntExtra(Constants.EXTRA_ARG1, -1);
+ msg.arg2 = result;
+ try {
+ mResultTo.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sending result back", e);
+ }
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.java
new file mode 100644
index 0000000..36c7a0a
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestBroadcastReceiver.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.stubs.am;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class TestBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "TestBroadcastReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, context.getPackageName() + " received broadcast: " + intent);
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java
new file mode 100644
index 0000000..4fdbf1f
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestContentProvider.java
@@ -0,0 +1,62 @@
+/*
+ * 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.stubs.am;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+public class TestContentProvider extends ContentProvider {
+ private static final String TAG = "TestContentProvider";
+ private static final boolean VERBOSE = InitService.VERBOSE;
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public boolean onCreate() {
+ if (VERBOSE) {
+ Log.i(TAG, getContext().getPackageName() + " onCreate()");
+ }
+ return false;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.java
new file mode 100644
index 0000000..ba220e0
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/stub-app/src/com/android/stubs/am/TestService.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 com.android.stubs.am;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+
+public class TestService extends Service {
+ private static final String TAG = "TestService";
+ private static final boolean VERBOSE = InitService.VERBOSE;
+
+ private Binder mStub = new Binder();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (VERBOSE) {
+ Log.i(TAG, getPackageName() + " onBind()");
+ }
+ return mStub;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (VERBOSE) {
+ Log.i(TAG, getPackageName() + " onStartCommand()");
+ }
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (VERBOSE) {
+ Log.i(TAG, getPackageName() + " onUnbind()");
+ }
+ Messenger messenger = intent.getParcelableExtra(Constants.EXTRA_RECEIVER_CALLBACK);
+ Message msg = Message.obtain();
+ msg.what = Constants.MSG_UNBIND_DONE;
+ Bundle b = new Bundle();
+ b.putString(Constants.EXTRA_SOURCE_PACKAGE, getPackageName());
+ msg.obj = b;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in sending result back", e);
+ }
+ return false;
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
index 76c40b2..475bb82 100644
--- a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
+++ b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
@@ -17,6 +17,9 @@
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="ActivityManagerPerfTests.apk"/>
<option name="test-file-name" value="ActivityManagerPerfTestsTestApp.apk"/>
+ <option name="test-file-name" value="ActivityManagerPerfTestsStubApp1.apk"/>
+ <option name="test-file-name" value="ActivityManagerPerfTestsStubApp2.apk"/>
+ <option name="test-file-name" value="ActivityManagerPerfTestsStubApp3.apk"/>
<option name="cleanup-apks" value="true"/>
</target_preparer>
@@ -26,4 +29,4 @@
<option name="package" value="com.android.frameworks.perftests.amtests"/>
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
</test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java
new file mode 100644
index 0000000..1d3ff06
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/OomAdjPerfTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.frameworks.perftests.am.tests;
+
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkLine;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+
+import com.android.frameworks.perftests.am.util.AtraceUtils;
+import com.android.frameworks.perftests.am.util.TargetPackageUtils;
+import com.android.frameworks.perftests.am.util.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This benchmark test basically manipulates 3 test packages, let them bind to
+ * each other, send broadcast to each other, etc. All of these actions essentially
+ * triggers OomAdjuster to update the oom_adj scores and proc state of them.
+ * In the meanwhile it'll also monitor the atrace output, extract duration between
+ * the start and exit entries of the updateOomAdjLocked, include each of them
+ * into the stats; when there are enough samples in the stats, the test will
+ * stop and output the mean/stddev time spent on the updateOomAdjLocked.
+ */
+@RunWith(JUnit4.class)
+@LargeTest
+public final class OomAdjPerfTest {
+ private static final String TAG = "OomAdjPerfTest";
+ private static final boolean VERBOSE = true;
+
+ private static final String STUB_PACKAGE1_NAME = "com.android.stubs.am1";
+ private static final String STUB_PACKAGE2_NAME = "com.android.stubs.am2";
+ private static final String STUB_PACKAGE3_NAME = "com.android.stubs.am3";
+
+ private static final Uri STUB_PACKAGE1_URI = new Uri.Builder().scheme(
+ ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am1.testapp").build();
+ private static final Uri STUB_PACKAGE2_URI = new Uri.Builder().scheme(
+ ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am2.testapp").build();
+ private static final Uri STUB_PACKAGE3_URI = new Uri.Builder().scheme(
+ ContentResolver.SCHEME_CONTENT).authority("com.android.stubs.am3.testapp").build();
+ private static final long NANOS_PER_MICROSECOND = 1000L;
+
+ private static final String ATRACE_CATEGORY = "am";
+ private static final String ATRACE_OOMADJ_PREFIX = "updateOomAdj_";
+
+ @Rule
+ public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+ private TraceMarkParser mTraceMarkParser = new TraceMarkParser(this::shouldFilterTraceLine);
+ private final ArrayList<Long> mDurations = new ArrayList<Long>();
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mHandlerThread = new HandlerThread("command receiver");
+ mHandlerThread.start();
+ TargetPackageUtils.initCommandResultReceiver(mHandlerThread.getLooper());
+
+ Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE1_NAME);
+ Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE2_NAME);
+ Utils.runShellCommand("cmd deviceidle whitelist +" + STUB_PACKAGE3_NAME);
+ TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE1_NAME);
+ TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE2_NAME);
+ TargetPackageUtils.startStubPackage(mContext, STUB_PACKAGE3_NAME);
+ }
+
+ @After
+ public void tearDown() {
+ TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE1_NAME);
+ TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE2_NAME);
+ TargetPackageUtils.stopStubPackage(mContext, STUB_PACKAGE3_NAME);
+ Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE1_NAME);
+ Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE2_NAME);
+ Utils.runShellCommand("cmd deviceidle whitelist -" + STUB_PACKAGE3_NAME);
+ mHandlerThread.quitSafely();
+ }
+
+ @Test
+ public void testOomAdj() {
+ final AtraceUtils atraceUtils = AtraceUtils.getInstance(
+ InstrumentationRegistry.getInstrumentation());
+ final ManualBenchmarkState state = mPerfManualStatusReporter.getBenchmarkState();
+ atraceUtils.startTrace(ATRACE_CATEGORY);
+ while (state.keepRunning(mDurations)) {
+ runCUJWithOomComputationOnce();
+
+ // Now kick off the trace dump
+ mDurations.clear();
+ atraceUtils.performDump(mTraceMarkParser, this::handleTraceMarkSlices);
+ }
+ atraceUtils.stopTrace();
+ }
+
+ private boolean shouldFilterTraceLine(final TraceMarkLine line) {
+ return line.name.startsWith(ATRACE_OOMADJ_PREFIX);
+ }
+
+ private void handleTraceMarkSlices(String key, List<TraceMarkSlice> slices) {
+ for (TraceMarkSlice slice: slices) {
+ mDurations.add(slice.getDurationInMicroseconds() * NANOS_PER_MICROSECOND);
+ }
+ }
+
+ /**
+ * This tries to mimic a user journey, involes multiple activity/service starts/stop,
+ * the time spent on oom adj computation would be different between all these samples,
+ * but with enough samples, we'll be able to know the overall distribution of the time
+ * spent on it.
+ */
+ private void runCUJWithOomComputationOnce() {
+ // Start activity from package1
+ TargetPackageUtils.startActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+ // Start activity from package2
+ TargetPackageUtils.startActivity(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+ // Start activity from package3
+ TargetPackageUtils.startActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+ // Stop activity in package1
+ TargetPackageUtils.stopActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+ // Stop activity in package2
+ TargetPackageUtils.stopActivity(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+ // Stop activity in package3
+ TargetPackageUtils.stopActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+ // Bind from package1 to package2
+ TargetPackageUtils.bindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME,
+ Context.BIND_AUTO_CREATE);
+ // Acquire content provider from package 1 to package3
+ TargetPackageUtils.acquireProvider(STUB_PACKAGE1_NAME, STUB_PACKAGE3_NAME,
+ STUB_PACKAGE3_URI);
+ // Start activity from package1
+ TargetPackageUtils.startActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+ // Bind from package2 to package3
+ TargetPackageUtils.bindService(STUB_PACKAGE2_NAME, STUB_PACKAGE3_NAME,
+ Context.BIND_AUTO_CREATE);
+ // Unbind from package 1 to package 2
+ TargetPackageUtils.unbindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, 0);
+ // Stop activity in package1
+ TargetPackageUtils.stopActivity(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+
+ // Send broadcast to all of them
+ TargetPackageUtils.sendBroadcast(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+ TargetPackageUtils.sendBroadcast(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+ TargetPackageUtils.sendBroadcast(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+ // Bind from package1 to package2 again
+ TargetPackageUtils.bindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME,
+ Context.BIND_AUTO_CREATE);
+ // Create a cycle: package3 to package1
+ TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME,
+ Context.BIND_AUTO_CREATE);
+
+ // Send broadcast to all of them again
+ TargetPackageUtils.sendBroadcast(STUB_PACKAGE1_NAME, STUB_PACKAGE1_NAME);
+ TargetPackageUtils.sendBroadcast(STUB_PACKAGE2_NAME, STUB_PACKAGE2_NAME);
+ TargetPackageUtils.sendBroadcast(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+ // Start activity in package3
+ TargetPackageUtils.startActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+
+ // Break the cycle: unbind from package3 to package1
+ TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, 0);
+
+ // Bind from package3 to package1 with waive priority
+ TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME,
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
+ // Release provider connection
+ TargetPackageUtils.releaseProvider(STUB_PACKAGE1_NAME, STUB_PACKAGE3_NAME,
+ STUB_PACKAGE3_URI);
+ // Unbind from package1 to package2
+ TargetPackageUtils.unbindService(STUB_PACKAGE1_NAME, STUB_PACKAGE2_NAME, 0);
+ // Unbind from package2 to packagae3
+ TargetPackageUtils.unbindService(STUB_PACKAGE2_NAME, STUB_PACKAGE3_NAME, 0);
+
+ // Bind from package3 to package2 with BIND_ABOVE_CLIENT
+ TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+ Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT);
+ // Unbind from package3 to packagae2
+ TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+ // Bind from package3 to package2 with BIND_ALLOW_OOM_MANAGEMENT
+ TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+ Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT);
+ // Unbind from package3 to packagae2
+ TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+ // Bind from package3 to package2 with BIND_IMPORTANT
+ TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+ Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+ // Unbind from package3 to packagae2
+ TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+ // Bind from package3 to package2 with BIND_NOT_FOREGROUND
+ TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
+ // Unbind from package3 to packagae2
+ TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+ // Bind from package3 to package2 with BIND_NOT_PERCEPTIBLE
+ TargetPackageUtils.bindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_PERCEPTIBLE);
+ // Unbind from package3 to packagae2
+ TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE2_NAME, 0);
+
+ // Stop activity in package3
+ TargetPackageUtils.stopActivity(STUB_PACKAGE3_NAME, STUB_PACKAGE3_NAME);
+ // Unbind from package3 to package1
+ TargetPackageUtils.unbindService(STUB_PACKAGE3_NAME, STUB_PACKAGE1_NAME, 0);
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java
new file mode 100644
index 0000000..fcccfce
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/AtraceUtils.java
@@ -0,0 +1,108 @@
+/*
+ * 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.frameworks.perftests.am.util;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+// Simplified version of AtraceLogger.
+public class AtraceUtils {
+ private static final String TAG = "AtraceUtils";
+ private static final boolean VERBOSE = true;
+
+ private static final String ATRACE_START = "atrace --async_start -b %d -c %s";
+ private static final String ATRACE_DUMP = "atrace --async_dump";
+ private static final String ATRACE_STOP = "atrace --async_stop";
+ private static final int DEFAULT_ATRACE_BUF_SIZE = 1024;
+
+ private UiAutomation mAutomation;
+ private static AtraceUtils sUtils = null;
+ private boolean mStarted = false;
+
+ private AtraceUtils(Instrumentation instrumentation) {
+ mAutomation = instrumentation.getUiAutomation();
+ }
+
+ public static AtraceUtils getInstance(Instrumentation instrumentation) {
+ if (sUtils == null) {
+ sUtils = new AtraceUtils(instrumentation);
+ }
+ return sUtils;
+ }
+
+ /**
+ * @param categories The list of the categories to trace, separated with space.
+ */
+ public void startTrace(String categories) {
+ synchronized (this) {
+ if (mStarted) {
+ throw new IllegalStateException("atrace already started");
+ }
+ Utils.runShellCommand(String.format(
+ ATRACE_START, DEFAULT_ATRACE_BUF_SIZE, categories));
+ mStarted = true;
+ }
+ }
+
+ public void stopTrace() {
+ synchronized (this) {
+ mStarted = false;
+ Utils.runShellCommand(ATRACE_STOP);
+ }
+ }
+
+ /**
+ * @param parser The function that can accept the buffer of atrace dump and parse it.
+ * @param handler The parse result handler
+ */
+ public void performDump(TraceMarkParser parser,
+ BiConsumer<String, List<TraceMarkSlice>> handler) {
+ parser.reset();
+ try {
+ if (VERBOSE) {
+ Log.i(TAG, "Collecting atrace dump...");
+ }
+ writeDataToBuf(mAutomation.executeShellCommand(ATRACE_DUMP), parser);
+ } catch (IOException e) {
+ Log.e(TAG, "Error in reading dump", e);
+ }
+ parser.forAllSlices(handler);
+ }
+
+ // The given file descriptor here will be closed by this function
+ private void writeDataToBuf(ParcelFileDescriptor pfDescriptor,
+ TraceMarkParser parser) throws IOException {
+ InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor);
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ parser.visit(line);
+ }
+ }
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
index 046dd6b..d7f4d9d 100644
--- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
@@ -22,12 +22,18 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.ResultReceiver;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
import org.junit.Assert;
@@ -36,6 +42,7 @@
public class TargetPackageUtils {
private static final String TAG = TargetPackageUtils.class.getSimpleName();
+ public static final boolean VERBOSE = true;
public static final String PACKAGE_NAME = "com.android.frameworks.perftests.amteststestapp";
public static final String ACTIVITY_NAME = PACKAGE_NAME + ".TestActivity";
@@ -48,6 +55,12 @@
// Cache for test app's uid, so we only have to query it once.
private static int sTestAppUid = -1;
+ private static final ArrayMap<String, ICommandReceiver> sStubPackages =
+ new ArrayMap<String, ICommandReceiver>();
+ private static final ArrayMap<Integer, CountDownLatch> sCommandLatches =
+ new ArrayMap<Integer, CountDownLatch>();
+ private static int sSeqNum = 0;
+
/**
* Kills the test package synchronously.
*/
@@ -145,5 +158,160 @@
}
}
+ private static boolean isUidRunning(int uid) {
+ return !Utils.runShellCommand(String.format("cmd activity get-uid-state %d", uid))
+ .contains("(NONEXISTENT)");
+ }
+
+ public static void startStubPackage(Context context, String pkgName) {
+ stopStubPackage(context, pkgName);
+ try {
+ Pair<Integer, CountDownLatch> pair = obtainLatch();
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(pkgName, Constants.STUB_INIT_SERVICE_NAME));
+ intent.putExtra(Constants.EXTRA_SOURCE_PACKAGE, context.getPackageName());
+ intent.putExtra(Constants.EXTRA_RECEIVER_CALLBACK, sMessenger);
+ intent.putExtra(Constants.EXTRA_SEQ, pair.first);
+ context.startService(intent);
+ Assert.assertTrue("Timeout when waiting for starting package " + pkgName,
+ pair.second.await(AWAIT_SERVICE_CONNECT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void stopStubPackage(Context context, String pkgName) {
+ final PackageManager pm = context.getPackageManager();
+ try {
+ final int uid = pm.getPackageUid(pkgName, 0);
+ if (isUidRunning(uid)) {
+ ActivityManager am = context.getSystemService(ActivityManager.class);
+ am.forceStopPackage(pkgName);
+ while (isUidRunning(uid)) {
+ SystemClock.sleep(WAIT_TIME_MS);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void initCommandResultReceiver(Looper looper) {
+ if (sMessenger == null) {
+ sMessenger = new Messenger(new H(looper));
+ }
+ }
+
+ private static void onPackageStartResult(int seq, Bundle bundle) {
+ ICommandReceiver receiver = ICommandReceiver.Stub.asInterface(
+ bundle.getBinder(Constants.EXTRA_RECEIVER_CALLBACK));
+ String sourcePkg = bundle.getString(Constants.EXTRA_SOURCE_PACKAGE);
+ sStubPackages.put(sourcePkg, receiver);
+ releaseLatch(seq);
+ }
+
+ private static void onCommandResult(int seq, int result) {
+ Assert.assertTrue("Error in command seq " + seq, result == Constants.RESULT_NO_ERROR);
+ releaseLatch(seq);
+ }
+
+ private static Messenger sMessenger = null;
+ private static class H extends Handler {
+ H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case Constants.REPLY_PACKAGE_START_RESULT:
+ onPackageStartResult(msg.arg1 /* seq */, (Bundle) msg.obj);
+ break;
+ case Constants.REPLY_COMMAND_RESULT:
+ onCommandResult(msg.arg1, msg.arg2);
+ break;
+ }
+ }
+ }
+
+ private static Pair<Integer, CountDownLatch> obtainLatch() {
+ CountDownLatch latch = new CountDownLatch(1);
+ int seq;
+ synchronized (sCommandLatches) {
+ seq = sSeqNum++;
+ sCommandLatches.put(seq, latch);
+ }
+ return new Pair<>(seq, latch);
+ }
+
+ private static void releaseLatch(int seq) {
+ synchronized (sCommandLatches) {
+ CountDownLatch latch = sCommandLatches.get(seq);
+ if (latch != null) {
+ latch.countDown();
+ sCommandLatches.remove(seq);
+ }
+ }
+ }
+
+ public static void sendCommand(int command, String sourcePackage, String targetPackage,
+ int flags, Bundle bundle, boolean waitForResult) {
+ ICommandReceiver receiver = sStubPackages.get(sourcePackage);
+ Assert.assertTrue("Package hasn't been started: " + sourcePackage, receiver != null);
+ try {
+ Pair<Integer, CountDownLatch> pair = null;
+ if (waitForResult) {
+ pair = obtainLatch();
+ }
+ if (VERBOSE) {
+ Log.i(TAG, "Sending command=" + command + ", seq=" + pair.first + ", from="
+ + sourcePackage + ", to=" + targetPackage + ", flags=" + flags);
+ }
+ receiver.sendCommand(command, pair.first, sourcePackage, targetPackage, flags, bundle);
+ if (waitForResult) {
+ Assert.assertTrue("Timeout when waiting for command " + command + " from "
+ + sourcePackage + " to " + targetPackage,
+ pair.second.await(AWAIT_SERVICE_CONNECT_MS, TimeUnit.MILLISECONDS));
+ }
+ } catch (RemoteException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void bindService(String sourcePackage, String targetPackage, int flags) {
+ sendCommand(Constants.COMMAND_BIND_SERVICE, sourcePackage, targetPackage, flags, null,
+ true);
+ }
+
+ public static void unbindService(String sourcePackage, String targetPackage, int flags) {
+ sendCommand(Constants.COMMAND_UNBIND_SERVICE, sourcePackage, targetPackage, flags, null,
+ true);
+ }
+
+ public static void acquireProvider(String sourcePackage, String targetPackage, Uri uri) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(Constants.EXTRA_URI, uri);
+ sendCommand(Constants.COMMAND_ACQUIRE_CONTENT_PROVIDER, sourcePackage, targetPackage, 0,
+ bundle, true);
+ }
+
+ public static void releaseProvider(String sourcePackage, String targetPackage, Uri uri) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(Constants.EXTRA_URI, uri);
+ sendCommand(Constants.COMMAND_RELEASE_CONTENT_PROVIDER, sourcePackage, targetPackage, 0,
+ bundle, true);
+ }
+
+ public static void sendBroadcast(String sourcePackage, String targetPackage) {
+ sendCommand(Constants.COMMAND_SEND_BROADCAST, sourcePackage, targetPackage, 0, null, true);
+ }
+
+ public static void startActivity(String sourcePackage, String targetPackage) {
+ sendCommand(Constants.COMMAND_START_ACTIVITY, sourcePackage, targetPackage, 0, null, true);
+ }
+
+ public static void stopActivity(String sourcePackage, String targetPackage) {
+ sendCommand(Constants.COMMAND_STOP_ACTIVITY, sourcePackage, targetPackage, 0, null, true);
+ }
}
diff --git a/tests/ActivityManagerPerfTests/utils/Android.bp b/tests/ActivityManagerPerfTests/utils/Android.bp
index 300b7ea..766c3ac 100644
--- a/tests/ActivityManagerPerfTests/utils/Android.bp
+++ b/tests/ActivityManagerPerfTests/utils/Android.bp
@@ -18,6 +18,7 @@
srcs: [
"src/**/*.java",
"src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl",
+ "src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl",
],
static_libs: [
"androidx.test.rules",
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
index 9b076c5..8e58665 100644
--- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
@@ -30,4 +30,33 @@
public static final String EXTRA_RECEIVER_CALLBACK = "receiver_callback_binder";
public static final String EXTRA_LOOPER_IDLE_CALLBACK = "looper_idle_callback_binder";
+ public static final String EXTRA_SOURCE_PACKAGE = "source_package";
+ public static final String EXTRA_URI = "uri";
+ public static final String EXTRA_REQ_FINISH_ACTIVITY = "req_finish_activity";
+ public static final String EXTRA_SEQ = "seq";
+ public static final String EXTRA_ARG1 = "arg1";
+ public static final String EXTRA_ARG2 = "arg2";
+
+ public static final int RESULT_NO_ERROR = 0;
+ public static final int RESULT_ERROR = 1;
+ public static final String STUB_INIT_SERVICE_NAME = "com.android.stubs.am.InitService";
+
+ public static final int COMMAND_BIND_SERVICE = 1;
+ public static final int COMMAND_UNBIND_SERVICE = 2;
+ public static final int COMMAND_ACQUIRE_CONTENT_PROVIDER = 3;
+ public static final int COMMAND_RELEASE_CONTENT_PROVIDER = 4;
+ public static final int COMMAND_SEND_BROADCAST = 5;
+ public static final int COMMAND_START_ACTIVITY = 6;
+ public static final int COMMAND_STOP_ACTIVITY = 7;
+
+ public static final int MSG_DEFAULT = 0;
+ public static final int MSG_UNBIND_DONE = 1;
+
+ public static final int REPLY_PACKAGE_START_RESULT = 0;
+ public static final int REPLY_COMMAND_RESULT = 1;
+
+ public static final String STUB_ACTION_ACTIVITY =
+ "com.android.stubs.am.ACTION_START_TEST_ACTIVITY";
+ public static final String STUB_ACTION_BROADCAST =
+ "com.android.stubs.am.ACTION_BROADCAST_TEST";
}
diff --git a/tests/utils/testutils/java/test/package-info.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl
similarity index 71%
copy from tests/utils/testutils/java/test/package-info.java
copy to tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl
index c34d7b2..59ea761 100644
--- a/tests/utils/testutils/java/test/package-info.java
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ICommandReceiver.aidl
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-/**
- * This package separated from android. because placing classes under android.'s .test/.util
- * may be confused with tests for that actual android subpackage.
- **/
-package test;
+package com.android.frameworks.perftests.am.util;
+
+import android.os.Bundle;
+
+interface ICommandReceiver {
+ oneway void sendCommand(int command, int seq, String sourcePackage, String targetPackage,
+ int flags, in Bundle bundle);
+}
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index adcbb428..c8d1ce1 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -13,20 +13,20 @@
// limitations under the License.
java_test_host {
- name: "ApkVerityTests",
+ name: "ApkVerityTest",
srcs: ["src/**/*.java"],
libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
test_suites: ["general-tests"],
target_required: [
"block_device_writer_module",
- "ApkVerityTestApp",
- "ApkVerityTestAppSplit",
],
data: [
":ApkVerityTestCertDer",
+ ":ApkVerityTestApp",
":ApkVerityTestAppFsvSig",
":ApkVerityTestAppDm",
":ApkVerityTestAppDmFsvSig",
+ ":ApkVerityTestAppSplit",
":ApkVerityTestAppSplitFsvSig",
":ApkVerityTestAppSplitDm",
":ApkVerityTestAppSplitDmFsvSig",
diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml
index 73779cb..55704ed 100644
--- a/tests/ApkVerityTest/AndroidTest.xml
+++ b/tests/ApkVerityTest/AndroidTest.xml
@@ -22,7 +22,7 @@
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<!-- Disable package verifier prevents it holding the target APK's fd that prevents cache
eviction. -->
- <option name="set-global-setting" key="package_verifier_enable" value="0" />
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
<option name="restore-settings" value="true" />
<!-- Skip in order to prevent reboot that confuses the test flow. -->
@@ -36,6 +36,6 @@
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
- <option name="jar" value="ApkVerityTests.jar" />
+ <option name="jar" value="ApkVerityTest.jar" />
</test>
</configuration>
diff --git a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp
index d95af34..1f47b03 100644
--- a/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp
+++ b/tests/ManagedProfileLifecycleStressTest/app/DummyDPC/Android.bp
@@ -17,4 +17,5 @@
defaults: ["cts_defaults"],
srcs: ["src/**/*.java"],
sdk_version: "current",
+ test_suites: ["device-tests"],
}
diff --git a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
index e323592..026677e 100644
--- a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
+++ b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
@@ -53,6 +53,8 @@
*/
@Test
public void testCreateStartDelete() throws Exception {
+ // Disable package verifier for ADB installs.
+ getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
int iteration = 0;
final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(TIME_LIMIT_MINUTES);
while (System.nanoTime() < deadline) {
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index ef8face..b4cafe4 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -328,7 +328,8 @@
long differentVersionCode = 2L;
TestObserver observer = new TestObserver(OBSERVER_NAME_1) {
@Override
- public int onHealthCheckFailed(VersionedPackage versionedPackage) {
+ public int onHealthCheckFailed(VersionedPackage versionedPackage,
+ int failureReason) {
if (versionedPackage.getVersionCode() == VERSION_CODE) {
// Only rollback for specific versionCode
return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
@@ -1012,7 +1013,7 @@
mImpact = impact;
}
- public int onHealthCheckFailed(VersionedPackage versionedPackage) {
+ public int onHealthCheckFailed(VersionedPackage versionedPackage, int failureReason) {
mHealthCheckFailedPackages.add(versionedPackage.getPackageName());
return mImpact;
}
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 abe6c61..daa85bd 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1098,4 +1098,28 @@
InstallUtils.dropShellPermissionIdentity();
}
}
+
+ /**
+ * Test we can't enable rollback for non-whitelisted app without
+ * TEST_MANAGE_ROLLBACKS permission
+ */
+ @Test
+ public void testNonRollbackWhitelistedApp() throws Exception {
+ try {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
+
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+
+ Install.single(TestApp.A2).setEnableRollback().commit();
+ Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+ assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+ } finally {
+ InstallUtils.dropShellPermissionIdentity();
+ }
+ }
}
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 3be7a4a..879ac64 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -22,18 +22,14 @@
import static com.google.common.truth.Truth.assertThat;
import android.Manifest;
-import android.annotation.Nullable;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageInfo;
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.provider.DeviceConfig;
-import android.text.TextUtils;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -75,7 +71,6 @@
private static final String PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS =
"watchdog_request_timeout_millis";
- private static final String MODULE_META_DATA_PACKAGE = getModuleMetadataPackageName();
private static final TestApp NETWORK_STACK = new TestApp("NetworkStack",
getNetworkStackPackageName(), -1, false, findNetworkStackApk());
@@ -186,21 +181,15 @@
}
/**
- * Stage install ModuleMetadata package to simulate a Mainline module update.
+ * Stage install an apk with rollback that will be later triggered by unattributable crash.
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
- resetModuleMetadataPackage();
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
- PackageInfo metadataPackageInfo = context.getPackageManager().getPackageInfo(
- MODULE_META_DATA_PACKAGE, 0);
- String metadataApkPath = metadataPackageInfo.applicationInfo.sourceDir;
- assertThat(metadataApkPath).isNotNull();
- assertThat(metadataApkPath).isNotEqualTo("");
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
- runShellCommand("pm install "
- + "-r --enable-rollback --staged --wait "
- + metadataApkPath);
+ Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
}
/**
@@ -208,9 +197,10 @@
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- MODULE_META_DATA_PACKAGE)).isNotNull();
+ TestApp.A)).isNotNull();
}
/**
@@ -218,9 +208,10 @@
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
- MODULE_META_DATA_PACKAGE)).isNotNull();
+ TestApp.A)).isNotNull();
}
@Test
@@ -351,24 +342,35 @@
getNetworkStackPackageName())).isNull();
}
- @Nullable
private static String getModuleMetadataPackageName() {
- String packageName = InstrumentationRegistry.getInstrumentation().getContext()
+ return InstrumentationRegistry.getInstrumentation().getContext()
.getResources().getString(R.string.config_defaultModuleMetadataProvider);
- if (TextUtils.isEmpty(packageName)) {
- return null;
- }
- return packageName;
}
- private void resetModuleMetadataPackage() {
- RollbackManager rm = RollbackUtils.getRollbackManager();
+ @Test
+ public void testRollbackWhitelistedApp_Phase1() throws Exception {
+ // Remove available rollbacks
+ String pkgName = getModuleMetadataPackageName();
+ RollbackUtils.getRollbackManager().expireRollbackForPackage(pkgName);
+ assertThat(RollbackUtils.getAvailableRollback(pkgName)).isNull();
- assertThat(MODULE_META_DATA_PACKAGE).isNotNull();
- rm.expireRollbackForPackage(MODULE_META_DATA_PACKAGE);
+ // Overwrite existing permissions. We don't want TEST_MANAGE_ROLLBACKS which allows us
+ // to enable rollback for any app
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.MANAGE_ROLLBACKS);
- assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- MODULE_META_DATA_PACKAGE)).isNull();
+ // Re-install a whitelisted app with rollbacks enabled
+ String filePath = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getPackageInfo(pkgName, 0).applicationInfo.sourceDir;
+ TestApp app = new TestApp("ModuleMetadata", pkgName, -1, false, new File(filePath));
+ Install.single(app).setStaged().setEnableRollback()
+ .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit();
+ }
+
+ @Test
+ public void testRollbackWhitelistedApp_Phase2() throws Exception {
+ assertThat(RollbackUtils.getAvailableRollback(getModuleMetadataPackageName())).isNotNull();
}
private static void runShellCommand(String cmd) {
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 e4a8feb..07d829d 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
@@ -175,6 +175,16 @@
runPhase("testPreviouslyAbandonedRollbacks_Phase3");
}
+ /**
+ * Tests we can enable rollback for a whitelisted app.
+ */
+ @Test
+ public void testRollbackWhitelistedApp() throws Exception {
+ runPhase("testRollbackWhitelistedApp_Phase1");
+ getDevice().reboot();
+ runPhase("testRollbackWhitelistedApp_Phase2");
+ }
+
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
String pid = "";
String lastPid = "invalid";
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index ae8285b..a7eef05 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -16,7 +16,9 @@
package android.net;
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
+import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -47,25 +49,22 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class LinkPropertiesTest {
- private static final InetAddress ADDRV4 = InetAddresses.parseNumericAddress("75.208.6.1");
- private static final InetAddress ADDRV6 = InetAddresses.parseNumericAddress(
- "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
- private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("75.208.7.1");
- private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("69.78.7.1");
- private static final InetAddress DNS6 = InetAddresses.parseNumericAddress(
- "2001:4860:4860::8888");
- private static final InetAddress PRIVDNS1 = InetAddresses.parseNumericAddress("1.1.1.1");
- private static final InetAddress PRIVDNS2 = InetAddresses.parseNumericAddress("1.0.0.1");
- private static final InetAddress PRIVDNS6 = InetAddresses.parseNumericAddress(
- "2606:4700:4700::1111");
- private static final InetAddress PCSCFV4 = InetAddresses.parseNumericAddress("10.77.25.37");
- private static final InetAddress PCSCFV6 = InetAddresses.parseNumericAddress(
- "2001:0db8:85a3:0000:0000:8a2e:0370:1");
- private static final InetAddress GATEWAY1 = InetAddresses.parseNumericAddress("75.208.8.1");
- private static final InetAddress GATEWAY2 = InetAddresses.parseNumericAddress("69.78.8.1");
- private static final InetAddress GATEWAY61 = InetAddresses.parseNumericAddress(
- "fe80::6:0000:613");
- private static final InetAddress GATEWAY62 = InetAddresses.parseNumericAddress("fe80::6:2222");
+ private static final InetAddress ADDRV4 = address("75.208.6.1");
+ private static final InetAddress ADDRV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+ private static final InetAddress DNS1 = address("75.208.7.1");
+ private static final InetAddress DNS2 = address("69.78.7.1");
+ private static final InetAddress DNS6 = address("2001:4860:4860::8888");
+ private static final InetAddress PRIVDNS1 = address("1.1.1.1");
+ private static final InetAddress PRIVDNS2 = address("1.0.0.1");
+ private static final InetAddress PRIVDNS6 = address("2606:4700:4700::1111");
+ private static final InetAddress PCSCFV4 = address("10.77.25.37");
+ private static final InetAddress PCSCFV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:1");
+ private static final InetAddress GATEWAY1 = address("75.208.8.1");
+ private static final InetAddress GATEWAY2 = address("69.78.8.1");
+ private static final InetAddress GATEWAY61 = address("fe80::6:0000:613");
+ 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 String NAME = "qmi0";
private static final String DOMAINS = "google.com";
private static final String PRIV_DNS_SERVER_NAME = "private.dns.com";
@@ -75,8 +74,7 @@
private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
- // TODO: replace all calls to NetworkUtils.numericToInetAddress with calls to this method.
- private InetAddress Address(String addrString) {
+ private static InetAddress address(String addrString) {
return InetAddresses.parseNumericAddress(addrString);
}
@@ -228,7 +226,7 @@
target.clear();
target.setInterfaceName(NAME);
// change link addresses
- target.addLinkAddress(new LinkAddress(Address("75.208.6.2"), 32));
+ target.addLinkAddress(new LinkAddress(address("75.208.6.2"), 32));
target.addLinkAddress(LINKADDRV6);
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
@@ -243,7 +241,7 @@
target.addLinkAddress(LINKADDRV4);
target.addLinkAddress(LINKADDRV6);
// change dnses
- target.addDnsServer(Address("75.208.7.2"));
+ target.addDnsServer(address("75.208.7.2"));
target.addDnsServer(DNS2);
target.addPcscfServer(PCSCFV6);
target.addRoute(new RouteInfo(GATEWAY1));
@@ -255,10 +253,10 @@
target.setInterfaceName(NAME);
target.addLinkAddress(LINKADDRV4);
target.addLinkAddress(LINKADDRV6);
- target.addDnsServer(Address("75.208.7.2"));
+ target.addDnsServer(address("75.208.7.2"));
target.addDnsServer(DNS2);
// change pcscf
- target.addPcscfServer(Address("2001::1"));
+ target.addPcscfServer(address("2001::1"));
target.addRoute(new RouteInfo(GATEWAY1));
target.addRoute(new RouteInfo(GATEWAY2));
target.setMtu(MTU);
@@ -271,9 +269,9 @@
target.addDnsServer(DNS1);
target.addDnsServer(DNS2);
// change gateway
- target.addRoute(new RouteInfo(Address("75.208.8.2")));
- target.addRoute(new RouteInfo(GATEWAY2));
+ target.addRoute(new RouteInfo(address("75.208.8.2")));
target.setMtu(MTU);
+ target.addRoute(new RouteInfo(GATEWAY2));
assertFalse(source.equals(target));
target.clear();
@@ -349,7 +347,7 @@
@Test
public void testRouteInterfaces() {
- LinkAddress prefix = new LinkAddress(Address("2001:db8::"), 32);
+ LinkAddress prefix = new LinkAddress(address("2001:db8::"), 32);
InetAddress address = ADDRV6;
// Add a route with no interface to a LinkProperties with no interface. No errors.
@@ -739,8 +737,7 @@
// Add an on-link route, making the on-link DNS server reachable,
// but there is still no IPv4 address.
- assertTrue(v4lp.addRoute(new RouteInfo(
- new IpPrefix(NetworkUtils.numericToInetAddress("75.208.0.0"), 16))));
+ assertTrue(v4lp.addRoute(new RouteInfo(new IpPrefix(address("75.208.0.0"), 16))));
assertFalse(v4lp.isReachable(DNS1));
assertFalse(v4lp.isReachable(DNS2));
@@ -756,9 +753,9 @@
assertTrue(v4lp.isReachable(DNS2));
final LinkProperties v6lp = new LinkProperties();
- final InetAddress kLinkLocalDns = Address("fe80::6:1");
- final InetAddress kLinkLocalDnsWithScope = Address("fe80::6:2%43");
- final InetAddress kOnLinkDns = Address("2001:db8:85a3::53");
+ final InetAddress kLinkLocalDns = address("fe80::6:1");
+ final InetAddress kLinkLocalDnsWithScope = address("fe80::6:2%43");
+ final InetAddress kOnLinkDns = address("2001:db8:85a3::53");
assertFalse(v6lp.isReachable(kLinkLocalDns));
assertFalse(v6lp.isReachable(kLinkLocalDnsWithScope));
assertFalse(v6lp.isReachable(kOnLinkDns));
@@ -767,7 +764,7 @@
// Add a link-local route, making the link-local DNS servers reachable. Because
// we assume the presence of an IPv6 link-local address, link-local DNS servers
// are considered reachable, but only those with a non-zero scope identifier.
- assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(Address("fe80::"), 64))));
+ assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(address("fe80::"), 64))));
assertFalse(v6lp.isReachable(kLinkLocalDns));
assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
assertFalse(v6lp.isReachable(kOnLinkDns));
@@ -783,7 +780,7 @@
// Add a global route on link, but no global address yet. DNS servers reachable
// via a route that doesn't require a gateway: give them the benefit of the
// doubt and hope the link-local source address suffices for communication.
- assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(Address("2001:db8:85a3::"), 64))));
+ assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(address("2001:db8:85a3::"), 64))));
assertFalse(v6lp.isReachable(kLinkLocalDns));
assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
assertTrue(v6lp.isReachable(kOnLinkDns));
@@ -812,7 +809,7 @@
stacked.setInterfaceName("v4-test0");
v6lp.addStackedLink(stacked);
- InetAddress stackedAddress = Address("192.0.0.4");
+ InetAddress stackedAddress = address("192.0.0.4");
LinkAddress stackedLinkAddress = new LinkAddress(stackedAddress, 32);
assertFalse(v6lp.isReachable(stackedAddress));
stacked.addLinkAddress(stackedLinkAddress);
@@ -845,7 +842,7 @@
LinkProperties rmnet1 = new LinkProperties();
rmnet1.setInterfaceName("rmnet1");
rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8"));
- RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null, Address("10.0.0.1"),
+ RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null, address("10.0.0.1"),
rmnet1.getInterfaceName());
RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
rmnet1.getInterfaceName());
@@ -864,7 +861,7 @@
rmnet2.setInterfaceName("rmnet2");
rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64"));
rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64"));
- RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null, Address("2001:db8::1"),
+ RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null, address("2001:db8::1"),
rmnet2.getInterfaceName());
RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null,
rmnet2.getInterfaceName());
@@ -930,24 +927,54 @@
public void testLinkPropertiesParcelable() throws Exception {
LinkProperties source = new LinkProperties();
source.setInterfaceName(NAME);
- // set 2 link addresses
+
source.addLinkAddress(LINKADDRV4);
source.addLinkAddress(LINKADDRV6);
- // set 2 dnses
+
source.addDnsServer(DNS1);
source.addDnsServer(DNS2);
- // set 2 gateways
+ source.addDnsServer(GATEWAY62);
+
+ source.addPcscfServer(TESTIPV4ADDR);
+ source.addPcscfServer(TESTIPV6ADDR);
+
+ source.setUsePrivateDns(true);
+ source.setPrivateDnsServerName(PRIV_DNS_SERVER_NAME);
+
+ source.setDomains(DOMAINS);
+
source.addRoute(new RouteInfo(GATEWAY1));
source.addRoute(new RouteInfo(GATEWAY2));
- // set 2 validated private dnses
+
source.addValidatedPrivateDnsServer(DNS6);
source.addValidatedPrivateDnsServer(GATEWAY61);
+ source.addValidatedPrivateDnsServer(TESTIPV6ADDR);
+
+ source.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888));
source.setMtu(MTU);
+ source.setTcpBufferSizes(TCP_BUFFER_SIZES);
+
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
- assertParcelingIsLossless(source);
+ source.setWakeOnLanSupported(true);
+
+ final LinkProperties stacked = new LinkProperties();
+ stacked.setInterfaceName("test-stacked");
+ source.addStackedLink(stacked);
+
+ assertParcelSane(source, 15 /* fieldCount */);
+ }
+
+ @Test
+ public void testLinkLocalDnsServerParceling() throws Exception {
+ final String strAddress = "fe80::1%lo";
+ final LinkProperties lp = new LinkProperties();
+ lp.addDnsServer(address(strAddress));
+ final LinkProperties unparceled = parcelingRoundTrip(lp);
+ // Inet6Address#equals does not test for the scope id
+ assertEquals(strAddress, unparceled.getDnsServers().get(0).getHostAddress());
}
@Test
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 25028fb..c4801aa 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -32,7 +32,6 @@
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkRequest
import android.net.TestNetworkStackClient
-import android.net.TetheringManager
import android.net.metrics.IpConnectivityLog
import android.os.ConditionVariable
import android.os.IBinder
@@ -169,7 +168,6 @@
val deps = spy(ConnectivityService.Dependencies())
doReturn(networkStackClient).`when`(deps).networkStack
doReturn(metricsLogger).`when`(deps).metricsLogger
- doReturn(mock(TetheringManager::class.java)).`when`(deps).getTetheringManager()
doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index 5cb0d7e..e632aaf 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -22,8 +22,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import android.net.SocketKeepalive.InvalidPacketException;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index a24426b..b2d363e 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -164,7 +164,6 @@
import android.net.ResolverParamsParcel;
import android.net.RouteInfo;
import android.net.SocketKeepalive;
-import android.net.TetheringManager;
import android.net.UidRange;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
@@ -1133,7 +1132,6 @@
doReturn(new TestNetIdManager()).when(deps).makeNetIdManager();
doReturn(mNetworkStack).when(deps).getNetworkStack();
doReturn(systemProperties).when(deps).getSystemProperties();
- doReturn(mock(TetheringManager.class)).when(deps).getTetheringManager();
doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
doReturn(mMetricsService).when(deps).getMetricsLogger();
doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp
index 027b1d6..f71be7b 100644
--- a/tests/utils/testutils/Android.bp
+++ b/tests/utils/testutils/Android.bp
@@ -17,16 +17,11 @@
java_library {
name: "frameworks-base-testutils",
- srcs: [
- "java/**/*.java",
- "java/**/*.kt",
- ],
+ srcs: ["java/**/*.java"],
static_libs: [
"junit",
"hamcrest-library",
- "truth-prebuilt",
- "mockito-target-minus-junit4",
],
libs: [
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 67ba895..c49c370 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -147,10 +147,11 @@
ResourceId(uint32_t res_id); // NOLINT(google-explicit-constructor)
ResourceId(uint8_t p, uint8_t t, uint16_t e);
- bool is_valid() const;
+ // Returns true if the ID is a valid ID that is not dynamic (package ID cannot be 0)
+ bool is_valid_static() const;
// Returns true if the ID is a valid ID or dynamic ID (package ID can be 0).
- bool is_valid_dynamic() const;
+ bool is_valid() const;
uint8_t package_id() const;
uint8_t type_id() const;
@@ -233,11 +234,11 @@
inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e)
: id((p << 24) | (t << 16) | e) {}
-inline bool ResourceId::is_valid() const {
+inline bool ResourceId::is_valid_static() const {
return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
}
-inline bool ResourceId::is_valid_dynamic() const {
+inline bool ResourceId::is_valid() const {
return (id & 0x00ff0000u) != 0;
}
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 1773b5a..e0a9a31e 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -398,7 +398,7 @@
// Check for package names appearing twice with two different package ids
ResourceTablePackage* package = FindOrCreatePackage(name.package);
- if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
+ if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) {
diag->Error(DiagMessage(source)
<< "trying to add resource '" << name << "' with ID " << res_id
<< " but package '" << package->name << "' already has ID "
@@ -407,9 +407,9 @@
}
// Whether or not to error on duplicate resources
- bool check_id = validate_resources_ && res_id.is_valid_dynamic();
+ bool check_id = validate_resources_ && res_id.is_valid();
// Whether or not to create a duplicate resource if the id does not match
- bool use_id = !validate_resources_ && res_id.is_valid_dynamic();
+ bool use_id = !validate_resources_ && res_id.is_valid();
ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id()
: Maybe<uint8_t>());
@@ -463,7 +463,7 @@
}
}
- if (res_id.is_valid_dynamic()) {
+ if (res_id.is_valid()) {
package->id = res_id.package_id();
type->id = res_id.type_id();
entry->id = res_id.entry_id();
@@ -504,7 +504,7 @@
// Check for package names appearing twice with two different package ids
ResourceTablePackage* package = FindOrCreatePackage(name.package);
- if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
+ if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) {
diag->Error(DiagMessage(source)
<< "trying to add resource '" << name << "' with ID " << res_id
<< " but package '" << package->name << "' already has ID "
@@ -513,9 +513,9 @@
}
// Whether or not to error on duplicate resources
- bool check_id = validate_resources_ && res_id.is_valid_dynamic();
+ bool check_id = validate_resources_ && res_id.is_valid();
// Whether or not to create a duplicate resource if the id does not match
- bool use_id = !validate_resources_ && res_id.is_valid_dynamic();
+ bool use_id = !validate_resources_ && res_id.is_valid();
ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id()
: Maybe<uint8_t>());
@@ -541,7 +541,7 @@
return false;
}
- if (res_id.is_valid_dynamic()) {
+ if (res_id.is_valid()) {
package->id = res_id.package_id();
type->id = res_id.type_id();
entry->id = res_id.entry_id();
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index bd2ab53..3623b11 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -516,7 +516,7 @@
if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
if (value.dataType == android::Res_value::TYPE_INT_HEX) {
ResourceId id(value.data);
- if (id.is_valid_dynamic()) {
+ if (id.is_valid()) {
return id;
}
}
@@ -738,7 +738,13 @@
const android::Res_value& res_value,
StringPool* dst_pool) {
if (type == ResourceType::kId) {
- return util::make_unique<Id>();
+ if (res_value.dataType != android::Res_value::TYPE_REFERENCE &&
+ res_value.dataType != android::Res_value::TYPE_DYNAMIC_REFERENCE) {
+ // plain "id" resources are actually encoded as dummy values (aapt1 uses an empty string,
+ // while aapt2 uses a false boolean).
+ return util::make_unique<Id>();
+ }
+ // fall through to regular reference deserialization logic
}
const uint32_t data = util::DeviceToHost32(res_value.data);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 34b46c5..4f0fa8a 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -117,7 +117,7 @@
bool Reference::Flatten(android::Res_value* out_value) const {
const ResourceId resid = id.value_or_default(ResourceId(0));
- const bool dynamic = resid.is_valid_dynamic() && is_dynamic;
+ const bool dynamic = resid.is_valid() && is_dynamic;
if (reference_type == Reference::Type::kResource) {
if (dynamic) {
@@ -159,7 +159,7 @@
*out << name.value();
}
- if (id && id.value().is_valid_dynamic()) {
+ if (id && id.value().is_valid()) {
if (name) {
*out << " ";
}
@@ -196,7 +196,7 @@
printer->Print("/");
printer->Print(name.entry);
}
- } else if (ref.id && ref.id.value().is_valid_dynamic()) {
+ } else if (ref.id && ref.id.value().is_valid()) {
printer->Print(ref.id.value().to_string());
}
}
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index b06607e..0db1807 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -160,13 +160,19 @@
void Visit(xml::Element* node) override {
if (node->namespace_uri.empty() && node->name == "item") {
for (const auto& attr : node->attributes) {
- if (attr.namespace_uri == xml::kSchemaAndroid) {
- if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") &&
- util::IsJavaClassName(attr.value)) {
- AddClass(node->line_number, attr.value, "android.content.Context");
- } else if (attr.name == "onClick") {
- AddMethod(node->line_number, attr.value, "android.view.MenuItem");
- }
+ // AppCompat-v7 defines its own versions of Android attributes if
+ // they're defined after SDK 7 (the below are from 11 and 14,
+ // respectively), so don't bother checking the XML namespace.
+ //
+ // Given the names of the containing XML files and the attribute
+ // names, it's unlikely that keeping these classes would be wrong.
+ if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") &&
+ util::IsJavaClassName(attr.value)) {
+ AddClass(node->line_number, attr.value, "android.content.Context");
+ }
+
+ if (attr.namespace_uri == xml::kSchemaAndroid && attr.name == "onClick") {
+ AddMethod(node->line_number, attr.value, "android.view.MenuItem");
}
}
}
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index 8720597..b6e7602 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -326,6 +326,25 @@
EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat")));
}
+TEST(ProguardRulesTest, MenuRulesAreEmittedForActionClasses) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"(
+ <menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:id="@+id/my_item"
+ app:actionViewClass="com.foo.Bar"
+ app:actionProviderClass="com.foo.Baz" />
+ </menu>)");
+ menu->file.name = test::ParseNameOrDie("menu/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules(context.get(), menu.get(), &set));
+
+ std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar"));
+ EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz"));
+}
+
TEST(ProguardRulesTest, TransitionPathMotionRulesAreEmitted) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<xml::XmlResource> transition = test::BuildXmlDom(R"(
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index bc09f19..83e20b5 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -340,7 +340,7 @@
}
res_id = asset_manager_.GetResourceId(real_name.to_string());
- if (res_id.is_valid() && asset_manager_.GetResourceFlags(res_id.id, &type_spec_flags)) {
+ if (res_id.is_valid_static() && asset_manager_.GetResourceFlags(res_id.id, &type_spec_flags)) {
found = true;
return false;
}
@@ -379,7 +379,7 @@
std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById(
ResourceId id) {
- if (!id.is_valid()) {
+ if (!id.is_valid_static()) {
// Exit early and avoid the error logs from AssetManager.
return {};
}
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
index 6b4c346..ca1847a 100755
--- a/tools/localedata/extract_icu_data.py
+++ b/tools/localedata/extract_icu_data.py
@@ -176,6 +176,9 @@
dump_representative_locales(representative_locales)
return likely_script_dict
+def escape_script_variable_name(script):
+ """Escape characters, e.g. '~', in a C++ variable name"""
+ return script.replace("~", "_")
def read_parent_data(icu_data_dir):
"""Read locale parent data from ICU data files."""
@@ -221,7 +224,7 @@
for script in sorted_scripts:
parent_dict = script_organized_dict[script]
print ('const std::unordered_map<uint32_t, uint32_t> %s_PARENTS({'
- % script.upper())
+ % escape_script_variable_name(script.upper()))
for locale in sorted(parent_dict.keys()):
parent = parent_dict[locale]
print ' {0x%08Xu, 0x%08Xu}, // %s -> %s' % (
@@ -239,7 +242,7 @@
for script in sorted_scripts:
print " {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % (
script[0], script[1], script[2], script[3],
- script.upper())
+ escape_script_variable_name(script.upper()))
print '};'
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index 7733761..15c3278 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -21,6 +21,7 @@
name: "stats-log-api-gen",
srcs: [
"Collation.cpp",
+ "atoms_info_writer.cpp",
"java_writer.cpp",
"java_writer_q.cpp",
"main.cpp",
@@ -102,13 +103,19 @@
cc_library {
name: "libstatslog",
host_supported: true,
- generated_sources: ["statslog.cpp"],
- generated_headers: ["statslog.h"],
+ generated_sources: [
+ "statslog.cpp",
+ ],
+ generated_headers: [
+ "statslog.h"
+ ],
cflags: [
"-Wall",
"-Werror",
],
- export_generated_headers: ["statslog.h"],
+ export_generated_headers: [
+ "statslog.h"
+ ],
shared_libs: [
"liblog",
"libcutils",
@@ -127,3 +134,4 @@
},
},
}
+
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 373adca..fa55601 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -379,6 +379,7 @@
int errorCount = 0;
const bool dbg = false;
+ int maxPushedAtomId = 2;
for (int i = 0; i < descriptor->field_count(); i++) {
const FieldDescriptor *atomField = descriptor->field(i);
@@ -447,8 +448,14 @@
}
atoms->non_chained_decls.insert(nonChainedAtomDecl);
}
+
+ if (atomDecl.code < PULL_ATOM_START_ID && atomDecl.code > maxPushedAtomId) {
+ maxPushedAtomId = atomDecl.code;
+ }
}
+ atoms->maxPushedAtomId = maxPushedAtomId;
+
if (dbg) {
printf("signatures = [\n");
for (map<vector<java_type_t>, set<string>>::const_iterator it =
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 44746c9..3efdd52 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -111,6 +111,7 @@
set<AtomDecl> decls;
set<AtomDecl> non_chained_decls;
map<vector<java_type_t>, set<string>> non_chained_signatures_to_modules;
+ int maxPushedAtomId;
};
/**
@@ -123,4 +124,4 @@
} // namespace android
-#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
\ No newline at end of file
+#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
new file mode 100644
index 0000000..54a9982
--- /dev/null
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -0,0 +1,227 @@
+/*
+ * 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 "atoms_info_writer.h"
+#include "utils.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace android {
+namespace stats_log_api_gen {
+
+static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) {
+ fprintf(out, "struct StateAtomFieldOptions {\n");
+ fprintf(out, " std::vector<int> primaryFields;\n");
+ fprintf(out, " int exclusiveField;\n");
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "struct AtomsInfo {\n");
+ fprintf(out,
+ " const static std::set<int> "
+ "kTruncatingTimestampAtomBlackList;\n");
+ fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n");
+ fprintf(out,
+ " const static std::set<int> kAtomsWithAttributionChain;\n");
+ fprintf(out,
+ " const static std::map<int, StateAtomFieldOptions> "
+ "kStateAtomsFieldOptions;\n");
+ fprintf(out,
+ " const static std::map<int, std::vector<int>> "
+ "kBytesFieldAtoms;\n");
+ fprintf(out,
+ " const static std::set<int> kWhitelistedAtoms;\n");
+ fprintf(out, "};\n");
+ fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", atoms.maxPushedAtomId);
+
+}
+
+static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) {
+ std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
+ "audio_state_changed",
+ "call_state_changed",
+ "phone_signal_strength_changed",
+ "mobile_bytes_transfer_by_fg_bg",
+ "mobile_bytes_transfer"};
+ fprintf(out,
+ "const std::set<int> "
+ "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
+ for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
+ blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
+ fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
+ }
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out,
+ "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ for (vector<AtomField>::const_iterator field = atom->fields.begin();
+ field != atom->fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ break;
+ }
+ }
+ }
+
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out,
+ "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->whitelisted) {
+ string constant = make_constant_name(atom->name);
+ fprintf(out, " %s,\n", constant.c_str());
+ }
+ }
+
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
+ fprintf(out, " std::map<int, int> uidField;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->uidField == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding uid field for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+ fprintf(out, " uidField[static_cast<int>(%s)] = %d;\n",
+ make_constant_name(atom->name).c_str(), atom->uidField);
+ }
+
+ fprintf(out, " return uidField;\n");
+ fprintf(out, "};\n");
+
+ fprintf(out,
+ "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
+ "getAtomUidField();\n");
+
+ fprintf(out,
+ "static std::map<int, StateAtomFieldOptions> "
+ "getStateAtomFieldOptions() {\n");
+ fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n");
+ fprintf(out, " StateAtomFieldOptions opt;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding primary and exclusive fields for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+ fprintf(out, " opt.primaryFields.clear();\n");
+ for (const auto& field : atom->primaryFields) {
+ fprintf(out, " opt.primaryFields.push_back(%d);\n", field);
+ }
+
+ fprintf(out, " opt.exclusiveField = %d;\n", atom->exclusiveField);
+ fprintf(out, " options[static_cast<int>(%s)] = opt;\n",
+ make_constant_name(atom->name).c_str());
+ }
+
+ fprintf(out, " return options;\n");
+ fprintf(out, "}\n");
+
+ fprintf(out,
+ "const std::map<int, StateAtomFieldOptions> "
+ "AtomsInfo::kStateAtomsFieldOptions = "
+ "getStateAtomFieldOptions();\n");
+
+ fprintf(out,
+ "static std::map<int, std::vector<int>> "
+ "getBinaryFieldAtoms() {\n");
+ fprintf(out, " std::map<int, std::vector<int>> options;\n");
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ if (atom->binaryFields.size() == 0) {
+ continue;
+ }
+ fprintf(out,
+ "\n // Adding binary fields for atom "
+ "(%d)%s\n",
+ atom->code, atom->name.c_str());
+
+ for (const auto& field : atom->binaryFields) {
+ fprintf(out, " options[static_cast<int>(%s)].push_back(%d);\n",
+ make_constant_name(atom->name).c_str(), field);
+ }
+ }
+
+ fprintf(out, " return options;\n");
+ fprintf(out, "}\n");
+
+ fprintf(out,
+ "const std::map<int, std::vector<int>> "
+ "AtomsInfo::kBytesFieldAtoms = "
+ "getBinaryFieldAtoms();\n");
+
+}
+
+int write_atoms_info_header(FILE* out, const Atoms &atoms, const string& namespaceStr) {
+ // Print prelude
+ fprintf(out, "// This file is autogenerated\n");
+ fprintf(out, "\n");
+ fprintf(out, "#pragma once\n");
+ fprintf(out, "\n");
+ fprintf(out, "#include <vector>\n");
+ fprintf(out, "#include <map>\n");
+ fprintf(out, "#include <set>\n");
+ fprintf(out, "\n");
+
+ write_namespace(out, namespaceStr);
+
+ write_atoms_info_header_body(out, atoms);
+
+ fprintf(out, "\n");
+ write_closing_namespace(out, namespaceStr);
+
+ return 0;
+}
+
+int write_atoms_info_cpp(FILE *out, const Atoms &atoms, const string& namespaceStr,
+ const string& importHeader, const string& statslogHeader) {
+ // Print prelude
+ fprintf(out, "// This file is autogenerated\n");
+ fprintf(out, "\n");
+ fprintf(out, "#include <%s>\n", importHeader.c_str());
+ fprintf(out, "#include <%s>\n", statslogHeader.c_str());
+ fprintf(out, "\n");
+
+ write_namespace(out, namespaceStr);
+
+ write_atoms_info_cpp_body(out, atoms);
+
+ // Print footer
+ fprintf(out, "\n");
+ write_closing_namespace(out, namespaceStr);
+
+ return 0;
+}
+
+} // namespace stats_log_api_gen
+} // namespace android
diff --git a/tools/stats_log_api_gen/atoms_info_writer.h b/tools/stats_log_api_gen/atoms_info_writer.h
new file mode 100644
index 0000000..bc67782
--- /dev/null
+++ b/tools/stats_log_api_gen/atoms_info_writer.h
@@ -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.
+ */
+
+#pragma once
+
+#include "Collation.h"
+
+#include <stdio.h>
+#include <string.h>
+
+namespace android {
+namespace stats_log_api_gen {
+
+using namespace std;
+
+int write_atoms_info_cpp(FILE* out, const Atoms& atoms, const string& namespaceStr,
+ const string& importHeader, const string& statslogHeader
+);
+
+int write_atoms_info_header(FILE* out, const Atoms& atoms, const string& namespaceStr);
+
+} // namespace stats_log_api_gen
+} // namespace android
diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp
index d45c4e7..712b48e 100644
--- a/tools/stats_log_api_gen/java_writer.cpp
+++ b/tools/stats_log_api_gen/java_writer.cpp
@@ -189,6 +189,7 @@
}
fprintf(out, "\n");
+ fprintf(out, "%s builder.usePooledBuffer();\n", indent.c_str());
fprintf(out, "%s StatsLog.write(builder.build());\n", indent.c_str());
// Add support for writing using Q schema if this is not the default module.
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index bc6d82a..ad171da 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -1,6 +1,7 @@
#include "Collation.h"
+#include "atoms_info_writer.h"
#if !defined(STATS_SCHEMA_LEGACY)
#include "java_writer.h"
#endif
@@ -18,8 +19,6 @@
#include <stdlib.h>
#include <string.h>
-#include "android-base/strings.h"
-
using namespace google::protobuf;
using namespace std;
@@ -28,152 +27,6 @@
using android::os::statsd::Atom;
-static void write_atoms_info_cpp(FILE *out, const Atoms &atoms) {
- std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
- "audio_state_changed",
- "call_state_changed",
- "phone_signal_strength_changed",
- "mobile_bytes_transfer_by_fg_bg",
- "mobile_bytes_transfer"};
- fprintf(out,
- "const std::set<int> "
- "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
- for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
- blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
- fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
- }
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out,
- "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- for (vector<AtomField>::const_iterator field = atom->fields.begin();
- field != atom->fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- string constant = make_constant_name(atom->name);
- fprintf(out, " %s,\n", constant.c_str());
- break;
- }
- }
- }
-
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out,
- "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->whitelisted) {
- string constant = make_constant_name(atom->name);
- fprintf(out, " %s,\n", constant.c_str());
- }
- }
-
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
- fprintf(out, " std::map<int, int> uidField;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->uidField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding uid field for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
- fprintf(out, " uidField[static_cast<int>(%s)] = %d;\n",
- make_constant_name(atom->name).c_str(), atom->uidField);
- }
-
- fprintf(out, " return uidField;\n");
- fprintf(out, "};\n");
-
- fprintf(out,
- "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
- "getAtomUidField();\n");
-
- fprintf(out,
- "static std::map<int, StateAtomFieldOptions> "
- "getStateAtomFieldOptions() {\n");
- fprintf(out, " std::map<int, StateAtomFieldOptions> options;\n");
- fprintf(out, " StateAtomFieldOptions opt;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding primary and exclusive fields for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
- fprintf(out, " opt.primaryFields.clear();\n");
- for (const auto& field : atom->primaryFields) {
- fprintf(out, " opt.primaryFields.push_back(%d);\n", field);
- }
-
- fprintf(out, " opt.exclusiveField = %d;\n", atom->exclusiveField);
- fprintf(out, " options[static_cast<int>(%s)] = opt;\n",
- make_constant_name(atom->name).c_str());
- }
-
- fprintf(out, " return options;\n");
- fprintf(out, "}\n");
-
- fprintf(out,
- "const std::map<int, StateAtomFieldOptions> "
- "AtomsInfo::kStateAtomsFieldOptions = "
- "getStateAtomFieldOptions();\n");
-
- fprintf(out,
- "static std::map<int, std::vector<int>> "
- "getBinaryFieldAtoms() {\n");
- fprintf(out, " std::map<int, std::vector<int>> options;\n");
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- if (atom->binaryFields.size() == 0) {
- continue;
- }
- fprintf(out,
- "\n // Adding binary fields for atom "
- "(%d)%s\n",
- atom->code, atom->name.c_str());
-
- for (const auto& field : atom->binaryFields) {
- fprintf(out, " options[static_cast<int>(%s)].push_back(%d);\n",
- make_constant_name(atom->name).c_str(), field);
- }
- }
-
- fprintf(out, " return options;\n");
- fprintf(out, "}\n");
-
- fprintf(out,
- "const std::map<int, std::vector<int>> "
- "AtomsInfo::kBytesFieldAtoms = "
- "getBinaryFieldAtoms();\n");
-}
-
-// Writes namespaces for the cpp and header files, returning the number of namespaces written.
-void write_namespace(FILE* out, const string& cppNamespaces) {
- vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
- for (string cppNamespace : cppNamespaceVec) {
- fprintf(out, "namespace %s {\n", cppNamespace.c_str());
- }
-}
-
-// Writes namespace closing brackets for cpp and header files.
-void write_closing_namespace(FILE* out, const string& cppNamespaces) {
- vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
- for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) {
- fprintf(out, "} // namespace %s\n", it->c_str());
- }
-}
-
static int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &attributionDecl,
const string& moduleName, const string& cppNamespace,
const string& importHeader) {
@@ -202,11 +55,6 @@
fprintf(out, "const static bool kStatsdEnabled = false;\n");
fprintf(out, "#endif\n");
- // AtomsInfo is only used by statsd internally and is not needed for other modules.
- if (moduleName == DEFAULT_MODULE_NAME) {
- write_atoms_info_cpp(out, atoms);
- }
-
fprintf(out, "int64_t lastRetryTimestampNs = -1;\n");
fprintf(out, "const int64_t kMinRetryIntervalNs = NS_PER_SEC * 60 * 20; // 20 minutes\n");
fprintf(out, "static std::mutex mLogdRetryMutex;\n");
@@ -543,42 +391,6 @@
return 0;
}
-static void write_cpp_usage(
- FILE* out, const string& method_name, const string& atom_code_name,
- const AtomDecl& atom, const AtomDecl &attributionDecl) {
- fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
- atom_code_name.c_str());
-
- for (vector<AtomField>::const_iterator field = atom.fields.begin();
- field != atom.fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- for (auto chainField : attributionDecl.fields) {
- if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", const std::vector<%s>& %s",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str());
- } else {
- fprintf(out, ", const %s* %s, size_t %s_length",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str(), chainField.name.c_str());
- }
- }
- } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
- fprintf(out, ", const std::map<int, int32_t>& %s_int"
- ", const std::map<int, int64_t>& %s_long"
- ", const std::map<int, char const*>& %s_str"
- ", const std::map<int, float>& %s_float",
- field->name.c_str(),
- field->name.c_str(),
- field->name.c_str(),
- field->name.c_str());
- } else {
- fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
- }
- }
- fprintf(out, ");\n");
-}
-
static void write_cpp_method_header(
FILE* out,
const string& method_name,
@@ -645,45 +457,8 @@
fprintf(out, " * API For logging statistics events.\n");
fprintf(out, " */\n");
fprintf(out, "\n");
- fprintf(out, "/**\n");
- fprintf(out, " * Constants for atom codes.\n");
- fprintf(out, " */\n");
- fprintf(out, "enum {\n");
- std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
- build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
-
- size_t i = 0;
- int maxPushedAtomId = 2;
- // Print atom constants
- for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
- atom != atoms.decls.end(); atom++) {
- // Skip if the atom is not needed for the module.
- if (!atom_needed_for_module(*atom, moduleName)) {
- continue;
- }
- string constant = make_constant_name(atom->name);
- fprintf(out, "\n");
- fprintf(out, " /**\n");
- fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
- write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
-
- auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
- if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
- write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
- attributionDecl);
- }
- fprintf(out, " */\n");
- char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
- fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma);
- if (atom->code < PULL_ATOM_START_ID && atom->code > maxPushedAtomId) {
- maxPushedAtomId = atom->code;
- }
- i++;
- }
- fprintf(out, "\n");
- fprintf(out, "};\n");
- fprintf(out, "\n");
+ write_native_atom_constants(out, atoms, attributionDecl, moduleName);
// Print constants for the enum values.
fprintf(out, "//\n");
@@ -723,36 +498,6 @@
fprintf(out, "};\n");
fprintf(out, "\n");
- // This metadata is only used by statsd, which uses the default libstatslog.
- if (moduleName == DEFAULT_MODULE_NAME) {
-
- fprintf(out, "struct StateAtomFieldOptions {\n");
- fprintf(out, " std::vector<int> primaryFields;\n");
- fprintf(out, " int exclusiveField;\n");
- fprintf(out, "};\n");
- fprintf(out, "\n");
-
- fprintf(out, "struct AtomsInfo {\n");
- fprintf(out,
- " const static std::set<int> "
- "kTruncatingTimestampAtomBlackList;\n");
- fprintf(out, " const static std::map<int, int> kAtomsWithUidField;\n");
- fprintf(out,
- " const static std::set<int> kAtomsWithAttributionChain;\n");
- fprintf(out,
- " const static std::map<int, StateAtomFieldOptions> "
- "kStateAtomsFieldOptions;\n");
- fprintf(out,
- " const static std::map<int, std::vector<int>> "
- "kBytesFieldAtoms;");
- fprintf(out,
- " const static std::set<int> kWhitelistedAtoms;\n");
- fprintf(out, "};\n");
-
- fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n",
- maxPushedAtomId);
- }
-
// Print write methods
fprintf(out, "//\n");
fprintf(out, "// Write methods\n");
@@ -1235,15 +980,21 @@
fprintf(stderr, "usage: stats-log-api-gen OPTIONS\n");
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS\n");
- fprintf(stderr, " --cpp FILENAME the header file to output\n");
- fprintf(stderr, " --header FILENAME the cpp file to output\n");
+ fprintf(stderr, " --cpp FILENAME the header file to output for write helpers\n");
+ fprintf(stderr, " --header FILENAME the cpp file to output for write helpers\n");
+ fprintf(stderr,
+ " --atomsInfoCpp FILENAME the header file to output for statsd metadata\n");
+ fprintf(stderr, " --atomsInfoHeader FILENAME the cpp file to output for statsd metadata\n");
fprintf(stderr, " --help this message\n");
fprintf(stderr, " --java FILENAME the java file to output\n");
fprintf(stderr, " --jni FILENAME the jni file to output\n");
fprintf(stderr, " --module NAME optional, module name to generate outputs for\n");
fprintf(stderr, " --namespace COMMA,SEP,NAMESPACE required for cpp/header with module\n");
fprintf(stderr, " comma separated namespace of the files\n");
- fprintf(stderr, " --importHeader NAME required for cpp/jni to say which header to import\n");
+ fprintf(stderr," --importHeader NAME required for cpp/jni to say which header to import "
+ "for write helpers\n");
+ fprintf(stderr," --atomsInfoImportHeader NAME required for cpp to say which header to import "
+ "for statsd metadata\n");
fprintf(stderr, " --javaPackage PACKAGE the package for the java file.\n");
fprintf(stderr, " required for java with module\n");
fprintf(stderr, " --javaClass CLASS the class name of the java class.\n");
@@ -1260,10 +1011,13 @@
string headerFilename;
string javaFilename;
string jniFilename;
+ string atomsInfoCppFilename;
+ string atomsInfoHeaderFilename;
string moduleName = DEFAULT_MODULE_NAME;
string cppNamespace = DEFAULT_CPP_NAMESPACE;
string cppHeaderImport = DEFAULT_CPP_HEADER_IMPORT;
+ string atomsInfoCppHeaderImport = DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT;
string javaPackage = DEFAULT_JAVA_PACKAGE;
string javaClass = DEFAULT_JAVA_CLASS;
@@ -1335,14 +1089,38 @@
return 1;
}
javaClass = argv[index];
+ } else if (0 == strcmp("--atomsInfoHeader", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoHeaderFilename = argv[index];
+ } else if (0 == strcmp("--atomsInfoCpp", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoCppFilename = argv[index];
+ } else if (0 == strcmp("--atomsInfoImportHeader", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ atomsInfoCppHeaderImport = argv[index];
}
+
index++;
}
if (cppFilename.size() == 0
&& headerFilename.size() == 0
&& javaFilename.size() == 0
- && jniFilename.size() == 0) {
+ && jniFilename.size() == 0
+ && atomsInfoHeaderFilename.size() == 0
+ && atomsInfoCppFilename.size() == 0) {
print_usage();
return 1;
}
@@ -1359,6 +1137,30 @@
collate_atom(android::os::statsd::AttributionNode::descriptor(),
&attributionDecl, &attributionSignature);
+ // Write the atoms info .cpp file
+ if (atomsInfoCppFilename.size() != 0) {
+ FILE* out = fopen(atomsInfoCppFilename.c_str(), "w");
+ if (out == NULL) {
+ fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoCppFilename.c_str());
+ return 1;
+ }
+ errorCount = android::stats_log_api_gen::write_atoms_info_cpp(
+ out, atoms, cppNamespace, atomsInfoCppHeaderImport, cppHeaderImport);
+ fclose(out);
+ }
+
+ // Write the atoms info .h file
+ if (atomsInfoHeaderFilename.size() != 0) {
+ FILE* out = fopen(atomsInfoHeaderFilename.c_str(), "w");
+ if (out == NULL) {
+ fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoHeaderFilename.c_str());
+ return 1;
+ }
+ errorCount = android::stats_log_api_gen::write_atoms_info_header(out, atoms, cppNamespace);
+ fclose(out);
+ }
+
+
// Write the .cpp file
if (cppFilename.size() != 0) {
FILE* out = fopen(cppFilename.c_str(), "w");
diff --git a/tools/stats_log_api_gen/utils.cpp b/tools/stats_log_api_gen/utils.cpp
index 141861d..d6cfe95 100644
--- a/tools/stats_log_api_gen/utils.cpp
+++ b/tools/stats_log_api_gen/utils.cpp
@@ -16,9 +16,19 @@
#include "utils.h"
+#include "android-base/strings.h"
+
namespace android {
namespace stats_log_api_gen {
+static void build_non_chained_decl_map(const Atoms& atoms,
+ std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
+ for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
+ atom != atoms.non_chained_decls.end(); atom++) {
+ decl_map->insert(std::make_pair(atom->code, atom));
+ }
+}
+
/**
* Turn lower and camel case into upper case with underscores.
*/
@@ -102,14 +112,98 @@
return modules.find(moduleName) != modules.end();
}
-void build_non_chained_decl_map(const Atoms& atoms,
- std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
- for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
- atom != atoms.non_chained_decls.end(); atom++) {
- decl_map->insert(std::make_pair(atom->code, atom));
+// Native
+// Writes namespaces for the cpp and header files, returning the number of namespaces written.
+void write_namespace(FILE* out, const string& cppNamespaces) {
+ vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
+ for (string cppNamespace : cppNamespaceVec) {
+ fprintf(out, "namespace %s {\n", cppNamespace.c_str());
}
}
+// Writes namespace closing brackets for cpp and header files.
+void write_closing_namespace(FILE* out, const string& cppNamespaces) {
+ vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
+ for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) {
+ fprintf(out, "} // namespace %s\n", it->c_str());
+ }
+}
+
+static void write_cpp_usage(
+ FILE* out, const string& method_name, const string& atom_code_name,
+ const AtomDecl& atom, const AtomDecl &attributionDecl) {
+ fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(),
+ atom_code_name.c_str());
+
+ for (vector<AtomField>::const_iterator field = atom.fields.begin();
+ field != atom.fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (auto chainField : attributionDecl.fields) {
+ if (chainField.javaType == JAVA_TYPE_STRING) {
+ fprintf(out, ", const std::vector<%s>& %s",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str());
+ } else {
+ fprintf(out, ", const %s* %s, size_t %s_length",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str(), chainField.name.c_str());
+ }
+ }
+ } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
+ fprintf(out, ", const std::map<int, int32_t>& %s_int"
+ ", const std::map<int, int64_t>& %s_long"
+ ", const std::map<int, char const*>& %s_str"
+ ", const std::map<int, float>& %s_float",
+ field->name.c_str(),
+ field->name.c_str(),
+ field->name.c_str(),
+ field->name.c_str());
+ } else {
+ fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+ }
+ }
+ fprintf(out, ");\n");
+}
+
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& moduleName) {
+ fprintf(out, "/**\n");
+ fprintf(out, " * Constants for atom codes.\n");
+ fprintf(out, " */\n");
+ fprintf(out, "enum {\n");
+
+ std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+ build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
+ size_t i = 0;
+ // Print atom constants
+ for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+ atom != atoms.decls.end(); atom++) {
+ // Skip if the atom is not needed for the module.
+ if (!atom_needed_for_module(*atom, moduleName)) {
+ continue;
+ }
+ string constant = make_constant_name(atom->name);
+ fprintf(out, "\n");
+ fprintf(out, " /**\n");
+ fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
+ write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
+
+ auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+ if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+ write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
+ attributionDecl);
+ }
+ fprintf(out, " */\n");
+ char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
+ fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma);
+ i++;
+ }
+ fprintf(out, "\n");
+ fprintf(out, "};\n");
+ fprintf(out, "\n");
+}
+
// Java
void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName) {
fprintf(out, " // Constants for atom codes.\n");
diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h
index e860fa9..a89387f 100644
--- a/tools/stats_log_api_gen/utils.h
+++ b/tools/stats_log_api_gen/utils.h
@@ -33,6 +33,7 @@
const string DEFAULT_MODULE_NAME = "DEFAULT";
const string DEFAULT_CPP_NAMESPACE = "android,util";
const string DEFAULT_CPP_HEADER_IMPORT = "statslog.h";
+const string DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT = "atoms_info.h";
const string DEFAULT_JAVA_PACKAGE = "android.util";
const string DEFAULT_JAVA_CLASS = "StatsLogInternal";
@@ -49,8 +50,14 @@
bool signature_needed_for_module(const set<string>& modules, const string& moduleName);
-void build_non_chained_decl_map(const Atoms& atoms,
- std::map<int, set<AtomDecl>::const_iterator>* decl_map);
+// Common Native helpers
+void write_namespace(FILE* out, const string& cppNamespaces);
+
+void write_closing_namespace(FILE* out, const string& cppNamespaces);
+
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const string& moduleName
+);
// Common Java helpers.
void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName);
diff --git a/wifi/Android.bp b/wifi/Android.bp
new file mode 100644
index 0000000..fb1f866
--- /dev/null
+++ b/wifi/Android.bp
@@ -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.
+
+
+filegroup {
+ name: "framework-wifi-updatable-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ exclude_srcs: [
+ ":framework-wifi-non-updatable-sources"
+ ],
+ path: "java",
+}
+
+filegroup {
+ name: "framework-wifi-non-updatable-sources",
+ srcs: [
+ // TODO(b/146011398) package android.net.wifi is now split amongst 2 jars: framework.jar and
+ // 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/wificond/*.java",
+ ":libwificond_ipc_aidl",
+ ],
+}
+
+filegroup {
+ name: "framework-wifi-annotations",
+ srcs: ["java/android/net/wifi/WifiAnnotations.java"],
+}
+
+java_library {
+ name: "framework-wifi",
+ sdk_version: "core_platform", // TODO(b/140299412) should be core_current
+ libs: [
+ "framework-minus-apex", // TODO(b/140299412) should be framework-system-stubs
+ ],
+ srcs: [
+ ":framework-wifi-updatable-sources",
+ ],
+ installable: true,
+ optimize: {
+ enabled: false
+ }
+}
+
+droidstubs {
+ name: "framework-wifi-stubs-srcs",
+ srcs: [
+ ":framework-annotations",
+ ":framework-wifi-updatable-sources",
+ ],
+ aidl: {
+ include_dirs: ["frameworks/base/core/java"],
+ },
+ defaults: [ "framework-module-stubs-defaults-systemapi" ],
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+}
+
+java_library {
+ name: "framework-wifi-stubs",
+ srcs: [":framework-wifi-stubs-srcs"],
+ aidl: {
+ export_include_dirs: [
+ "java",
+ ],
+ },
+ sdk_version: "core_current",
+ libs: ["android_system_stubs_current"],
+ installable: false,
+}
+
diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
index b8c82fd..4fa93ee 100644
--- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
+++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
@@ -17,32 +17,46 @@
package android.net.wifi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.os.Handler;
+import android.util.SparseArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* Easy Connect (DPP) Status Callback. Use this callback to get status updates (success, failure,
* progress) from the Easy Connect operation started with
- * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String,
- * int, int, Handler, EasyConnectStatusCallback)} or
- * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
- * Handler, EasyConnectStatusCallback)}
+ * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Executor,
+ * EasyConnectStatusCallback)} or {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
+ * Executor, EasyConnectStatusCallback)}
*
* @hide
*/
@SystemApi
public abstract class EasyConnectStatusCallback {
/**
- * Easy Connect Success event: Configuration sent (Configurator mode).
+ * Easy Connect R1 Success event: Configuration sent (Configurator mode). This is the last
+ * and final Easy Connect event when either the local device or remote device implement R1.
+ * If both devices implement R2, this event will never be received, and the
+ * {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED} will be received.
*/
public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0;
+ /**
+ * East Connect R2 Success event: Configuration applied by Enrollee (Configurator mode).
+ * This is the last and final Easy Connect event when both the local device and remote device
+ * implement R2. If either the local device or remote device implement R1, this event will never
+ * be received, and the {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received.
+ */
+ public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1;
+
/** @hide */
@IntDef(prefix = {"EASY_CONNECT_EVENT_SUCCESS_"}, value = {
EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT,
+ EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EasyConnectSuccessStatusCode {
@@ -58,10 +72,22 @@
*/
public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1;
+ /**
+ * Easy Connect R2 Progress event: Configuration sent to Enrollee, waiting for response
+ */
+ public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2;
+
+ /**
+ * Easy Connect R2 Progress event: Configuration accepted by Enrollee, waiting for response
+ */
+ public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3;
+
/** @hide */
@IntDef(prefix = {"EASY_CONNECT_EVENT_PROGRESS_"}, value = {
EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS,
EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING,
+ EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE,
+ EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EasyConnectProgressStatusCode {
@@ -114,6 +140,20 @@
*/
public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9;
+ /**
+ * Easy Connect R2 Failure event: Enrollee cannot find the network.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10;
+
+ /**
+ * Easy Connect R2 Failure event: Enrollee failed to authenticate with the network.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11;
+
+ /**
+ * Easy Connect R2 Failure event: Enrollee rejected the configuration.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12;
/** @hide */
@IntDef(prefix = {"EASY_CONNECT_EVENT_FAILURE_"}, value = {
@@ -126,6 +166,9 @@
EASY_CONNECT_EVENT_FAILURE_GENERIC,
EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED,
EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK,
+ EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK,
+ EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION,
+ EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EasyConnectFailureStatusCode {
@@ -138,9 +181,8 @@
* current Easy Connect
* session, and no further callbacks will be called. This callback is the successful outcome
* of a Easy Connect flow starting with
- * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
- * Handler,
- * EasyConnectStatusCallback)}.
+ * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String, Executor,
+ * EasyConnectStatusCallback)} .
*
* @param newNetworkId New Wi-Fi configuration with a network ID received from the configurator
*/
@@ -148,13 +190,11 @@
/**
* Called when a Easy Connect success event takes place, except for when configuration is
- * received from
- * an external Configurator. The callback onSuccessConfigReceived will be used in this case.
- * This callback marks the successful end of the current Easy Connect session, and no further
- * callbacks will be called. This callback is the successful outcome of a Easy Connect flow
- * starting with
- * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Handler,
- * EasyConnectStatusCallback)}.
+ * received from an external Configurator. The callback onSuccessConfigReceived will be used in
+ * this case. This callback marks the successful end of the current Easy Connect session, and no
+ * further callbacks will be called. This callback is the successful outcome of a Easy Connect
+ * flow starting with {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int,
+ * int, Executor,EasyConnectStatusCallback)}.
*
* @param code Easy Connect success status code.
*/
@@ -162,12 +202,36 @@
/**
* Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful
- * end of the
- * current Easy Connect session, and no further callbacks will be called.
+ * end of the current Easy Connect session, and no further callbacks will be called.
*
* @param code Easy Connect failure status code.
*/
- public abstract void onFailure(@EasyConnectFailureStatusCode int code);
+ public void onFailure(@EasyConnectFailureStatusCode int code) {}
+
+ /**
+ * Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful
+ * end of the current Easy Connect session, and no further callbacks will be called.
+ *
+ * Note: Easy Connect (DPP) R2, provides additional details for the Configurator when the
+ * remote Enrollee is unable to connect to a network. The ssid, channelList and bandList
+ * inputs are initialized only for the EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK failure
+ * code, and the ssid and bandList are initialized for the
+ * EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION failure code.
+ *
+ * @param code Easy Connect failure status code.
+ * @param ssid SSID of the network the Enrollee tried to connect to.
+ * @param channelListArray List of Global Operating classes and channel sets the Enrollee used
+ * to scan to find the network, see the "DPP Connection Status Object"
+ * section in the specification for the format, and Table E-4 in
+ * IEEE Std 802.11-2016 - Global operating classes for more details.
+ * @param operatingClassArray Array of bands the Enrollee supports as expressed as the Global
+ * Operating Class, see Table E-4 in IEEE Std 802.11-2016 - Global
+ * operating classes.
+ */
+ public void onFailure(@EasyConnectFailureStatusCode int code, @Nullable String ssid,
+ @NonNull SparseArray<int[]> channelListArray, @NonNull int[] operatingClassArray) {
+ onFailure(code);
+ }
/**
* Called when Easy Connect events that indicate progress take place. Can be used by UI elements
diff --git a/wifi/java/android/net/wifi/IDppCallback.aidl b/wifi/java/android/net/wifi/IDppCallback.aidl
index c452c76..d7a958a 100644
--- a/wifi/java/android/net/wifi/IDppCallback.aidl
+++ b/wifi/java/android/net/wifi/IDppCallback.aidl
@@ -38,7 +38,7 @@
/**
* Called when DPP Failure events take place.
*/
- void onFailure(int status);
+ void onFailure(int status, String ssid, String channelList, in int[] bandArray);
/**
* Called when DPP events that indicate progress take place. Can be used by UI elements
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index b52880e..1678d5a 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -108,7 +108,9 @@
String getCountryCode();
- boolean isDualBandSupported();
+ boolean is5GHzBandSupported();
+
+ boolean is6GHzBandSupported();
boolean needs5GHzToAnyApBandConversion();
@@ -186,7 +188,7 @@
byte[] retrieveSoftApBackupData();
- void restoreSoftApBackupData(in byte[] data);
+ SoftApConfiguration restoreSoftApBackupData(in byte[] data);
void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData);
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 729ef61..83a1800 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -103,6 +103,12 @@
/**
* @hide
+ * Security protocol type: WAPI.
+ */
+ public static final int PROTOCOL_WAPI = 4;
+
+ /**
+ * @hide
* No security key management scheme.
*/
public static final int KEY_MGMT_NONE = 0;
@@ -169,6 +175,18 @@
public static final int KEY_MGMT_OWE_TRANSITION = 12;
/**
* @hide
+ * Security key management scheme: WAPI_PSK.
+ */
+ @SystemApi
+ public static final int KEY_MGMT_WAPI_PSK = 13;
+ /**
+ * @hide
+ * Security key management scheme: WAPI_CERT.
+ */
+ @SystemApi
+ public static final int KEY_MGMT_WAPI_CERT = 14;
+ /**
+ * @hide
* No cipher suite.
*/
public static final int CIPHER_NONE = 0;
@@ -192,6 +210,11 @@
* Cipher suite: GCMP
*/
public static final int CIPHER_GCMP_256 = 4;
+ /**
+ * @hide
+ * Cipher suite: SMS4
+ */
+ public static final int CIPHER_SMS4 = 5;
/**
* The detected signal level in dBm, also known as the RSSI.
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index d755053..fd8a924 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -60,32 +60,77 @@
* @hide
*/
@SystemApi
- public static final int BAND_2GHZ = 0;
+ public static final int BAND_2GHZ = 1 << 0;
/**
* 5GHz band.
* @hide
*/
@SystemApi
- public static final int BAND_5GHZ = 1;
+ public static final int BAND_5GHZ = 1 << 1;
/**
- * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability,
+ * 6GHz band.
+ * @hide
+ */
+ @SystemApi
+ public static final int BAND_6GHZ = 1 << 2;
+
+ /**
+ * Device is allowed to choose the optimal band (2Ghz, 5Ghz, 6Ghz) based on device capability,
* operating country code and current radio conditions.
* @hide
*/
@SystemApi
- public static final int BAND_ANY = -1;
+ public static final int BAND_ANY = BAND_2GHZ | BAND_5GHZ | BAND_6GHZ;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "BAND_TYPE_" }, value = {
+ @IntDef(flag = true, prefix = { "BAND_TYPE_" }, value = {
BAND_2GHZ,
BAND_5GHZ,
- BAND_ANY,
+ BAND_6GHZ,
})
public @interface BandType {}
+ private static boolean isBandValid(@BandType int band) {
+ return ((band != 0) && ((band & ~BAND_ANY) == 0));
+ }
+
+ private static final int MIN_CH_2G_BAND = 1;
+ private static final int MAX_CH_2G_BAND = 14;
+ private static final int MIN_CH_5G_BAND = 34;
+ private static final int MAX_CH_5G_BAND = 196;
+ private static final int MIN_CH_6G_BAND = 1;
+ private static final int MAX_CH_6G_BAND = 253;
+
+
+
+ private static boolean isChannelBandPairValid(int channel, @BandType int band) {
+ switch (band) {
+ case BAND_2GHZ:
+ if (channel < MIN_CH_2G_BAND || channel > MAX_CH_2G_BAND) {
+ return false;
+ }
+ break;
+
+ case BAND_5GHZ:
+ if (channel < MIN_CH_5G_BAND || channel > MAX_CH_5G_BAND) {
+ return false;
+ }
+ break;
+
+ case BAND_6GHZ:
+ if (channel < MIN_CH_6G_BAND || channel > MAX_CH_6G_BAND) {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
/**
* SSID for the AP, or null for a framework-determined SSID.
*/
@@ -439,39 +484,42 @@
* <p>
* <li>If not set, defaults to BAND_2GHZ {@link @BandType}.</li>
*
- * @param band One of the band types from {@link @BandType}.
+ * @param band One or combination of the band types from {@link @BandType}.
* @return Builder for chaining.
*/
@NonNull
public Builder setBand(@BandType int band) {
- switch (band) {
- case BAND_2GHZ:
- break;
- case BAND_5GHZ:
- break;
- case BAND_ANY:
- break;
- default:
- throw new IllegalArgumentException("Invalid band type");
+ if (!isBandValid(band)) {
+ throw new IllegalArgumentException("Invalid band type");
}
mBand = band;
+ // Since band preference is specified, no specific channel is selected.
+ mChannel = 0;
return this;
}
/**
- * Specifies the channel for the AP.
+ * Specifies the channel and associated band for the AP.
*
* The channel which AP resides on. Valid channels are country dependent.
- * Use the special channel value 0 to have the framework auto-select a valid channel
- * from the band configured with {@link #setBand(@BandType int)}.
+ * The default for the channel is a the special value 0 to have the framework
+ * auto-select a valid channel from the band configured with
+ * {@link #setBand(@BandType int)}.
+ * Note, since 6GHz band use the same channel numbering of 2.4GHz and 5GHZ bands,
+ * the caller needs to pass the band containing the selected channel.
*
* <p>
* <li>If not set, defaults to 0.</li>
* @param channel operating channel of the AP.
+ * @param band containing this channel.
* @return Builder for chaining.
*/
@NonNull
- public Builder setChannel(int channel) {
+ public Builder setChannel(int channel, @BandType int band) {
+ if (!isChannelBandPairValid(channel, band)) {
+ throw new IllegalArgumentException("Invalid band type");
+ }
+ mBand = band;
mChannel = channel;
return this;
}
diff --git a/wifi/java/android/net/wifi/SoftApInfo.java b/wifi/java/android/net/wifi/SoftApInfo.java
index 375a977..24ed8ef 100644
--- a/wifi/java/android/net/wifi/SoftApInfo.java
+++ b/wifi/java/android/net/wifi/SoftApInfo.java
@@ -16,15 +16,12 @@
package android.net.wifi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -85,26 +82,12 @@
*/
public static final int CHANNEL_WIDTH_160MHZ = 6;
- /**
- * @hide
- */
- @IntDef(prefix = { "CHANNEL_WIDTH_" }, value = {
- CHANNEL_WIDTH_INVALID,
- CHANNEL_WIDTH_20MHZ_NOHT,
- CHANNEL_WIDTH_20MHZ,
- CHANNEL_WIDTH_40MHZ,
- CHANNEL_WIDTH_80MHZ,
- CHANNEL_WIDTH_80MHZ_PLUS_MHZ,
- CHANNEL_WIDTH_160MHZ,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface Bandwidth {}
/** The frequency which AP resides on. */
private int mFrequency = 0;
- @Bandwidth
+ @WifiAnnotations.Bandwidth
private int mBandwidth = CHANNEL_WIDTH_INVALID;
/**
@@ -127,9 +110,9 @@
*
* @return One of {@link #CHANNEL_WIDTH_20MHZ}, {@link #CHANNEL_WIDTH_40MHZ},
* {@link #CHANNEL_WIDTH_80MHZ}, {@link #CHANNEL_WIDTH_160MHZ},
- * {@link #CHANNEL_WIDTH_80MHZ_PLUS_MHZ} or {@link #CHANNEL_WIDTH_UNKNOWN}.
+ * {@link #CHANNEL_WIDTH_80MHZ_PLUS_MHZ} or {@link #CHANNEL_WIDTH_INVALID}.
*/
- @Bandwidth
+ @WifiAnnotations.Bandwidth
public int getBandwidth() {
return mBandwidth;
}
@@ -138,7 +121,7 @@
* Set AP Channel bandwidth.
* @hide
*/
- public void setBandwidth(@Bandwidth int bandwidth) {
+ public void setBandwidth(@WifiAnnotations.Bandwidth int bandwidth) {
mBandwidth = bandwidth;
}
diff --git a/wifi/java/android/net/wifi/WifiAnnotations.java b/wifi/java/android/net/wifi/WifiAnnotations.java
new file mode 100644
index 0000000..9223d28
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiAnnotations.java
@@ -0,0 +1,63 @@
+/*
+ * 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.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Wifi annotations meant to be statically linked into client modules, since they cannot be
+ * exposed as @SystemApi.
+ *
+ * e.g. {@link IntDef}, {@link StringDef}
+ *
+ * @hide
+ */
+public final class WifiAnnotations {
+ private WifiAnnotations() {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SCAN_TYPE_"}, value = {
+ WifiScanner.SCAN_TYPE_LOW_LATENCY,
+ WifiScanner.SCAN_TYPE_LOW_POWER,
+ WifiScanner.SCAN_TYPE_HIGH_ACCURACY})
+ public @interface ScanType {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"WIFI_BAND_"}, value = {
+ WifiScanner.WIFI_BAND_UNSPECIFIED,
+ WifiScanner.WIFI_BAND_24_GHZ,
+ WifiScanner.WIFI_BAND_5_GHZ,
+ WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY,
+ WifiScanner.WIFI_BAND_6_GHZ})
+ public @interface WifiBandBasic {}
+
+ @IntDef(prefix = { "CHANNEL_WIDTH_" }, value = {
+ SoftApInfo.CHANNEL_WIDTH_INVALID,
+ SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT,
+ SoftApInfo.CHANNEL_WIDTH_20MHZ,
+ SoftApInfo.CHANNEL_WIDTH_40MHZ,
+ SoftApInfo.CHANNEL_WIDTH_80MHZ,
+ SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ,
+ SoftApInfo.CHANNEL_WIDTH_160MHZ,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Bandwidth {}
+}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index d068fc6..e3a945d 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -36,14 +36,9 @@
import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.BackupUtils;
import android.util.Log;
import android.util.SparseArray;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
@@ -123,7 +118,9 @@
OWE,
SUITE_B_192,
WPA_PSK_SHA256,
- WPA_EAP_SHA256})
+ WPA_EAP_SHA256,
+ WAPI_PSK,
+ WAPI_CERT})
public @interface KeyMgmtScheme {}
/** WPA is not used; plaintext or static WEP could be used. */
@@ -190,11 +187,26 @@
*/
public static final int WPA_EAP_SHA256 = 12;
+ /**
+ * WAPI pre-shared key (requires {@code preSharedKey} to be specified).
+ * @hide
+ */
+ @SystemApi
+ public static final int WAPI_PSK = 13;
+
+ /**
+ * WAPI certificate to be specified.
+ * @hide
+ */
+ @SystemApi
+ public static final int WAPI_CERT = 14;
+
public static final String varName = "key_mgmt";
public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP",
"IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP",
- "SAE", "OWE", "SUITE_B_192", "WPA_PSK_SHA256", "WPA_EAP_SHA256" };
+ "SAE", "OWE", "SUITE_B_192", "WPA_PSK_SHA256", "WPA_EAP_SHA256",
+ "WAPI_PSK", "WAPI_CERT" };
}
/**
@@ -215,9 +227,14 @@
*/
public static final int OSEN = 2;
+ /**
+ * WAPI Protocol
+ */
+ public static final int WAPI = 3;
+
public static final String varName = "proto";
- public static final String[] strings = { "WPA", "RSN", "OSEN" };
+ public static final String[] strings = { "WPA", "RSN", "OSEN", "WAPI" };
}
/**
@@ -260,10 +277,14 @@
* AES in Galois/Counter Mode
*/
public static final int GCMP_256 = 3;
+ /**
+ * SMS4 cipher for WAPI
+ */
+ public static final int SMS4 = 4;
public static final String varName = "pairwise";
- public static final String[] strings = { "NONE", "TKIP", "CCMP", "GCMP_256" };
+ public static final String[] strings = { "NONE", "TKIP", "CCMP", "GCMP_256", "SMS4" };
}
/**
@@ -301,12 +322,17 @@
* AES in Galois/Counter Mode
*/
public static final int GCMP_256 = 5;
+ /**
+ * SMS4 cipher for WAPI
+ */
+ public static final int SMS4 = 6;
public static final String varName = "group";
public static final String[] strings =
{ /* deprecated */ "WEP40", /* deprecated */ "WEP104",
- "TKIP", "CCMP", "GTK_NOT_USED", "GCMP_256" };
+ "TKIP", "CCMP", "GTK_NOT_USED", "GCMP_256",
+ "SMS4" };
}
/**
@@ -388,6 +414,10 @@
public static final int SECURITY_TYPE_EAP_SUITE_B = 5;
/** @hide */
public static final int SECURITY_TYPE_OWE = 6;
+ /** @hide */
+ public static final int SECURITY_TYPE_WAPI_PSK = 7;
+ /** @hide */
+ public static final int SECURITY_TYPE_WAPI_CERT = 8;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -398,7 +428,9 @@
SECURITY_TYPE_EAP,
SECURITY_TYPE_SAE,
SECURITY_TYPE_EAP_SUITE_B,
- SECURITY_TYPE_OWE
+ SECURITY_TYPE_OWE,
+ SECURITY_TYPE_WAPI_PSK,
+ SECURITY_TYPE_WAPI_CERT
})
public @interface SecurityType {}
@@ -450,6 +482,18 @@
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
requirePMF = true;
break;
+ case SECURITY_TYPE_WAPI_PSK:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_PSK);
+ allowedProtocols.set(WifiConfiguration.Protocol.WAPI);
+ allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.SMS4);
+ allowedGroupCiphers.set(WifiConfiguration.GroupCipher.SMS4);
+ break;
+ case SECURITY_TYPE_WAPI_CERT:
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WAPI_CERT);
+ allowedProtocols.set(WifiConfiguration.Protocol.WAPI);
+ allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.SMS4);
+ allowedGroupCiphers.set(WifiConfiguration.GroupCipher.SMS4);
+ break;
default:
throw new IllegalArgumentException("unknown security type " + securityType);
}
@@ -2350,6 +2394,10 @@
return KeyMgmt.OWE;
} else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
return KeyMgmt.SUITE_B_192;
+ } else if (allowedKeyManagement.get(KeyMgmt.WAPI_PSK)) {
+ return KeyMgmt.WAPI_PSK;
+ } else if (allowedKeyManagement.get(KeyMgmt.WAPI_CERT)) {
+ return KeyMgmt.WAPI_CERT;
}
return KeyMgmt.NONE;
}
@@ -2388,6 +2436,10 @@
key = SSID + KeyMgmt.strings[KeyMgmt.SAE];
} else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
key = SSID + KeyMgmt.strings[KeyMgmt.SUITE_B_192];
+ } else if (allowedKeyManagement.get(KeyMgmt.WAPI_PSK)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.WAPI_PSK];
+ } else if (allowedKeyManagement.get(KeyMgmt.WAPI_CERT)) {
+ key = SSID + KeyMgmt.strings[KeyMgmt.WAPI_CERT];
} else {
key = SSID + KeyMgmt.strings[KeyMgmt.NONE];
}
@@ -2768,54 +2820,4 @@
return new WifiConfiguration[size];
}
};
-
- /**
- * Serialize the Soft AP configuration contained in this object for backup.
- * @hide
- */
- @NonNull
- // TODO(b/144368124): this method should be removed once we migrate to SoftApConfiguration
- public byte[] getBytesForBackup() throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DataOutputStream out = new DataOutputStream(baos);
-
- out.writeInt(BACKUP_VERSION);
- BackupUtils.writeString(out, SSID);
- out.writeInt(apBand);
- out.writeInt(apChannel);
- BackupUtils.writeString(out, preSharedKey);
- out.writeInt(getAuthType());
- out.writeBoolean(hiddenSSID);
- return baos.toByteArray();
- }
-
- /**
- * Deserialize a byte array containing Soft AP configuration into a WifiConfiguration object.
- * @return The deserialized WifiConfiguration containing Soft AP configuration, or null if
- * the version contains a bad dataset e.g. Version 1
- * @throws BackupUtils.BadVersionException if the version is unrecognized
- * @hide
- */
- @Nullable
- // TODO(b/144368124): this method should be removed once we migrate to SoftApConfiguration
- public static WifiConfiguration getWifiConfigFromBackup(@NonNull DataInputStream in)
- throws IOException, BackupUtils.BadVersionException {
- WifiConfiguration config = new WifiConfiguration();
- int version = in.readInt();
- if (version < 1 || version > BACKUP_VERSION) {
- throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
- }
-
- if (version == 1) return null; // Version 1 is a bad dataset.
-
- config.SSID = BackupUtils.readString(in);
- config.apBand = in.readInt();
- config.apChannel = in.readInt();
- config.preSharedKey = BackupUtils.readString(in);
- config.allowedKeyManagement.set(in.readInt());
- if (version >= 3) {
- config.hiddenSSID = in.readBoolean();
- }
- return config;
- }
}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 449f95e..7a59a4f 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -22,7 +22,6 @@
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.security.Credentials;
import android.text.TextUtils;
import android.util.Log;
@@ -42,6 +41,36 @@
*/
public class WifiEnterpriseConfig implements Parcelable {
+ /** Key prefix for WAPI AS certificates. */
+ public static final String WAPI_AS_CERTIFICATE = "WAPIAS_";
+
+ /** Key prefix for WAPI user certificates. */
+ public static final String WAPI_USER_CERTIFICATE = "WAPIUSR_";
+
+ /**
+ * Intent extra: name for WAPI AS certificates
+ */
+ public static final String EXTRA_WAPI_AS_CERTIFICATE_NAME =
+ "android.net.wifi.extra.WAPI_AS_CERTIFICATE_NAME";
+
+ /**
+ * Intent extra: data for WAPI AS certificates
+ */
+ public static final String EXTRA_WAPI_AS_CERTIFICATE_DATA =
+ "android.net.wifi.extra.WAPI_AS_CERTIFICATE_DATA";
+
+ /**
+ * Intent extra: name for WAPI AS certificates
+ */
+ public static final String EXTRA_WAPI_USER_CERTIFICATE_NAME =
+ "android.net.wifi.extra.WAPI_USER_CERTIFICATE_NAME";
+
+ /**
+ * Intent extra: data for WAPI AS certificates
+ */
+ public static final String EXTRA_WAPI_USER_CERTIFICATE_DATA =
+ "android.net.wifi.extra.WAPI_USER_CERTIFICATE_DATA";
+
/** @hide */
public static final String EMPTY_VALUE = "NULL";
/** @hide */
@@ -62,6 +91,7 @@
public static final String DOM_SUFFIX_MATCH_KEY = "domain_suffix_match";
/** @hide */
public static final String OPP_KEY_CACHING = "proactive_key_caching";
+
/**
* String representing the keystore OpenSSL ENGINE's ID.
* @hide
@@ -93,10 +123,26 @@
*/
public static final String ENGINE_DISABLE = "0";
+ /**
+ * Key prefix for CA certificates.
+ * Note: copied from {@link android.security.Credentials#CA_CERTIFICATE} since it is @hide.
+ */
+ private static final String CA_CERTIFICATE = "CACERT_";
+ /**
+ * Key prefix for user certificates.
+ * Note: copied from {@link android.security.Credentials#USER_CERTIFICATE} since it is @hide.
+ */
+ private static final String USER_CERTIFICATE = "USRCERT_";
+ /**
+ * Key prefix for user private and secret keys.
+ * Note: copied from {@link android.security.Credentials#USER_PRIVATE_KEY} since it is @hide.
+ */
+ private static final String USER_PRIVATE_KEY = "USRPKEY_";
+
/** @hide */
- public static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
+ public static final String CA_CERT_PREFIX = KEYSTORE_URI + CA_CERTIFICATE;
/** @hide */
- public static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + Credentials.USER_CERTIFICATE;
+ public static final String CLIENT_CERT_PREFIX = KEYSTORE_URI + USER_CERTIFICATE;
/** @hide */
public static final String CLIENT_CERT_KEY = "client_cert";
/** @hide */
@@ -115,6 +161,8 @@
public static final String PLMN_KEY = "plmn";
/** @hide */
public static final String CA_CERT_ALIAS_DELIMITER = " ";
+ /** @hide */
+ public static final String WAPI_CERT_SUITE_KEY = "wapi_cert_suite";
/**
* Do not use OCSP stapling (TLS certificate status extension)
@@ -333,9 +381,12 @@
public static final int AKA_PRIME = 6;
/** Hotspot 2.0 r2 OSEN */
public static final int UNAUTH_TLS = 7;
+ /** WAPI Certificate */
+ public static final int WAPI_CERT = 8;
/** @hide */
public static final String[] strings =
- { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'", "WFA-UNAUTH-TLS" };
+ { "PEAP", "TLS", "TTLS", "PWD", "SIM", "AKA", "AKA'", "WFA-UNAUTH-TLS",
+ "WAPI_CERT" };
/** Prevent initialization */
private Eap() {}
@@ -479,6 +530,10 @@
public void setEapMethod(int eapMethod) {
switch (eapMethod) {
/** Valid methods */
+ case Eap.WAPI_CERT:
+ mEapMethod = eapMethod;
+ setPhase2Method(Phase2.NONE);
+ break;
case Eap.TLS:
case Eap.UNAUTH_TLS:
setPhase2Method(Phase2.NONE);
@@ -659,7 +714,7 @@
if (i > 0) {
sb.append(CA_CERT_ALIAS_DELIMITER);
}
- sb.append(encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + aliases[i]));
+ sb.append(encodeCaCertificateAlias(CA_CERTIFICATE + aliases[i]));
}
setFieldValue(CA_CERT_KEY, sb.toString(), KEYSTORES_URI);
}
@@ -693,8 +748,8 @@
String[] aliases = TextUtils.split(values, CA_CERT_ALIAS_DELIMITER);
for (int i = 0; i < aliases.length; i++) {
aliases[i] = decodeCaCertificateAlias(aliases[i]);
- if (aliases[i].startsWith(Credentials.CA_CERTIFICATE)) {
- aliases[i] = aliases[i].substring(Credentials.CA_CERTIFICATE.length());
+ if (aliases[i].startsWith(CA_CERTIFICATE)) {
+ aliases[i] = aliases[i].substring(CA_CERTIFICATE.length());
}
}
return aliases.length != 0 ? aliases : null;
@@ -832,7 +887,7 @@
@SystemApi
public void setClientCertificateAlias(@Nullable String alias) {
setFieldValue(CLIENT_CERT_KEY, alias, CLIENT_CERT_PREFIX);
- setFieldValue(PRIVATE_KEY_ID_KEY, alias, Credentials.USER_PRIVATE_KEY);
+ setFieldValue(PRIVATE_KEY_ID_KEY, alias, USER_PRIVATE_KEY);
// Also, set engine parameters
if (TextUtils.isEmpty(alias)) {
setFieldValue(ENGINE_KEY, ENGINE_DISABLE);
@@ -1298,4 +1353,29 @@
}
return false;
}
+
+ /**
+ * Set the WAPI certificate suite name on wpa_supplicant.
+ *
+ * If this field is not specified, WAPI-CERT uses ASU ID from WAI packet
+ * as the certificate suite name automatically.
+ *
+ * @param wapiCertSuite The name for WAPI certificate suite, or null/empty string to clear.
+ * @hide
+ */
+ @SystemApi
+ public void setWapiCertSuite(@Nullable String wapiCertSuite) {
+ setFieldValue(WAPI_CERT_SUITE_KEY, wapiCertSuite);
+ }
+
+ /**
+ * Get the WAPI certificate suite name
+ * @return the certificate suite name
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public String getWapiCertSuite() {
+ return getFieldValue(WAPI_CERT_SUITE_KEY);
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 86c398b..5ab0583 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -51,16 +51,17 @@
import android.os.WorkSource;
import android.os.connectivity.WifiActivityEnergyInfo;
import android.text.TextUtils;
+import android.util.CloseGuard;
import android.util.Log;
import android.util.Pair;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import dalvik.system.CloseGuard;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.ArrayList;
@@ -70,6 +71,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.StringTokenizer;
import java.util.concurrent.Executor;
/**
@@ -1069,7 +1071,7 @@
* @deprecated This API is non-functional and will have no impact.
*/
@Deprecated
- public static final int WIFI_MODE_FULL = WifiProtoEnums.WIFI_MODE_FULL; // 1
+ public static final int WIFI_MODE_FULL = 1;
/**
* In this Wi-Fi lock mode, Wi-Fi will be kept active,
@@ -1083,7 +1085,7 @@
* @deprecated This API is non-functional and will have no impact.
*/
@Deprecated
- public static final int WIFI_MODE_SCAN_ONLY = WifiProtoEnums.WIFI_MODE_SCAN_ONLY; // 2
+ public static final int WIFI_MODE_SCAN_ONLY = 2;
/**
* In this Wi-Fi lock mode, Wi-Fi will not go to power save.
@@ -1102,7 +1104,7 @@
* When there is no support from the hardware, the {@link #WIFI_MODE_FULL_HIGH_PERF}
* lock will have no impact.
*/
- public static final int WIFI_MODE_FULL_HIGH_PERF = WifiProtoEnums.WIFI_MODE_FULL_HIGH_PERF; // 3
+ public static final int WIFI_MODE_FULL_HIGH_PERF = 3;
/**
* In this Wi-Fi lock mode, Wi-Fi will operate with a priority to achieve low latency.
@@ -1132,8 +1134,8 @@
* lock will be effective when app is running in foreground and screen is on,
* while the {@link #WIFI_MODE_FULL_HIGH_PERF} lock will take effect otherwise.
*/
- public static final int WIFI_MODE_FULL_LOW_LATENCY =
- WifiProtoEnums.WIFI_MODE_FULL_LOW_LATENCY; // 4
+ public static final int WIFI_MODE_FULL_LOW_LATENCY = 4;
+
/** Anything worse than or equal to this will show 0 bars. */
@UnsupportedAppUsage
@@ -1153,6 +1155,7 @@
@UnsupportedAppUsage
public static final int RSSI_LEVELS = 5;
+ //TODO (b/146346676): This needs to be removed, not used in the code.
/**
* Auto settings in the driver. The driver could choose to operate on both
* 2.4 GHz and 5 GHz or make a dynamic decision on selecting the band.
@@ -2156,8 +2159,6 @@
/** @hide */
public static final long WIFI_FEATURE_INFRA = 0x0001L; // Basic infrastructure mode
/** @hide */
- public static final long WIFI_FEATURE_INFRA_5G = 0x0002L; // Support for 5 GHz Band
- /** @hide */
public static final long WIFI_FEATURE_PASSPOINT = 0x0004L; // Support for GAS/ANQP
/** @hide */
public static final long WIFI_FEATURE_P2P = 0x0008L; // Wifi-Direct
@@ -2228,7 +2229,7 @@
/** @hide */
public static final long WIFI_FEATURE_OCE = 0x1000000000L; // OCE Support
/** @hide */
- public static final long WIFI_FEATURE_INFRA_6G = 0x2000000000L; // Support 6 GHz band
+ public static final long WIFI_FEATURE_WAPI = 0x2000000000L; // WAPI
private long getSupportedFeatures() {
try {
@@ -2242,22 +2243,7 @@
return (getSupportedFeatures() & feature) == feature;
}
- /**
- * @return true if this adapter supports 5 GHz band
- */
- public boolean is5GHzBandSupported() {
- return isFeatureSupported(WIFI_FEATURE_INFRA_5G);
- }
-
- /**
- * @return true if the device supports operating in the 6 GHz band and Wi-Fi is enabled,
- * false otherwise.
- */
- public boolean is6GHzBandSupported() {
- return isFeatureSupported(WIFI_FEATURE_INFRA_6G);
- }
-
- /**
+ /**
* @return true if this adapter supports Passpoint
* @hide
*/
@@ -2379,6 +2365,30 @@
}
/**
+ * Check if the chipset supports 5GHz band.
+ * @return {@code true} if supported, {@code false} otherwise.
+ */
+ public boolean is5GHzBandSupported() {
+ try {
+ return mService.is5GHzBandSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check if the chipset supports 6GHz band.
+ * @return {@code true} if supported, {@code false} otherwise.
+ */
+ public boolean is6GHzBandSupported() {
+ try {
+ return mService.is6GHzBandSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Interface for Wi-Fi activity energy info listener. Should be implemented by applications and
* set when calling {@link WifiManager#getWifiActivityEnergyInfoAsync}.
*
@@ -2592,28 +2602,13 @@
}
/**
- * Check if the chipset supports dual frequency band (2.4 GHz and 5 GHz).
- * No permissions are required to call this method.
- * @return {@code true} if supported, {@code false} otherwise.
- * @hide
- */
- @SystemApi
- public boolean isDualBandSupported() {
- try {
- return mService.isDualBandSupported();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Check if the device is dual mode capable i.e. supports concurrent STA + Soft AP.
*
* If the device is dual mode capable, it may require conversion of the user's Soft AP band
- * selection {@link WifiConfiguration#apBand} from {@link WifiConfiguration#AP_BAND_5GHZ} to
- * {@link WifiConfiguration#AP_BAND_ANY}, since if the device is connected to a 5GHz DFS
- * channel as a STA, it may be unable to honor a request to start Soft AP on the same DFS
- * channel.
+ * selection {@link SoftApConfiguration#mBand} from {@link SoftApConfiguration#BAND_5GHZ} to
+ * include also {@link SoftApConfiguration#BAND_2GHZ}, since if the device is connected to a
+ * 5GHz DFS channel as a STA, it may be unable to honor a request to start Soft AP on the same
+ * DFS channel.
*
* @return {@code true} if dual mode STA + AP is supported by this device, {@code false}
* otherwise.
@@ -3574,7 +3569,7 @@
*/
public class LocalOnlyHotspotReservation implements AutoCloseable {
- private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final CloseGuard mCloseGuard = new CloseGuard();
private final WifiConfiguration mConfig;
private boolean mClosed = false;
@@ -3601,6 +3596,8 @@
}
} catch (Exception e) {
Log.e(TAG, "Failed to stop Local Only Hotspot.");
+ } finally {
+ Reference.reachabilityFence(this);
}
}
@@ -3725,7 +3722,7 @@
* @hide
*/
public class LocalOnlyHotspotSubscription implements AutoCloseable {
- private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final CloseGuard mCloseGuard = new CloseGuard();
/** @hide */
@VisibleForTesting
@@ -3740,6 +3737,8 @@
mCloseGuard.close();
} catch (Exception e) {
Log.e(TAG, "Failed to unregister LocalOnlyHotspotObserver.");
+ } finally {
+ Reference.reachabilityFence(this);
}
}
@@ -4686,10 +4685,13 @@
}
/**
- * Retrieve the soft ap config data to be backed to save current config data.
+ * Returns a byte stream representing the data that needs to be backed up to save the
+ * current soft ap config data.
+ *
+ * This soft ap config can be restored by calling {@link #restoreSoftApBackupData(byte[])}
* @hide
*/
- @Nullable
+ @NonNull
@SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public byte[] retrieveSoftApBackupData() {
@@ -4701,15 +4703,17 @@
}
/**
- * Restore soft ap config from the backed up data.
+ * Returns soft ap config from the backed up data.
+ * @param data byte stream in the same format produced by {@link #retrieveSoftApBackupData()}
+ *
* @hide
*/
@Nullable
@SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
- public void restoreSoftApBackupData(@NonNull byte[] data) {
+ public SoftApConfiguration restoreSoftApBackupData(@NonNull byte[] data) {
try {
- mService.restoreSoftApBackupData(data);
+ return mService.restoreSoftApBackupData(data);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4962,6 +4966,13 @@
}
/**
+ * @return true if this device supports WAPI.
+ */
+ public boolean isWapiSupported() {
+ return isFeatureSupported(WIFI_FEATURE_WAPI);
+ }
+
+ /**
* Gets the factory Wi-Fi MAC addresses.
* @return Array of String representing Wi-Fi MAC addresses sorted lexically or an empty Array
* if failed.
@@ -5168,6 +5179,7 @@
@Override
public void onSuccessConfigReceived(int newNetworkId) {
Log.d(TAG, "Easy Connect onSuccessConfigReceived callback");
+ Binder.clearCallingIdentity();
mExecutor.execute(() -> {
mEasyConnectStatusCallback.onEnrolleeSuccess(newNetworkId);
});
@@ -5176,22 +5188,28 @@
@Override
public void onSuccess(int status) {
Log.d(TAG, "Easy Connect onSuccess callback");
+ Binder.clearCallingIdentity();
mExecutor.execute(() -> {
mEasyConnectStatusCallback.onConfiguratorSuccess(status);
});
}
@Override
- public void onFailure(int status) {
+ public void onFailure(int status, String ssid, String channelList,
+ int[] operatingClassArray) {
Log.d(TAG, "Easy Connect onFailure callback");
+ Binder.clearCallingIdentity();
mExecutor.execute(() -> {
- mEasyConnectStatusCallback.onFailure(status);
+ SparseArray<int[]> channelListArray = parseDppChannelList(channelList);
+ mEasyConnectStatusCallback.onFailure(status, ssid, channelListArray,
+ operatingClassArray);
});
}
@Override
public void onProgress(int status) {
Log.d(TAG, "Easy Connect onProgress callback");
+ Binder.clearCallingIdentity();
mExecutor.execute(() -> {
mEasyConnectStatusCallback.onProgress(status);
});
@@ -5523,4 +5541,77 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Parse the list of channels the DPP enrollee reports when it fails to find an AP.
+ *
+ * @param channelList List of channels in the format defined in the DPP specification.
+ * @return A parsed sparse array, where the operating class is the key.
+ * @hide
+ */
+ @VisibleForTesting
+ public static SparseArray<int[]> parseDppChannelList(String channelList) {
+ SparseArray<int[]> channelListArray = new SparseArray<>();
+
+ if (TextUtils.isEmpty(channelList)) {
+ return channelListArray;
+ }
+ StringTokenizer str = new StringTokenizer(channelList, ",");
+ String classStr = null;
+ List<Integer> channelsInClass = new ArrayList<>();
+
+ try {
+ while (str.hasMoreElements()) {
+ String cur = str.nextToken();
+
+ /**
+ * Example for a channel list:
+ *
+ * 81/1,2,3,4,5,6,7,8,9,10,11,115/36,40,44,48,118/52,56,60,64,121/100,104,108,112,
+ * 116,120,124,128,132,136,140,0/144,124/149,153,157,161,125/165
+ *
+ * Detect operating class by the delimiter of '/' and use a string tokenizer with
+ * ',' as a delimiter.
+ */
+ int classDelim = cur.indexOf('/');
+ if (classDelim != -1) {
+ if (classStr != null) {
+ // Store the last channel array in the sparse array, where the operating
+ // class is the key (as an integer).
+ int[] channelsArray = new int[channelsInClass.size()];
+ for (int i = 0; i < channelsInClass.size(); i++) {
+ channelsArray[i] = channelsInClass.get(i);
+ }
+ channelListArray.append(Integer.parseInt(classStr), channelsArray);
+ channelsInClass = new ArrayList<>();
+ }
+
+ // Init a new operating class and store the first channel
+ classStr = cur.substring(0, classDelim);
+ String channelStr = cur.substring(classDelim + 1);
+ channelsInClass.add(Integer.parseInt(channelStr));
+ } else {
+ if (classStr == null) {
+ // Invalid format
+ Log.e(TAG, "Cannot parse DPP channel list");
+ return new SparseArray<>();
+ }
+ channelsInClass.add(Integer.parseInt(cur));
+ }
+ }
+
+ // Store the last array
+ if (classStr != null) {
+ int[] channelsArray = new int[channelsInClass.size()];
+ for (int i = 0; i < channelsInClass.size(); i++) {
+ channelsArray[i] = channelsInClass.get(i);
+ }
+ channelListArray.append(Integer.parseInt(classStr), channelsArray);
+ }
+ return channelListArray;
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Cannot parse DPP channel list");
+ return new SparseArray<>();
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
index 24aa23a..04d2e1a 100644
--- a/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkAgentSpecifier.java
@@ -23,7 +23,6 @@
import android.annotation.Nullable;
import android.net.MacAddress;
import android.net.MatchAllNetworkSpecifier;
-import android.net.NetworkAgent;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.os.Parcel;
@@ -120,7 +119,7 @@
/**
* Match {@link WifiNetworkSpecifier} in app's {@link NetworkRequest} with the
- * {@link WifiNetworkAgentSpecifier} in wifi platform's {@link NetworkAgent}.
+ * {@link WifiNetworkAgentSpecifier} in wifi platform's {@link android.net.NetworkAgent}.
*/
public boolean satisfiesNetworkSpecifier(@NonNull WifiNetworkSpecifier ns) {
// None of these should be null by construction.
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 9fd29ae..9c1475f 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -122,6 +122,16 @@
* Whether the setIsUserAllowedToManuallyConnect have been called.
*/
private boolean mIsUserAllowedBeenSet;
+ /**
+ * Pre-shared key for use with WAPI-PSK networks.
+ */
+ private @Nullable String mWapiPskPassphrase;
+
+ /**
+ * The enterprise configuration details specifying the EAP method,
+ * certificates and other settings associated with the WAPI networks.
+ */
+ private @Nullable WifiEnterpriseConfig mWapiEnterpriseConfig;
public Builder() {
mSsid = null;
@@ -140,6 +150,8 @@
mIsUserAllowedBeenSet = false;
mPriority = UNASSIGNED_PRIORITY;
mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+ mWapiPskPassphrase = null;
+ mWapiEnterpriseConfig = null;
}
/**
@@ -294,6 +306,39 @@
}
/**
+ * Set the ASCII WAPI passphrase for this network. Needed for authenticating to
+ * WAPI-PSK networks.
+ *
+ * @param passphrase passphrase of the network.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
+ *
+ */
+ public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) {
+ checkNotNull(passphrase);
+ final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
+ if (!asciiEncoder.canEncode(passphrase)) {
+ throw new IllegalArgumentException("passphrase not ASCII encodable");
+ }
+ mWapiPskPassphrase = passphrase;
+ return this;
+ }
+
+ /**
+ * Set the associated enterprise configuration for this network. Needed for authenticating
+ * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description.
+ *
+ * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setWapiEnterpriseConfig(
+ @NonNull WifiEnterpriseConfig enterpriseConfig) {
+ checkNotNull(enterpriseConfig);
+ mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
+ return this;
+ }
+
+ /**
* Specifies whether this represents a hidden network.
* <p>
* <li>If not set, defaults to false (i.e not a hidden network).</li>
@@ -413,6 +458,13 @@
configuration.enterpriseConfig = mWpa3EnterpriseConfig;
} else if (mIsEnhancedOpen) { // OWE network
configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
+ } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network.
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
+ // WifiConfiguration.preSharedKey needs quotes around ASCII password.
+ configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\"";
+ } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network
+ configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
+ configuration.enterpriseConfig = mWapiEnterpriseConfig;
} else { // Open network
configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
}
@@ -446,13 +498,16 @@
numSecurityTypes += mIsEnhancedOpen ? 1 : 0;
numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0;
numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0;
+ numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0;
numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0;
numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0;
+ numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0;
numSecurityTypes += mPasspointConfiguration != null ? 1 : 0;
if (numSecurityTypes > 1) {
throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase,"
- + "setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig"
- + "or setPasspointConfig can be invoked for network suggestion");
+ + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig"
+ + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto"
+ + " or setPasspointConfig can be invoked for network suggestion");
}
}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 760497b..8badcc0 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -89,16 +89,6 @@
/** 6 GHz band */
public static final int WIFI_BAND_6_GHZ = 1 << WIFI_BAND_INDEX_6_GHZ;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"WIFI_BAND_"}, value = {
- WIFI_BAND_UNSPECIFIED,
- WIFI_BAND_24_GHZ,
- WIFI_BAND_5_GHZ,
- WIFI_BAND_5_GHZ_DFS_ONLY,
- WIFI_BAND_6_GHZ})
- public @interface WifiBandBasic {}
-
/**
* Combination of bands
* Note that those are only the common band combinations,
@@ -249,14 +239,6 @@
*/
public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"SCAN_TYPE_"}, value = {
- SCAN_TYPE_LOW_LATENCY,
- SCAN_TYPE_LOW_POWER,
- SCAN_TYPE_HIGH_ACCURACY})
- public @interface ScanType {}
-
/**
* Optimize the scan for lower latency.
* @see ScanSettings#type
@@ -354,7 +336,7 @@
* {@link #SCAN_TYPE_HIGH_ACCURACY}.
* Default value: {@link #SCAN_TYPE_LOW_LATENCY}.
*/
- @ScanType
+ @WifiAnnotations.ScanType
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public int type = SCAN_TYPE_LOW_LATENCY;
/**
@@ -742,21 +724,6 @@
public int min24GHzRssi;
/** Minimum 6GHz RSSI for a BSSID to be considered */
public int min6GHzRssi;
- /** Maximum score that a network can have before bonuses */
- public int initialScoreMax;
- /**
- * Only report when there is a network's score this much higher
- * than the current connection.
- */
- public int currentConnectionBonus;
- /** score bonus for all networks with the same network flag */
- public int sameNetworkBonus;
- /** score bonus for networks that are not open */
- public int secureBonus;
- /** 5GHz RSSI score bonus (applied to all 5GHz networks) */
- public int band5GHzBonus;
- /** 6GHz RSSI score bonus (applied to all 5GHz networks) */
- public int band6GHzBonus;
/** Pno Network filter list */
public PnoNetwork[] networkList;
@@ -771,12 +738,6 @@
dest.writeInt(min5GHzRssi);
dest.writeInt(min24GHzRssi);
dest.writeInt(min6GHzRssi);
- dest.writeInt(initialScoreMax);
- dest.writeInt(currentConnectionBonus);
- dest.writeInt(sameNetworkBonus);
- dest.writeInt(secureBonus);
- dest.writeInt(band5GHzBonus);
- dest.writeInt(band6GHzBonus);
if (networkList != null) {
dest.writeInt(networkList.length);
for (int i = 0; i < networkList.length; i++) {
@@ -799,12 +760,6 @@
settings.min5GHzRssi = in.readInt();
settings.min24GHzRssi = in.readInt();
settings.min6GHzRssi = in.readInt();
- settings.initialScoreMax = in.readInt();
- settings.currentConnectionBonus = in.readInt();
- settings.sameNetworkBonus = in.readInt();
- settings.secureBonus = in.readInt();
- settings.band5GHzBonus = in.readInt();
- settings.band6GHzBonus = in.readInt();
int numNetworks = in.readInt();
settings.networkList = new PnoNetwork[numNetworks];
for (int i = 0; i < numNetworks; i++) {
diff --git a/wifi/java/android/net/wifi/aware/Characteristics.java b/wifi/java/android/net/wifi/aware/Characteristics.java
index e2cf4dc..d5fd48e 100644
--- a/wifi/java/android/net/wifi/aware/Characteristics.java
+++ b/wifi/java/android/net/wifi/aware/Characteristics.java
@@ -16,10 +16,14 @@
package android.net.wifi.aware;
+import android.annotation.IntDef;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* The characteristics of the Wi-Fi Aware implementation.
*/
@@ -31,6 +35,8 @@
"key_max_service_specific_info_length";
/** @hide */
public static final String KEY_MAX_MATCH_FILTER_LENGTH = "key_max_match_filter_length";
+ /** @hide */
+ public static final String KEY_SUPPORTED_CIPHER_SUITES = "key_supported_cipher_suites";
private Bundle mCharacteristics = new Bundle();
@@ -71,12 +77,41 @@
* {@link PublishConfig.Builder#setMatchFilter(java.util.List)} and
* {@link SubscribeConfig.Builder#setMatchFilter(java.util.List)}.
*
- * @return A positive integer, maximum legngth of byte array for Aware discovery match filter.
+ * @return A positive integer, maximum length of byte array for Aware discovery match filter.
*/
public int getMaxMatchFilterLength() {
return mCharacteristics.getInt(KEY_MAX_MATCH_FILTER_LENGTH);
}
+ /** @hide */
+ @IntDef(flag = true, prefix = { "WIFI_AWARE_CIPHER_SUITE_" }, value = {
+ WIFI_AWARE_CIPHER_SUITE_NCS_SK_128,
+ WIFI_AWARE_CIPHER_SUITE_NCS_SK_256,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface WifiAwareCipherSuites {}
+
+ /**
+ * Wi-Fi Aware supported ciphier suite representing NCS SK 128: 128 bit shared-key.
+ */
+ public static final int WIFI_AWARE_CIPHER_SUITE_NCS_SK_128 = 1 << 0;
+
+ /**
+ * Wi-Fi Aware supported ciphier suite representing NCS SK 256: 256 bit shared-key.
+ */
+ public static final int WIFI_AWARE_CIPHER_SUITE_NCS_SK_256 = 1 << 1;
+
+ /**
+ * Returns the set of cipher suites supported by the device for use in Wi-Fi Aware data-paths.
+ * The device automatically picks the strongest cipher suite when initiating a data-path setup.
+ *
+ * @return A set of flags from {@link #WIFI_AWARE_CIPHER_SUITE_NCS_SK_128}, or
+ * {@link #WIFI_AWARE_CIPHER_SUITE_NCS_SK_256}.
+ */
+ public @WifiAwareCipherSuites int getSupportedCipherSuites() {
+ return mCharacteristics.getInt(KEY_SUPPORTED_CIPHER_SUITES);
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeBundle(mCharacteristics);
diff --git a/wifi/java/android/net/wifi/aware/ConfigRequest.java b/wifi/java/android/net/wifi/aware/ConfigRequest.java
index b07d8ed..61ab92c 100644
--- a/wifi/java/android/net/wifi/aware/ConfigRequest.java
+++ b/wifi/java/android/net/wifi/aware/ConfigRequest.java
@@ -47,6 +47,7 @@
*/
public static final int NAN_BAND_24GHZ = 0;
public static final int NAN_BAND_5GHZ = 1;
+ public static final int NAN_BAND_6GHZ = 2;
/**
* Magic values for Discovery Window (DW) interval configuration
@@ -60,6 +61,11 @@
public final boolean mSupport5gBand;
/**
+ * Indicates whether 6G band support is requested.
+ */
+ public final boolean mSupport6gBand;
+
+ /**
* Specifies the desired master preference.
*/
public final int mMasterPreference;
@@ -81,9 +87,10 @@
*/
public final int mDiscoveryWindowInterval[];
- private ConfigRequest(boolean support5gBand, int masterPreference, int clusterLow,
- int clusterHigh, int discoveryWindowInterval[]) {
+ private ConfigRequest(boolean support5gBand, boolean support6gBand, int masterPreference,
+ int clusterLow, int clusterHigh, int[] discoveryWindowInterval) {
mSupport5gBand = support5gBand;
+ mSupport6gBand = support6gBand;
mMasterPreference = masterPreference;
mClusterLow = clusterLow;
mClusterHigh = clusterHigh;
@@ -92,10 +99,12 @@
@Override
public String toString() {
- return "ConfigRequest [mSupport5gBand=" + mSupport5gBand + ", mMasterPreference="
- + mMasterPreference + ", mClusterLow=" + mClusterLow + ", mClusterHigh="
- + mClusterHigh + ", mDiscoveryWindowInterval="
- + Arrays.toString(mDiscoveryWindowInterval) + "]";
+ return "ConfigRequest [mSupport5gBand=" + mSupport5gBand
+ + ", mSupport6gBand=" + mSupport6gBand
+ + ", mMasterPreference=" + mMasterPreference
+ + ", mClusterLow=" + mClusterLow
+ + ", mClusterHigh=" + mClusterHigh
+ + ", mDiscoveryWindowInterval=" + Arrays.toString(mDiscoveryWindowInterval) + "]";
}
@Override
@@ -106,6 +115,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mSupport5gBand ? 1 : 0);
+ dest.writeInt(mSupport6gBand ? 1 : 0);
dest.writeInt(mMasterPreference);
dest.writeInt(mClusterLow);
dest.writeInt(mClusterHigh);
@@ -121,13 +131,14 @@
@Override
public ConfigRequest createFromParcel(Parcel in) {
boolean support5gBand = in.readInt() != 0;
+ boolean support6gBand = in.readInt() != 0;
int masterPreference = in.readInt();
int clusterLow = in.readInt();
int clusterHigh = in.readInt();
int discoveryWindowInterval[] = in.createIntArray();
- return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh,
- discoveryWindowInterval);
+ return new ConfigRequest(support5gBand, support6gBand, masterPreference, clusterLow,
+ clusterHigh, discoveryWindowInterval);
}
};
@@ -143,7 +154,9 @@
ConfigRequest lhs = (ConfigRequest) o;
- return mSupport5gBand == lhs.mSupport5gBand && mMasterPreference == lhs.mMasterPreference
+ return mSupport5gBand == lhs.mSupport5gBand
+ && mSupport6gBand == lhs.mSupport6gBand
+ && mMasterPreference == lhs.mMasterPreference
&& mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh
&& Arrays.equals(mDiscoveryWindowInterval, lhs.mDiscoveryWindowInterval);
}
@@ -153,6 +166,7 @@
int result = 17;
result = 31 * result + (mSupport5gBand ? 1 : 0);
+ result = 31 * result + (mSupport6gBand ? 1 : 0);
result = 31 * result + mMasterPreference;
result = 31 * result + mClusterLow;
result = 31 * result + mClusterHigh;
@@ -190,9 +204,9 @@
throw new IllegalArgumentException(
"Invalid argument combination - must have Cluster Low <= Cluster High");
}
- if (mDiscoveryWindowInterval.length != 2) {
+ if (mDiscoveryWindowInterval.length != 3) {
throw new IllegalArgumentException(
- "Invalid discovery window interval: must have 2 elements (2.4 & 5");
+ "Invalid discovery window interval: must have 3 elements (2.4 & 5 & 6");
}
if (mDiscoveryWindowInterval[NAN_BAND_24GHZ] != DW_INTERVAL_NOT_INIT &&
(mDiscoveryWindowInterval[NAN_BAND_24GHZ] < 1 // valid for 2.4GHz: [1-5]
@@ -206,7 +220,12 @@
throw new IllegalArgumentException(
"Invalid discovery window interval for 5GHz: valid is UNSET or [0,5]");
}
-
+ if (mDiscoveryWindowInterval[NAN_BAND_6GHZ] != DW_INTERVAL_NOT_INIT
+ && (mDiscoveryWindowInterval[NAN_BAND_6GHZ] < 0 // valid for 6GHz: [0-5]
+ || mDiscoveryWindowInterval[NAN_BAND_6GHZ] > 5)) {
+ throw new IllegalArgumentException(
+ "Invalid discovery window interval for 6GHz: valid is UNSET or [0,5]");
+ }
}
/**
@@ -214,10 +233,12 @@
*/
public static final class Builder {
private boolean mSupport5gBand = true;
+ private boolean mSupport6gBand = false;
private int mMasterPreference = 0;
private int mClusterLow = CLUSTER_ID_MIN;
private int mClusterHigh = CLUSTER_ID_MAX;
- private int mDiscoveryWindowInterval[] = {DW_INTERVAL_NOT_INIT, DW_INTERVAL_NOT_INIT};
+ private int[] mDiscoveryWindowInterval = {DW_INTERVAL_NOT_INIT, DW_INTERVAL_NOT_INIT,
+ DW_INTERVAL_NOT_INIT};
/**
* Specify whether 5G band support is required in this request. Disabled by default.
@@ -233,6 +254,19 @@
}
/**
+ * Specify whether 6G band support is required in this request. Disabled by default.
+ *
+ * @param support6gBand Support for 6G band is required.
+ *
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder setSupport6gBand(boolean support6gBand) {
+ mSupport6gBand = support6gBand;
+ return this;
+ }
+
+ /**
* Specify the Master Preference requested. The permitted range is 0 (the default) to
* 255 with 1 and 255 excluded (reserved).
*
@@ -310,7 +344,8 @@
* awake. The configuration enables trading off latency vs. power (higher interval means
* higher discovery latency but lower power).
*
- * @param band Either {@link #NAN_BAND_24GHZ} or {@link #NAN_BAND_5GHZ}.
+ * @param band Either {@link #NAN_BAND_24GHZ} or {@link #NAN_BAND_5GHZ} or
+ * {@link #NAN_BAND_6GHZ}.
* @param interval A value of 1, 2, 3, 4, or 5 indicating an interval of 2^(interval-1). For
* the 5GHz band a value of 0 indicates that the device will not be awake
* for any discovery windows.
@@ -319,13 +354,14 @@
* {@code builder.setDiscoveryWindowInterval(...).setMasterPreference(...)}.
*/
public Builder setDiscoveryWindowInterval(int band, int interval) {
- if (band != NAN_BAND_24GHZ && band != NAN_BAND_5GHZ) {
+ if (band != NAN_BAND_24GHZ && band != NAN_BAND_5GHZ && band != NAN_BAND_6GHZ) {
throw new IllegalArgumentException("Invalid band value");
}
if ((band == NAN_BAND_24GHZ && (interval < 1 || interval > 5))
- || (band == NAN_BAND_5GHZ && (interval < 0 || interval > 5))) {
+ || (band == NAN_BAND_5GHZ && (interval < 0 || interval > 5))
+ || (band == NAN_BAND_6GHZ && (interval < 0 || interval > 5))) {
throw new IllegalArgumentException(
- "Invalid interval value: 2.4 GHz [1,5] or 5GHz [0,5]");
+ "Invalid interval value: 2.4 GHz [1,5] or 5GHz/6GHz [0,5]");
}
mDiscoveryWindowInterval[band] = interval;
@@ -342,8 +378,8 @@
"Invalid argument combination - must have Cluster Low <= Cluster High");
}
- return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh,
- mDiscoveryWindowInterval);
+ return new ConfigRequest(mSupport5gBand, mSupport6gBand, mMasterPreference, mClusterLow,
+ mClusterHigh, mDiscoveryWindowInterval);
}
}
}
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index d97f6fb..4d92ae1 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -20,12 +20,12 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.NetworkSpecifier;
+import android.util.CloseGuard;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import dalvik.system.CloseGuard;
-
+import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
/**
@@ -58,7 +58,7 @@
/** @hide */
protected boolean mTerminated = false;
- private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final CloseGuard mCloseGuard = new CloseGuard();
/**
* Return the maximum permitted retry count when sending messages using
@@ -108,6 +108,7 @@
mTerminated = true;
mMgr.clear();
mCloseGuard.close();
+ Reference.reachabilityFence(this);
}
/**
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 3c97813..fe0872c 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -23,12 +23,12 @@
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
+import android.util.CloseGuard;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import dalvik.system.CloseGuard;
-
+import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
/**
@@ -45,7 +45,7 @@
private final int mClientId;
private boolean mTerminated = true;
- private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final CloseGuard mCloseGuard = new CloseGuard();
/** @hide */
public WifiAwareSession(WifiAwareManager manager, Binder binder, int clientId) {
@@ -80,6 +80,7 @@
mTerminated = true;
mMgr.clear();
mCloseGuard.close();
+ Reference.reachabilityFence(this);
}
/** @hide */
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 495b1bb..c9bca4f 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -48,21 +48,29 @@
*/
public WpsInfo wps;
- /**
- * The network name of a group, should be configured by helper method
- */
+ /** Get the network name of this P2P configuration, or null if unset. */
+ @Nullable
+ public String getNetworkName() {
+ return networkName;
+ }
+
/** @hide */
public String networkName = "";
- /**
- * The passphrase of a group, should be configured by helper method
- */
+ /** Get the passphrase of this P2P configuration, or null if unset. */
+ @Nullable
+ public String getPassphrase() {
+ return passphrase;
+ }
+
/** @hide */
public String passphrase = "";
- /**
- * The required band for Group Owner
- */
+ /** Get the required band for the group owner. */
+ public int getGroupOwnerBand() {
+ return groupOwnerBand;
+ }
+
/** @hide */
public int groupOwnerBand = GROUP_OWNER_BAND_AUTO;
@@ -123,6 +131,15 @@
@UnsupportedAppUsage
public int netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ /**
+ * Get the network ID of this P2P configuration.
+ * @return either a non-negative network ID, or one of {@link WifiP2pGroup#PERSISTENT_NET_ID} or
+ * {@link WifiP2pGroup#TEMPORARY_NET_ID}.
+ */
+ public int getNetworkId() {
+ return netId;
+ }
+
public WifiP2pConfig() {
//set defaults
wps = new WpsInfo();
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index f9d1266..d8c50f2 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -39,18 +39,15 @@
/**
* The temporary network id.
- *
- * @hide
+ * @see #getNetworkId()
*/
- @UnsupportedAppUsage
public static final int TEMPORARY_NET_ID = -1;
/**
* The persistent network id.
* If a matching persistent profile is found, use it.
* Otherwise, create a new persistent profile.
- *
- * @hide
+ * @see #getNetworkId()
*/
public static final int PERSISTENT_NET_ID = -2;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 1c20679..6120e4e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -44,15 +44,15 @@
import android.os.Messenger;
import android.os.RemoteException;
import android.text.TextUtils;
+import android.util.CloseGuard;
import android.util.Log;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
-import dalvik.system.CloseGuard;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -869,7 +869,7 @@
private final Object mListenerMapLock = new Object();
private int mListenerKey = 0;
- private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final CloseGuard mCloseGuard = new CloseGuard();
/**
* Close the current P2P connection and indicate to the P2P service that connections
@@ -888,6 +888,7 @@
mAsyncChannel.disconnect();
mCloseGuard.close();
+ Reference.reachabilityFence(this);
}
/** @hide */
diff --git a/wifi/java/android/net/wifi/wificond/ChannelSettings.java b/wifi/java/android/net/wifi/wificond/ChannelSettings.java
new file mode 100644
index 0000000..c2d65b5
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/ChannelSettings.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * ChannelSettings for wificond
+ *
+ * @hide
+ */
+public class ChannelSettings implements Parcelable {
+ private static final String TAG = "ChannelSettings";
+
+ public int frequency;
+
+ /** public constructor */
+ public ChannelSettings() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof ChannelSettings)) {
+ return false;
+ }
+ ChannelSettings channel = (ChannelSettings) rhs;
+ if (channel == null) {
+ return false;
+ }
+ return frequency == channel.frequency;
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(frequency);
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flags| is ignored.
+ **/
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(frequency);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<ChannelSettings> CREATOR =
+ new Parcelable.Creator<ChannelSettings>() {
+ /**
+ * Caller is responsible for providing a valid parcel.
+ */
+ @Override
+ public ChannelSettings createFromParcel(Parcel in) {
+ ChannelSettings result = new ChannelSettings();
+ result.frequency = in.readInt();
+ if (in.dataAvail() != 0) {
+ Log.e(TAG, "Found trailing data after parcel parsing.");
+ }
+
+ return result;
+ }
+
+ @Override
+ public ChannelSettings[] newArray(int size) {
+ return new ChannelSettings[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/HiddenNetwork.java b/wifi/java/android/net/wifi/wificond/HiddenNetwork.java
new file mode 100644
index 0000000..38dacea
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/HiddenNetwork.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * HiddenNetwork for wificond
+ *
+ * @hide
+ */
+public class HiddenNetwork implements Parcelable {
+ private static final String TAG = "HiddenNetwork";
+
+ public byte[] ssid;
+
+ /** public constructor */
+ public HiddenNetwork() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof HiddenNetwork)) {
+ return false;
+ }
+ HiddenNetwork network = (HiddenNetwork) rhs;
+ return Arrays.equals(ssid, network.ssid);
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(ssid);
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flags| is ignored.
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeByteArray(ssid);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<HiddenNetwork> CREATOR =
+ new Parcelable.Creator<HiddenNetwork>() {
+ /**
+ * Caller is responsible for providing a valid parcel.
+ */
+ @Override
+ public HiddenNetwork createFromParcel(Parcel in) {
+ HiddenNetwork result = new HiddenNetwork();
+ result.ssid = in.createByteArray();
+ return result;
+ }
+
+ @Override
+ public HiddenNetwork[] newArray(int size) {
+ return new HiddenNetwork[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
new file mode 100644
index 0000000..6ed1708
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * Raw scan result data from the wificond daemon.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NativeScanResult implements Parcelable {
+ private static final int CAPABILITY_SIZE = 16;
+
+ /** @hide */
+ @VisibleForTesting
+ public byte[] ssid;
+ /** @hide */
+ @VisibleForTesting
+ public byte[] bssid;
+ /** @hide */
+ @VisibleForTesting
+ public byte[] infoElement;
+ /** @hide */
+ @VisibleForTesting
+ public int frequency;
+ /** @hide */
+ @VisibleForTesting
+ public int signalMbm;
+ /** @hide */
+ @VisibleForTesting
+ public long tsf;
+ /** @hide */
+ @VisibleForTesting
+ public BitSet capability;
+ /** @hide */
+ @VisibleForTesting
+ public boolean associated;
+ /** @hide */
+ @VisibleForTesting
+ public List<RadioChainInfo> radioChainInfos;
+
+ /**
+ * Returns the SSID raw byte array of the AP represented by this scan result.
+ *
+ * @return A byte array.
+ */
+ @NonNull public byte[] getSsid() {
+ return ssid;
+ }
+
+ /**
+ * Returns raw bytes representing the MAC address (BSSID) of the AP represented by this scan
+ * result.
+ *
+ * @return a byte array, possibly null or containing the incorrect number of bytes for a MAC
+ * address.
+ */
+ @NonNull public byte[] getBssid() {
+ return bssid;
+ }
+
+ /**
+ * Returns the raw bytes of the information element advertised by the AP represented by this
+ * scan result.
+ *
+ * @return A byte array, possibly null or containing an invalid TLV configuration.
+ */
+ @NonNull public byte[] getInformationElements() {
+ return infoElement;
+ }
+
+ /**
+ * Returns the frequency (in MHz) on which the AP represented by this scan result was observed.
+ *
+ * @return The frequency in MHz.
+ */
+ public int getFrequencyMhz() {
+ return frequency;
+ }
+
+ /**
+ * Return the signal strength of probe response/beacon in (100 * dBm).
+ *
+ * @return Signal strenght in (100 * dBm).
+ */
+ public int getSignalMbm() {
+ return signalMbm;
+ }
+
+ /**
+ * Return the TSF (Timing Synchronization Function) of the received probe response/beacon.
+ * @return
+ */
+ public long getTsf() {
+ return tsf;
+ }
+
+ /**
+ * Return a boolean indicating whether or not we're associated to the AP represented by this
+ * scan result.
+ *
+ * @return A boolean indicating association.
+ */
+ public boolean isAssociated() {
+ return associated;
+ }
+
+ /**
+ * Returns the capabilities of the AP repseresented by this scan result as advertised in the
+ * received probe response or beacon.
+ *
+ * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 8.4.1.4:
+ * Bit 0 - ESS
+ * Bit 1 - IBSS
+ * Bit 2 - CF Pollable
+ * Bit 3 - CF-Poll Request
+ * Bit 4 - Privacy
+ * Bit 5 - Short Preamble
+ * Bit 6 - PBCC
+ * Bit 7 - Channel Agility
+ * Bit 8 - Spectrum Mgmt
+ * Bit 9 - QoS
+ * Bit 10 - Short Slot Time
+ * Bit 11 - APSD
+ * Bit 12 - Radio Measurement
+ * Bit 13 - DSSS-OFDM
+ * Bit 14 - Delayed Block Ack
+ * Bit 15 - Immediate Block Ack
+ *
+ * @return a bit mask of capabilities.
+ */
+ @NonNull public BitSet getCapabilities() {
+ return capability;
+ }
+
+ /**
+ * Returns details of the signal received on each radio chain for the AP represented by this
+ * scan result in a list of {@link RadioChainInfo} elements.
+ *
+ * @return A list of {@link RadioChainInfo} - possibly empty in case of error.
+ */
+ @NonNull public List<RadioChainInfo> getRadioChainInfos() {
+ return radioChainInfos;
+ }
+
+ /**
+ * @hide
+ */
+ public NativeScanResult() { }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeByteArray(ssid);
+ out.writeByteArray(bssid);
+ out.writeByteArray(infoElement);
+ out.writeInt(frequency);
+ out.writeInt(signalMbm);
+ out.writeLong(tsf);
+ int capabilityInt = 0;
+ for (int i = 0; i < CAPABILITY_SIZE; i++) {
+ if (capability.get(i)) {
+ capabilityInt |= 1 << i;
+ }
+ }
+ out.writeInt(capabilityInt);
+ out.writeInt(associated ? 1 : 0);
+ out.writeTypedList(radioChainInfos);
+ }
+
+ /** implement Parcelable interface */
+ @NonNull public static final Parcelable.Creator<NativeScanResult> CREATOR =
+ new Parcelable.Creator<NativeScanResult>() {
+ @Override
+ public NativeScanResult createFromParcel(Parcel in) {
+ NativeScanResult result = new NativeScanResult();
+ result.ssid = in.createByteArray();
+ if (result.ssid == null) {
+ result.ssid = new byte[0];
+ }
+ result.bssid = in.createByteArray();
+ if (result.bssid == null) {
+ result.bssid = new byte[0];
+ }
+ result.infoElement = in.createByteArray();
+ if (result.infoElement == null) {
+ result.infoElement = new byte[0];
+ }
+ result.frequency = in.readInt();
+ result.signalMbm = in.readInt();
+ result.tsf = in.readLong();
+ int capabilityInt = in.readInt();
+ result.capability = new BitSet(CAPABILITY_SIZE);
+ for (int i = 0; i < CAPABILITY_SIZE; i++) {
+ if ((capabilityInt & (1 << i)) != 0) {
+ result.capability.set(i);
+ }
+ }
+ result.associated = (in.readInt() != 0);
+ result.radioChainInfos = new ArrayList<>();
+ in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR);
+ return result;
+ }
+
+ @Override
+ public NativeScanResult[] newArray(int size) {
+ return new NativeScanResult[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
new file mode 100644
index 0000000..554f929
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
@@ -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 android.net.wifi.wificond;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * Structure providing information about clients (STAs) associated with a SoftAp.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NativeWifiClient implements Parcelable {
+ /**
+ * The raw bytes of the MAC address of the client (STA) represented by this object.
+ */
+ @NonNull public final byte[] macAddress;
+
+ /**
+ * public constructor
+ * @hide
+ */
+ public NativeWifiClient(@NonNull byte[] macAddress) {
+ this.macAddress = macAddress;
+ }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof NativeWifiClient)) {
+ return false;
+ }
+ NativeWifiClient other = (NativeWifiClient) rhs;
+ return Arrays.equals(macAddress, other.macAddress);
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(macAddress);
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flag| is ignored.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeByteArray(macAddress);
+ }
+
+ /** implement Parcelable interface */
+ @NonNull public static final Parcelable.Creator<NativeWifiClient> CREATOR =
+ new Parcelable.Creator<NativeWifiClient>() {
+ @Override
+ public NativeWifiClient createFromParcel(Parcel in) {
+ byte[] macAddress = in.createByteArray();
+ if (macAddress == null) {
+ macAddress = new byte[0];
+ }
+ return new NativeWifiClient(macAddress);
+ }
+
+ @Override
+ public NativeWifiClient[] newArray(int size) {
+ return new NativeWifiClient[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/PnoNetwork.java b/wifi/java/android/net/wifi/wificond/PnoNetwork.java
new file mode 100644
index 0000000..ca0b1cf
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/PnoNetwork.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Configuration for a PNO (preferred network offload) network used in {@link PnoSettings}. A PNO
+ * network allows configuration of a specific network to search for.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PnoNetwork implements Parcelable {
+ private boolean mIsHidden;
+ private byte[] mSsid;
+ private int[] mFrequencies;
+
+ /**
+ * Indicates whether the PNO network configuration is for a hidden SSID - i.e. a network which
+ * does not broadcast its SSID and must be queried explicitly.
+ *
+ * @return True if the configuration is for a hidden network, false otherwise.
+ */
+ public boolean isHidden() {
+ return mIsHidden;
+ }
+
+ /**
+ * Configure whether the PNO network configuration is for a hidden SSID - i.e. a network which
+ * does not broadcast its SSID and must be queried explicitly.
+ *
+ * @param isHidden True if the configuration is for a hidden network, false otherwise.
+ */
+ public void setHidden(boolean isHidden) {
+ mIsHidden = isHidden;
+ }
+
+ /**
+ * Get the raw bytes for the SSID of the PNO network being scanned for.
+ *
+ * @return A byte array.
+ */
+ @NonNull public byte[] getSsid() {
+ return mSsid;
+ }
+
+ /**
+ * Set the raw bytes for the SSID of the PNO network being scanned for.
+ *
+ * @param ssid A byte array.
+ */
+ public void setSsid(@NonNull byte[] ssid) {
+ if (ssid == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+ this.mSsid = ssid;
+ }
+
+ /**
+ * Get the frequencies (in MHz) on which to PNO scan for the current network is being searched
+ * for. A null return (i.e. no frequencies configured) indicates that the network is search for
+ * on all supported frequencies.
+ *
+ * @return A array of frequencies (in MHz), a null indicates no configured frequencies.
+ */
+ @NonNull public int[] getFrequenciesMhz() {
+ return mFrequencies;
+ }
+
+ /**
+ * Set the frequencies (in MHz) on which to PNO scan for the current network is being searched
+ * for. A null configuration (i.e. no frequencies configured) indicates that the network is
+ * search for on all supported frequencies.
+ *
+ * @param frequenciesMhz an array of frequencies (in MHz), null indicating no configured
+ * frequencies.
+ */
+ public void setFrequenciesMhz(@NonNull int[] frequenciesMhz) {
+ if (frequenciesMhz == null) {
+ throw new IllegalArgumentException("null argument");
+ }
+ this.mFrequencies = frequenciesMhz;
+ }
+
+ /** Construct an uninitialized PnoNetwork object */
+ public PnoNetwork() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof PnoNetwork)) {
+ return false;
+ }
+ PnoNetwork network = (PnoNetwork) rhs;
+ return Arrays.equals(mSsid, network.mSsid)
+ && Arrays.equals(mFrequencies, network.mFrequencies)
+ && mIsHidden == network.mIsHidden;
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mIsHidden,
+ Arrays.hashCode(mSsid),
+ Arrays.hashCode(mFrequencies));
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flag| is ignored.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mIsHidden ? 1 : 0);
+ out.writeByteArray(mSsid);
+ out.writeIntArray(mFrequencies);
+ }
+
+ /** implement Parcelable interface */
+ @NonNull public static final Parcelable.Creator<PnoNetwork> CREATOR =
+ new Parcelable.Creator<PnoNetwork>() {
+ @Override
+ public PnoNetwork createFromParcel(Parcel in) {
+ PnoNetwork result = new PnoNetwork();
+ result.mIsHidden = in.readInt() != 0 ? true : false;
+ result.mSsid = in.createByteArray();
+ if (result.mSsid == null) {
+ result.mSsid = new byte[0];
+ }
+ result.mFrequencies = in.createIntArray();
+ if (result.mFrequencies == null) {
+ result.mFrequencies = new int[0];
+ }
+ return result;
+ }
+
+ @Override
+ public PnoNetwork[] newArray(int size) {
+ return new PnoNetwork[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/PnoSettings.java b/wifi/java/android/net/wifi/wificond/PnoSettings.java
new file mode 100644
index 0000000..57c9ca5
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/PnoSettings.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Configuration for a PNO (preferred network offload). A mechanism by which scans are offloaded
+ * from the host device to the Wi-Fi chip.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PnoSettings implements Parcelable {
+ private int mIntervalMs;
+ private int mMin2gRssi;
+ private int mMin5gRssi;
+ private int mMin6gRssi;
+ private List<PnoNetwork> mPnoNetworks;
+
+ /** Construct an uninitialized PnoSettings object */
+ public PnoSettings() { }
+
+ /**
+ * Get the requested PNO scan interval in milliseconds.
+ *
+ * @return An interval in milliseconds.
+ */
+ public int getIntervalMillis() {
+ return mIntervalMs;
+ }
+
+ /**
+ * Set the requested PNO scan interval in milliseconds.
+ *
+ * @param intervalMs An interval in milliseconds.
+ */
+ public void setIntervalMillis(int intervalMs) {
+ this.mIntervalMs = intervalMs;
+ }
+
+ /**
+ * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the
+ * 2.4GHz band.
+ *
+ * @return An RSSI value in dBm.
+ */
+ public int getMin2gRssiDbm() {
+ return mMin2gRssi;
+ }
+
+ /**
+ * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in
+ * the 2.4GHz band.
+ *
+ * @param min2gRssiDbm An RSSI value in dBm.
+ */
+ public void setMin2gRssiDbm(int min2gRssiDbm) {
+ this.mMin2gRssi = min2gRssiDbm;
+ }
+
+ /**
+ * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the
+ * 5GHz band.
+ *
+ * @return An RSSI value in dBm.
+ */
+ public int getMin5gRssiDbm() {
+ return mMin5gRssi;
+ }
+
+ /**
+ * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in
+ * the 5GHz band.
+ *
+ * @param min5gRssiDbm An RSSI value in dBm.
+ */
+ public void setMin5gRssiDbm(int min5gRssiDbm) {
+ this.mMin5gRssi = min5gRssiDbm;
+ }
+
+ /**
+ * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the
+ * 6GHz band.
+ *
+ * @return An RSSI value in dBm.
+ */
+ public int getMin6gRssiDbm() {
+ return mMin6gRssi;
+ }
+
+ /**
+ * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in
+ * the 6GHz band.
+ *
+ * @param min6gRssiDbm An RSSI value in dBm.
+ */
+ public void setMin6gRssiDbm(int min6gRssiDbm) {
+ this.mMin6gRssi = min6gRssiDbm;
+ }
+
+ /**
+ * Return the configured list of specific networks to search for in a PNO scan.
+ *
+ * @return A list of {@link PnoNetwork} objects, possibly empty if non configured.
+ */
+ @NonNull public List<PnoNetwork> getPnoNetworks() {
+ return mPnoNetworks;
+ }
+
+ /**
+ * Set the list of specified networks to scan for in a PNO scan. The networks (APs) are
+ * specified using {@link PnoNetwork}s. An empty list indicates that all networks are scanned
+ * for.
+ *
+ * @param pnoNetworks A (possibly empty) list of {@link PnoNetwork} objects.
+ */
+ public void setPnoNetworks(@NonNull List<PnoNetwork> pnoNetworks) {
+ this.mPnoNetworks = pnoNetworks;
+ }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof PnoSettings)) {
+ return false;
+ }
+ PnoSettings settings = (PnoSettings) rhs;
+ if (settings == null) {
+ return false;
+ }
+ return mIntervalMs == settings.mIntervalMs
+ && mMin2gRssi == settings.mMin2gRssi
+ && mMin5gRssi == settings.mMin5gRssi
+ && mMin6gRssi == settings.mMin6gRssi
+ && mPnoNetworks.equals(settings.mPnoNetworks);
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIntervalMs, mMin2gRssi, mMin5gRssi, mMin6gRssi, mPnoNetworks);
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flag| is ignored.
+ **/
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mIntervalMs);
+ out.writeInt(mMin2gRssi);
+ out.writeInt(mMin5gRssi);
+ out.writeInt(mMin6gRssi);
+ out.writeTypedList(mPnoNetworks);
+ }
+
+ /** implement Parcelable interface */
+ @NonNull public static final Parcelable.Creator<PnoSettings> CREATOR =
+ new Parcelable.Creator<PnoSettings>() {
+ @Override
+ public PnoSettings createFromParcel(Parcel in) {
+ PnoSettings result = new PnoSettings();
+ result.mIntervalMs = in.readInt();
+ result.mMin2gRssi = in.readInt();
+ result.mMin5gRssi = in.readInt();
+ result.mMin6gRssi = in.readInt();
+
+ result.mPnoNetworks = new ArrayList<>();
+ in.readTypedList(result.mPnoNetworks, PnoNetwork.CREATOR);
+
+ return result;
+ }
+
+ @Override
+ public PnoSettings[] newArray(int size) {
+ return new PnoSettings[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/RadioChainInfo.java b/wifi/java/android/net/wifi/wificond/RadioChainInfo.java
new file mode 100644
index 0000000..64102dd
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/RadioChainInfo.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * A class representing the radio chains of the Wi-Fi modems. Use to provide raw information about
+ * signals received on different radio chains.
+ *
+ * @hide
+ */
+@SystemApi
+public final class RadioChainInfo implements Parcelable {
+ private static final String TAG = "RadioChainInfo";
+
+ /** @hide */
+ @VisibleForTesting
+ public int chainId;
+ /** @hide */
+ @VisibleForTesting
+ public int level;
+
+ /**
+ * Return an identifier for this radio chain. This is an arbitrary ID which is consistent for
+ * the same device.
+ *
+ * @return The radio chain ID.
+ */
+ public int getChainId() {
+ return chainId;
+ }
+
+ /**
+ * Returns the detected signal level on this radio chain in dBm (aka RSSI).
+ *
+ * @return A signal level in dBm.
+ */
+ public int getLevelDbm() {
+ return level;
+ }
+
+ /** @hide */
+ public RadioChainInfo(int chainId, int level) {
+ this.chainId = chainId;
+ this.level = level;
+ }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof RadioChainInfo)) {
+ return false;
+ }
+ RadioChainInfo chainInfo = (RadioChainInfo) rhs;
+ if (chainInfo == null) {
+ return false;
+ }
+ return chainId == chainInfo.chainId && level == chainInfo.level;
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(chainId, level);
+ }
+
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flags| is ignored.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(chainId);
+ out.writeInt(level);
+ }
+
+ /** implement Parcelable interface */
+ @NonNull public static final Parcelable.Creator<RadioChainInfo> CREATOR =
+ new Parcelable.Creator<RadioChainInfo>() {
+ /**
+ * Caller is responsible for providing a valid parcel.
+ */
+ @Override
+ public RadioChainInfo createFromParcel(Parcel in) {
+ return new RadioChainInfo(in.readInt(), in.readInt());
+ }
+
+ @Override
+ public RadioChainInfo[] newArray(int size) {
+ return new RadioChainInfo[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/SingleScanSettings.java b/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
new file mode 100644
index 0000000..8065c01
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.net.wifi.IWifiScannerImpl;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * SingleScanSettings for wificond
+ *
+ * @hide
+ */
+public class SingleScanSettings implements Parcelable {
+ private static final String TAG = "SingleScanSettings";
+
+ public int scanType;
+ public ArrayList<ChannelSettings> channelSettings;
+ public ArrayList<HiddenNetwork> hiddenNetworks;
+
+ /** public constructor */
+ public SingleScanSettings() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof SingleScanSettings)) {
+ return false;
+ }
+ SingleScanSettings settings = (SingleScanSettings) rhs;
+ if (settings == null) {
+ return false;
+ }
+ return scanType == settings.scanType
+ && channelSettings.equals(settings.channelSettings)
+ && hiddenNetworks.equals(settings.hiddenNetworks);
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(scanType, channelSettings, hiddenNetworks);
+ }
+
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private static boolean isValidScanType(int scanType) {
+ return scanType == IWifiScannerImpl.SCAN_TYPE_LOW_SPAN
+ || scanType == IWifiScannerImpl.SCAN_TYPE_LOW_POWER
+ || scanType == IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flags| is ignored.
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (!isValidScanType(scanType)) {
+ Log.wtf(TAG, "Invalid scan type " + scanType);
+ }
+ out.writeInt(scanType);
+ out.writeTypedList(channelSettings);
+ out.writeTypedList(hiddenNetworks);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<SingleScanSettings> CREATOR =
+ new Parcelable.Creator<SingleScanSettings>() {
+ /**
+ * Caller is responsible for providing a valid parcel.
+ */
+ @Override
+ public SingleScanSettings createFromParcel(Parcel in) {
+ SingleScanSettings result = new SingleScanSettings();
+ result.scanType = in.readInt();
+ if (!isValidScanType(result.scanType)) {
+ Log.wtf(TAG, "Invalid scan type " + result.scanType);
+ }
+ result.channelSettings = new ArrayList<ChannelSettings>();
+ in.readTypedList(result.channelSettings, ChannelSettings.CREATOR);
+ result.hiddenNetworks = new ArrayList<HiddenNetwork>();
+ in.readTypedList(result.hiddenNetworks, HiddenNetwork.CREATOR);
+ if (in.dataAvail() != 0) {
+ Log.e(TAG, "Found trailing data after parcel parsing.");
+ }
+ return result;
+ }
+
+ @Override
+ public SingleScanSettings[] newArray(int size) {
+ return new SingleScanSettings[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
new file mode 100644
index 0000000..94f1212
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
@@ -0,0 +1,1165 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.net.wifi.IApInterface;
+import android.net.wifi.IApInterfaceEventCallback;
+import android.net.wifi.IClientInterface;
+import android.net.wifi.IPnoScanEvent;
+import android.net.wifi.IScanEvent;
+import android.net.wifi.ISendMgmtFrameEvent;
+import android.net.wifi.IWifiScannerImpl;
+import android.net.wifi.IWificond;
+import android.net.wifi.SoftApInfo;
+import android.net.wifi.WifiAnnotations;
+import android.net.wifi.WifiScanner;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The
+ * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.WIFI_COND_SERVICE)
+public class WifiCondManager {
+ private static final String TAG = "WifiCondManager";
+ private boolean mVerboseLoggingEnabled = false;
+
+ /**
+ * The {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
+ * timeout, in milliseconds, after which
+ * {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason
+ * {@link #SEND_MGMT_FRAME_ERROR_TIMEOUT}.
+ */
+ private static final int SEND_MGMT_FRAME_TIMEOUT_MS = 1000;
+
+ private static final String TIMEOUT_ALARM_TAG = TAG + " Send Management Frame Timeout";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SCAN_TYPE_"},
+ value = {SCAN_TYPE_SINGLE_SCAN,
+ SCAN_TYPE_PNO_SCAN})
+ public @interface ScanResultType {}
+
+ /**
+ * Specifies a scan type: single scan initiated by the framework. Can be used in
+ * {@link #getScanResults(String, int)} to specify the type of scan result to fetch.
+ */
+ public static final int SCAN_TYPE_SINGLE_SCAN = 0;
+
+ /**
+ * Specifies a scan type: PNO scan. Can be used in {@link #getScanResults(String, int)} to
+ * specify the type of scan result to fetch.
+ */
+ public static final int SCAN_TYPE_PNO_SCAN = 1;
+
+ private AlarmManager mAlarmManager;
+ private Handler mEventHandler;
+
+ // Cached wificond binder handlers.
+ private IWificond mWificond;
+ private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
+ private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
+ private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
+ private HashMap<String, IScanEvent> mScanEventHandlers = new HashMap<>();
+ private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
+ private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
+ private Runnable mDeathEventHandler;
+ /**
+ * Ensures that no more than one sendMgmtFrame operation runs concurrently.
+ */
+ private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false);
+
+ /**
+ * Interface used when waiting for scans to be completed (with results).
+ */
+ public interface ScanEventCallback {
+ /**
+ * Called when scan results are available. Scans results should then be obtained from
+ * {@link #getScanResults(String, int)}.
+ */
+ void onScanResultReady();
+
+ /**
+ * Called when a scan has failed.
+ */
+ void onScanFailed();
+ }
+
+ /**
+ * Interface for a callback to provide information about PNO scan request requested with
+ * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. Note that the
+ * callback are for the status of the request - not the scan itself. The results of the scan
+ * are returned with {@link ScanEventCallback}.
+ */
+ public interface PnoScanRequestCallback {
+ /**
+ * Called when a PNO scan request has been successfully submitted.
+ */
+ void onPnoRequestSucceeded();
+
+ /**
+ * Called when a PNO scan request fails.
+ */
+ void onPnoRequestFailed();
+ }
+
+ private class ScanEventHandler extends IScanEvent.Stub {
+ private Executor mExecutor;
+ private ScanEventCallback mCallback;
+
+ ScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void OnScanResultReady() {
+ Log.d(TAG, "Scan result ready event");
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onScanResultReady());
+ }
+
+ @Override
+ public void OnScanFailed() {
+ Log.d(TAG, "Scan failed event");
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onScanFailed());
+ }
+ }
+
+ /**
+ * Result of a signal poll requested using {@link #signalPoll(String)}.
+ */
+ public static class SignalPollResult {
+ /** @hide */
+ public SignalPollResult(int currentRssiDbm, int txBitrateMbps, int rxBitrateMbps,
+ int associationFrequencyMHz) {
+ this.currentRssiDbm = currentRssiDbm;
+ this.txBitrateMbps = txBitrateMbps;
+ this.rxBitrateMbps = rxBitrateMbps;
+ this.associationFrequencyMHz = associationFrequencyMHz;
+ }
+
+ /**
+ * RSSI value in dBM.
+ */
+ public final int currentRssiDbm;
+
+ /**
+ * Transmission bit rate in Mbps.
+ */
+ public final int txBitrateMbps;
+
+ /**
+ * Last received packet bit rate in Mbps.
+ */
+ public final int rxBitrateMbps;
+
+ /**
+ * Association frequency in MHz.
+ */
+ public final int associationFrequencyMHz;
+ }
+
+ /**
+ * Transmission counters obtained using {@link #getTxPacketCounters(String)}.
+ */
+ public static class TxPacketCounters {
+ /** @hide */
+ public TxPacketCounters(int txPacketSucceeded, int txPacketFailed) {
+ this.txPacketSucceeded = txPacketSucceeded;
+ this.txPacketFailed = txPacketFailed;
+ }
+
+ /**
+ * Number of successfully transmitted packets.
+ */
+ public final int txPacketSucceeded;
+
+ /**
+ * Number of packet transmission failures.
+ */
+ public final int txPacketFailed;
+ }
+
+ /**
+ * Callbacks for SoftAp interface registered using
+ * {@link #registerApCallback(String, Executor, SoftApCallback)}.
+ */
+ public interface SoftApCallback {
+ /**
+ * Invoked when there is a fatal failure and the SoftAp is shutdown.
+ */
+ void onFailure();
+
+ /**
+ * Invoked when there is a change in the associated station (STA).
+ * @param client Information about the client whose status has changed.
+ * @param isConnected Indication as to whether the client is connected (true), or
+ * disconnected (false).
+ */
+ void onConnectedClientsChanged(@NonNull NativeWifiClient client, boolean isConnected);
+
+ /**
+ * Invoked when a channel switch event happens - i.e. the SoftAp is moved to a different
+ * channel. Also called on initial registration.
+ * @param frequencyMhz The new frequency of the SoftAp. A value of 0 is invalid and is an
+ * indication that the SoftAp is not enabled.
+ * @param bandwidth The new bandwidth of the SoftAp.
+ */
+ void onSoftApChannelSwitched(int frequencyMhz, @WifiAnnotations.Bandwidth int bandwidth);
+ }
+
+ /**
+ * Callback to notify the results of a
+ * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} call.
+ * Note: no callbacks will be triggered if the interface dies while sending a frame.
+ */
+ public interface SendMgmtFrameCallback {
+ /**
+ * Called when the management frame was successfully sent and ACKed by the recipient.
+ * @param elapsedTimeMs The elapsed time between when the management frame was sent and when
+ * the ACK was processed, in milliseconds, as measured by wificond.
+ * This includes the time that the send frame spent queuing before it
+ * was sent, any firmware retries, and the time the received ACK spent
+ * queuing before it was processed.
+ */
+ void onAck(int elapsedTimeMs);
+
+ /**
+ * Called when the send failed.
+ * @param reason The error code for the failure.
+ */
+ void onFailure(@SendMgmtFrameError int reason);
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SEND_MGMT_FRAME_ERROR_"},
+ value = {SEND_MGMT_FRAME_ERROR_UNKNOWN,
+ SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED,
+ SEND_MGMT_FRAME_ERROR_NO_ACK,
+ SEND_MGMT_FRAME_ERROR_TIMEOUT,
+ SEND_MGMT_FRAME_ERROR_ALREADY_STARTED})
+ public @interface SendMgmtFrameError {}
+
+ // Send management frame error codes
+
+ /**
+ * Unknown error occurred during call to
+ * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1;
+
+ /**
+ * Specifying the MCS rate in
+ * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} is not
+ * supported by this device.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2;
+
+ /**
+ * Driver reported that no ACK was received for the frame transmitted using
+ * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3;
+
+ /**
+ * Error code for when the driver fails to report on the status of the frame sent by
+ * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
+ * after {@link #SEND_MGMT_FRAME_TIMEOUT_MS} milliseconds.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4;
+
+ /**
+ * An existing call to
+ * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
+ * is in progress. Another frame cannot be sent until the first call completes.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5;
+
+ /** @hide */
+ public WifiCondManager(Context context) {
+ mAlarmManager = context.getSystemService(AlarmManager.class);
+ mEventHandler = new Handler(context.getMainLooper());
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public WifiCondManager(Context context, IWificond wificond) {
+ this(context);
+ mWificond = wificond;
+ }
+
+ private class PnoScanEventHandler extends IPnoScanEvent.Stub {
+ private Executor mExecutor;
+ private ScanEventCallback mCallback;
+
+ PnoScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void OnPnoNetworkFound() {
+ Log.d(TAG, "Pno scan result event");
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onScanResultReady());
+ }
+
+ @Override
+ public void OnPnoScanFailed() {
+ Log.d(TAG, "Pno Scan failed event");
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onScanFailed());
+ }
+ }
+
+ /**
+ * Listener for AP Interface events.
+ */
+ private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
+ private Executor mExecutor;
+ private SoftApCallback mSoftApListener;
+
+ ApInterfaceEventCallback(Executor executor, SoftApCallback listener) {
+ mExecutor = executor;
+ mSoftApListener = listener;
+ }
+
+ @Override
+ public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "onConnectedClientsChanged called with "
+ + client.macAddress + " isConnected: " + isConnected);
+ }
+
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mSoftApListener.onConnectedClientsChanged(client, isConnected));
+ }
+
+ @Override
+ public void onSoftApChannelSwitched(int frequency, int bandwidth) {
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mSoftApListener.onSoftApChannelSwitched(frequency,
+ toFrameworkBandwidth(bandwidth)));
+ }
+
+ private @WifiAnnotations.Bandwidth int toFrameworkBandwidth(int bandwidth) {
+ switch(bandwidth) {
+ case IApInterfaceEventCallback.BANDWIDTH_INVALID:
+ return SoftApInfo.CHANNEL_WIDTH_INVALID;
+ case IApInterfaceEventCallback.BANDWIDTH_20_NOHT:
+ return SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
+ case IApInterfaceEventCallback.BANDWIDTH_20:
+ return SoftApInfo.CHANNEL_WIDTH_20MHZ;
+ case IApInterfaceEventCallback.BANDWIDTH_40:
+ return SoftApInfo.CHANNEL_WIDTH_40MHZ;
+ case IApInterfaceEventCallback.BANDWIDTH_80:
+ return SoftApInfo.CHANNEL_WIDTH_80MHZ;
+ case IApInterfaceEventCallback.BANDWIDTH_80P80:
+ return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
+ case IApInterfaceEventCallback.BANDWIDTH_160:
+ return SoftApInfo.CHANNEL_WIDTH_160MHZ;
+ default:
+ return SoftApInfo.CHANNEL_WIDTH_INVALID;
+ }
+ }
+ }
+
+ /**
+ * Callback triggered by wificond.
+ */
+ private class SendMgmtFrameEvent extends ISendMgmtFrameEvent.Stub {
+ private Executor mExecutor;
+ private SendMgmtFrameCallback mCallback;
+ private AlarmManager.OnAlarmListener mTimeoutCallback;
+ /**
+ * ensures that mCallback is only called once
+ */
+ private boolean mWasCalled;
+
+ private void runIfFirstCall(Runnable r) {
+ if (mWasCalled) return;
+ mWasCalled = true;
+
+ mSendMgmtFrameInProgress.set(false);
+ r.run();
+ }
+
+ SendMgmtFrameEvent(@NonNull Executor executor, @NonNull SendMgmtFrameCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ // called in main thread
+ mTimeoutCallback = () -> runIfFirstCall(() -> {
+ if (mVerboseLoggingEnabled) {
+ Log.e(TAG, "Timed out waiting for ACK");
+ }
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT));
+ });
+ mWasCalled = false;
+
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + SEND_MGMT_FRAME_TIMEOUT_MS,
+ TIMEOUT_ALARM_TAG, mTimeoutCallback, mEventHandler);
+ }
+
+ // called in binder thread
+ @Override
+ public void OnAck(int elapsedTimeMs) {
+ // post to main thread
+ mEventHandler.post(() -> runIfFirstCall(() -> {
+ mAlarmManager.cancel(mTimeoutCallback);
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onAck(elapsedTimeMs));
+ }));
+ }
+
+ // called in binder thread
+ @Override
+ public void OnFailure(int reason) {
+ // post to main thread
+ mEventHandler.post(() -> runIfFirstCall(() -> {
+ mAlarmManager.cancel(mTimeoutCallback);
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onFailure(reason));
+ }));
+ }
+ }
+
+ /**
+ * Called by the binder subsystem upon remote object death.
+ * Invoke all the register death handlers and clear state.
+ * @hide
+ */
+ @VisibleForTesting
+ public void binderDied() {
+ mEventHandler.post(() -> {
+ Log.e(TAG, "Wificond died!");
+ clearState();
+ // Invalidate the global wificond handle on death. Will be refreshed
+ // on the next setup call.
+ mWificond = null;
+ if (mDeathEventHandler != null) {
+ mDeathEventHandler.run();
+ }
+ });
+ }
+
+ /**
+ * Enable or disable verbose logging of the WifiCondManager module.
+ * @param enable True to enable verbose logging. False to disable verbose logging.
+ */
+ public void enableVerboseLogging(boolean enable) {
+ mVerboseLoggingEnabled = enable;
+ }
+
+ /**
+ * Initializes WifiCondManager & registers a death notification for the WifiCondManager which
+ * acts as a proxy for the wificond daemon (i.e. the death listener will be called when and if
+ * the wificond daemon dies).
+ *
+ * Note: This method clears any existing state in wificond daemon.
+ *
+ * @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies.
+ * @return Returns true on success.
+ */
+ public boolean initialize(@NonNull Runnable deathEventHandler) {
+ if (mDeathEventHandler != null) {
+ Log.e(TAG, "Death handler already present");
+ }
+ mDeathEventHandler = deathEventHandler;
+ tearDownInterfaces();
+ return true;
+ }
+
+ /**
+ * Helper method to retrieve the global wificond handle and register for
+ * death notifications.
+ */
+ private boolean retrieveWificondAndRegisterForDeath() {
+ if (mWificond != null) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "Wificond handle already retrieved");
+ }
+ // We already have a wificond handle.
+ return true;
+ }
+ IBinder binder = ServiceManager.getService(Context.WIFI_COND_SERVICE);
+ mWificond = IWificond.Stub.asInterface(binder);
+ if (mWificond == null) {
+ Log.e(TAG, "Failed to get reference to wificond");
+ return false;
+ }
+ try {
+ mWificond.asBinder().linkToDeath(() -> binderDied(), 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register death notification for wificond");
+ // The remote has already died.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set up an interface for client (STA) mode.
+ *
+ * @param ifaceName Name of the interface to configure.
+ * @param executor The Executor on which to execute the callbacks.
+ * @param scanCallback A callback for framework initiated scans.
+ * @param pnoScanCallback A callback for PNO (offloaded) scans.
+ * @return true on success.
+ */
+ public boolean setupInterfaceForClientMode(@NonNull String ifaceName,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) {
+ Log.d(TAG, "Setting up interface for client mode");
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+
+ if (scanCallback == null || pnoScanCallback == null || executor == null) {
+ Log.e(TAG, "setupInterfaceForClientMode invoked with null callbacks");
+ return false;
+ }
+
+ IClientInterface clientInterface = null;
+ try {
+ clientInterface = mWificond.createClientInterface(ifaceName);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to get IClientInterface due to remote exception");
+ return false;
+ }
+
+ if (clientInterface == null) {
+ Log.e(TAG, "Could not get IClientInterface instance from wificond");
+ return false;
+ }
+ Binder.allowBlocking(clientInterface.asBinder());
+
+ // Refresh Handlers
+ mClientInterfaces.put(ifaceName, clientInterface);
+ try {
+ IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
+ if (wificondScanner == null) {
+ Log.e(TAG, "Failed to get WificondScannerImpl");
+ return false;
+ }
+ mWificondScanners.put(ifaceName, wificondScanner);
+ Binder.allowBlocking(wificondScanner.asBinder());
+ ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback);
+ mScanEventHandlers.put(ifaceName, scanEventHandler);
+ wificondScanner.subscribeScanEvents(scanEventHandler);
+ PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(executor,
+ pnoScanCallback);
+ mPnoScanEventHandlers.put(ifaceName, pnoScanEventHandler);
+ wificondScanner.subscribePnoScanEvents(pnoScanEventHandler);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
+ }
+
+ return true;
+ }
+
+ /**
+ * Tear down a specific client (STA) interface, initially configured using
+ * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
+ *
+ * @param ifaceName Name of the interface to tear down.
+ * @return Returns true on success.
+ */
+ public boolean tearDownClientInterface(@NonNull String ifaceName) {
+ if (getClientInterface(ifaceName) == null) {
+ Log.e(TAG, "No valid wificond client interface handler");
+ return false;
+ }
+ try {
+ IWifiScannerImpl scannerImpl = mWificondScanners.get(ifaceName);
+ if (scannerImpl != null) {
+ scannerImpl.unsubscribeScanEvents();
+ scannerImpl.unsubscribePnoScanEvents();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unsubscribe wificond scanner due to remote exception");
+ return false;
+ }
+
+ if (mWificond == null) {
+ Log.e(TAG, "Reference to wifiCond is null");
+ return false;
+ }
+
+ boolean success;
+ try {
+ success = mWificond.tearDownClientInterface(ifaceName);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to teardown client interface due to remote exception");
+ return false;
+ }
+ if (!success) {
+ Log.e(TAG, "Failed to teardown client interface");
+ return false;
+ }
+
+ mClientInterfaces.remove(ifaceName);
+ mWificondScanners.remove(ifaceName);
+ mScanEventHandlers.remove(ifaceName);
+ mPnoScanEventHandlers.remove(ifaceName);
+ return true;
+ }
+
+ /**
+ * Set up interface as a Soft AP.
+ *
+ * @param ifaceName Name of the interface to configure.
+ * @return true on success.
+ */
+ public boolean setupInterfaceForSoftApMode(@NonNull String ifaceName) {
+ Log.d(TAG, "Setting up interface for soft ap mode");
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+
+ IApInterface apInterface = null;
+ try {
+ apInterface = mWificond.createApInterface(ifaceName);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to get IApInterface due to remote exception");
+ return false;
+ }
+
+ if (apInterface == null) {
+ Log.e(TAG, "Could not get IApInterface instance from wificond");
+ return false;
+ }
+ Binder.allowBlocking(apInterface.asBinder());
+
+ // Refresh Handlers
+ mApInterfaces.put(ifaceName, apInterface);
+ return true;
+ }
+
+ /**
+ * Tear down a Soft AP interface initially configured using
+ * {@link #setupInterfaceForSoftApMode(String)}.
+ *
+ * @param ifaceName Name of the interface to tear down.
+ * @return Returns true on success.
+ */
+ public boolean tearDownSoftApInterface(@NonNull String ifaceName) {
+ if (getApInterface(ifaceName) == null) {
+ Log.e(TAG, "No valid wificond ap interface handler");
+ return false;
+ }
+
+ if (mWificond == null) {
+ Log.e(TAG, "Reference to wifiCond is null");
+ return false;
+ }
+
+ boolean success;
+ try {
+ success = mWificond.tearDownApInterface(ifaceName);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to teardown AP interface due to remote exception");
+ return false;
+ }
+ if (!success) {
+ Log.e(TAG, "Failed to teardown AP interface");
+ return false;
+ }
+ mApInterfaces.remove(ifaceName);
+ mApInterfaceListeners.remove(ifaceName);
+ return true;
+ }
+
+ /**
+ * Tear down all interfaces, whether clients (STA) or Soft AP.
+ *
+ * @return Returns true on success.
+ */
+ public boolean tearDownInterfaces() {
+ Log.d(TAG, "tearing down interfaces in wificond");
+ // Explicitly refresh the wificodn handler because |tearDownInterfaces()|
+ // could be used to cleanup before we setup any interfaces.
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+
+ try {
+ for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
+ entry.getValue().unsubscribeScanEvents();
+ entry.getValue().unsubscribePnoScanEvents();
+ }
+ mWificond.tearDownInterfaces();
+ clearState();
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to tear down interfaces due to remote exception");
+ }
+
+ return false;
+ }
+
+ /** Helper function to look up the interface handle using name */
+ private IClientInterface getClientInterface(@NonNull String ifaceName) {
+ return mClientInterfaces.get(ifaceName);
+ }
+
+ /**
+ * Request signal polling.
+ *
+ * @param ifaceName Name of the interface on which to poll.
+ * @return A {@link SignalPollResult} object containing interface statistics, or a null on
+ * error.
+ */
+ @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) {
+ IClientInterface iface = getClientInterface(ifaceName);
+ if (iface == null) {
+ Log.e(TAG, "No valid wificond client interface handler");
+ return null;
+ }
+
+ int[] resultArray;
+ try {
+ resultArray = iface.signalPoll();
+ if (resultArray == null || resultArray.length != 4) {
+ Log.e(TAG, "Invalid signal poll result from wificond");
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to do signal polling due to remote exception");
+ return null;
+ }
+ return new SignalPollResult(resultArray[0], resultArray[1], resultArray[3], resultArray[2]);
+ }
+
+ /**
+ * Get current transmit (Tx) packet counters of the specified interface.
+ *
+ * @param ifaceName Name of the interface.
+ * @return {@link TxPacketCounters} of the current interface or null on error.
+ */
+ @Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
+ IClientInterface iface = getClientInterface(ifaceName);
+ if (iface == null) {
+ Log.e(TAG, "No valid wificond client interface handler");
+ return null;
+ }
+
+ int[] resultArray;
+ try {
+ resultArray = iface.getPacketCounters();
+ if (resultArray == null || resultArray.length != 2) {
+ Log.e(TAG, "Invalid signal poll result from wificond");
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to do signal polling due to remote exception");
+ return null;
+ }
+ return new TxPacketCounters(resultArray[0], resultArray[1]);
+ }
+
+ /** Helper function to look up the scanner impl handle using name */
+ private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) {
+ return mWificondScanners.get(ifaceName);
+ }
+
+ /**
+ * Fetch the latest scan results of the indicated type for the specified interface. Note that
+ * this method fetches the latest results - it does not initiate a scan. Initiating a scan can
+ * be done using {@link #startScan(String, int, Set, List)} or
+ * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
+ *
+ * @param ifaceName Name of the interface.
+ * @param scanType The type of scan result to be returned, can be
+ * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.
+ * @return Returns an array of {@link NativeScanResult} or an empty array on failure.
+ */
+ @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName,
+ @ScanResultType int scanType) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return new ArrayList<>();
+ }
+ List<NativeScanResult> results = null;
+ try {
+ if (scanType == SCAN_TYPE_SINGLE_SCAN) {
+ results = Arrays.asList(scannerImpl.getScanResults());
+ } else {
+ results = Arrays.asList(scannerImpl.getPnoScanResults());
+ }
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to create ScanDetail ArrayList");
+ }
+ if (results == null) {
+ results = new ArrayList<>();
+ }
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "get " + results.size() + " scan results from wificond");
+ }
+
+ return results;
+ }
+
+ /**
+ * Return scan type for the parcelable {@link SingleScanSettings}
+ */
+ private static int getScanType(@WifiAnnotations.ScanType int scanType) {
+ switch (scanType) {
+ case WifiScanner.SCAN_TYPE_LOW_LATENCY:
+ return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN;
+ case WifiScanner.SCAN_TYPE_LOW_POWER:
+ return IWifiScannerImpl.SCAN_TYPE_LOW_POWER;
+ case WifiScanner.SCAN_TYPE_HIGH_ACCURACY:
+ return IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ default:
+ throw new IllegalArgumentException("Invalid scan type " + scanType);
+ }
+ }
+
+ /**
+ * Start a scan using the specified parameters. A scan is an asynchronous operation. The
+ * result of the operation is returned in the {@link ScanEventCallback} registered when
+ * setting up an interface using
+ * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
+ * The latest scans can be obtained using {@link #getScanResults(String, int)} and using a
+ * {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}.
+ *
+ * @param ifaceName Name of the interface on which to initiate the scan.
+ * @param scanType Type of scan to perform, can be any of
+ * {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or
+ * {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}.
+ * @param freqs list of frequencies to scan for, if null scan all supported channels.
+ * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
+ * @return Returns true on success.
+ */
+ public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
+ @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return false;
+ }
+ SingleScanSettings settings = new SingleScanSettings();
+ try {
+ settings.scanType = getScanType(scanType);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Invalid scan type ", e);
+ return false;
+ }
+ settings.channelSettings = new ArrayList<>();
+ settings.hiddenNetworks = new ArrayList<>();
+
+ if (freqs != null) {
+ for (Integer freq : freqs) {
+ ChannelSettings channel = new ChannelSettings();
+ channel.frequency = freq;
+ settings.channelSettings.add(channel);
+ }
+ }
+ if (hiddenNetworkSSIDs != null) {
+ for (byte[] ssid : hiddenNetworkSSIDs) {
+ HiddenNetwork network = new HiddenNetwork();
+ network.ssid = ssid;
+
+ // settings.hiddenNetworks is expected to be very small, so this shouldn't cause
+ // any performance issues.
+ if (!settings.hiddenNetworks.contains(network)) {
+ settings.hiddenNetworks.add(network);
+ }
+ }
+ }
+
+ try {
+ return scannerImpl.scan(settings);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request scan due to remote exception");
+ }
+ return false;
+ }
+
+ /**
+ * Request a PNO (Preferred Network Offload). The offload request and the scans are asynchronous
+ * operations. The result of the request are returned in the {@code callback} parameter which
+ * is an {@link PnoScanRequestCallback}. The scan results are are return in the
+ * {@link ScanEventCallback} which is registered when setting up an interface using
+ * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
+ * The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the
+ * {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}.
+ *
+ * @param ifaceName Name of the interface on which to request a PNO.
+ * @param pnoSettings PNO scan configuration.
+ * @param executor The Executor on which to execute the callback.
+ * @param callback Callback for the results of the offload request.
+ * @return true on success.
+ */
+ public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull PnoScanRequestCallback callback) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return false;
+ }
+
+ if (callback == null || executor == null) {
+ Log.e(TAG, "startPnoScan called with a null callback");
+ return false;
+ }
+
+ try {
+ boolean success = scannerImpl.startPnoScan(pnoSettings);
+ if (success) {
+ executor.execute(callback::onPnoRequestSucceeded);
+ } else {
+ executor.execute(callback::onPnoRequestFailed);
+ }
+ return success;
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to start pno scan due to remote exception");
+ }
+ return false;
+ }
+
+ /**
+ * Stop PNO scan configured with
+ * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
+ *
+ * @param ifaceName Name of the interface on which the PNO scan was configured.
+ * @return true on success.
+ */
+ public boolean stopPnoScan(@NonNull String ifaceName) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return false;
+ }
+ try {
+ return scannerImpl.stopPnoScan();
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to stop pno scan due to remote exception");
+ }
+ return false;
+ }
+
+ /**
+ * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}.
+ *
+ * @param ifaceName Name of the interface on which the scan was started.
+ */
+ public void abortScan(@NonNull String ifaceName) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return;
+ }
+ try {
+ scannerImpl.abortScan();
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request abortScan due to remote exception");
+ }
+ }
+
+ /**
+ * Query the list of valid frequencies (in MHz) for the provided band.
+ * The result depends on the on the country code that has been set.
+ *
+ * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+ * The following bands are supported {@link @WifiScanner.WifiBandBasic}:
+ * WifiScanner.WIFI_BAND_24_GHZ
+ * WifiScanner.WIFI_BAND_5_GHZ
+ * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+ * WifiScanner.WIFI_BAND_6_GHZ
+ * @return frequencies vector of valid frequencies (MHz), or an empty array for error.
+ * @throws IllegalArgumentException if band is not recognized.
+ */
+ public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) {
+ if (mWificond == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return new int[0];
+ }
+ int[] result = null;
+ try {
+ switch (band) {
+ case WifiScanner.WIFI_BAND_24_GHZ:
+ result = mWificond.getAvailable2gChannels();
+ break;
+ case WifiScanner.WIFI_BAND_5_GHZ:
+ result = mWificond.getAvailable5gNonDFSChannels();
+ break;
+ case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
+ result = mWificond.getAvailableDFSChannels();
+ break;
+ case WifiScanner.WIFI_BAND_6_GHZ:
+ result = mWificond.getAvailable6gChannels();
+ break;
+ default:
+ throw new IllegalArgumentException("unsupported band " + band);
+ }
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+ }
+ if (result == null) {
+ result = new int[0];
+ }
+ return result;
+ }
+
+ /** Helper function to look up the interface handle using name */
+ private IApInterface getApInterface(@NonNull String ifaceName) {
+ return mApInterfaces.get(ifaceName);
+ }
+
+ /**
+ * Register the provided callback handler for SoftAp events. Note that the Soft AP itself is
+ * configured using {@link #setupInterfaceForSoftApMode(String)}.
+ *
+ * @param ifaceName Name of the interface on which to register the callback.
+ * @param executor The Executor on which to execute the callbacks.
+ * @param callback Callback for AP events.
+ * @return true on success, false otherwise.
+ */
+ public boolean registerApCallback(@NonNull String ifaceName,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SoftApCallback callback) {
+ IApInterface iface = getApInterface(ifaceName);
+ if (iface == null) {
+ Log.e(TAG, "No valid ap interface handler");
+ return false;
+ }
+
+ if (callback == null || executor == null) {
+ Log.e(TAG, "registerApCallback called with a null callback");
+ return false;
+ }
+
+ try {
+ IApInterfaceEventCallback wificondCallback = new ApInterfaceEventCallback(executor,
+ callback);
+ mApInterfaceListeners.put(ifaceName, wificondCallback);
+ boolean success = iface.registerCallback(wificondCallback);
+ if (!success) {
+ Log.e(TAG, "Failed to register ap callback.");
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in registering AP callback: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Send a management frame on the specified interface at the specified rate. Useful for probing
+ * the link with arbitrary frames.
+ *
+ * @param ifaceName The interface on which to send the frame.
+ * @param frame The raw byte array of the management frame to tramit.
+ * @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the
+ * frame. Specified per IEEE 802.11.
+ * @param executor The Executor on which to execute the callbacks.
+ * @param callback A {@link SendMgmtFrameCallback} callback for results of the operation.
+ */
+ public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame, int mcs,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SendMgmtFrameCallback callback) {
+
+ if (callback == null || executor == null) {
+ Log.e(TAG, "callback cannot be null!");
+ return;
+ }
+
+ if (frame == null) {
+ Log.e(TAG, "frame cannot be null!");
+ executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN));
+ return;
+ }
+
+ // TODO (b/112029045) validate mcs
+ IClientInterface clientInterface = getClientInterface(ifaceName);
+ if (clientInterface == null) {
+ Log.e(TAG, "No valid wificond client interface handler");
+ executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN));
+ return;
+ }
+
+ if (!mSendMgmtFrameInProgress.compareAndSet(false, true)) {
+ Log.e(TAG, "An existing management frame transmission is in progress!");
+ executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_ALREADY_STARTED));
+ return;
+ }
+
+ SendMgmtFrameEvent sendMgmtFrameEvent = new SendMgmtFrameEvent(executor, callback);
+ try {
+ clientInterface.SendMgmtFrame(frame, sendMgmtFrameEvent, mcs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while starting link probe: " + e);
+ // Call sendMgmtFrameEvent.OnFailure() instead of callback.onFailure() so that
+ // sendMgmtFrameEvent can clean up internal state, such as cancelling the timer.
+ sendMgmtFrameEvent.OnFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ }
+ }
+
+ /**
+ * Clear all internal handles.
+ */
+ private void clearState() {
+ // Refresh handlers
+ mClientInterfaces.clear();
+ mWificondScanners.clear();
+ mPnoScanEventHandlers.clear();
+ mScanEventHandlers.clear();
+ mApInterfaces.clear();
+ mApInterfaceListeners.clear();
+ mSendMgmtFrameInProgress.set(false);
+ }
+}
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index 2c27037..d58083c 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -28,7 +28,6 @@
import android.net.wifi.IOnWifiActivityEnergyInfoListener;
import android.net.wifi.IOnWifiUsabilityStatsListener;
import android.net.wifi.IScanResultsCallback;
-import android.net.wifi.IScanResultsListener;
import android.net.wifi.ISoftApCallback;
import android.net.wifi.ISuggestionConnectionStatusListener;
import android.net.wifi.ITrafficStateCallback;
@@ -227,12 +226,23 @@
throw new UnsupportedOperationException();
}
- @Override
+ /** @deprecated use {@link #is5GHzBandSupported} instead */
+ @Deprecated
public boolean isDualBandSupported() {
throw new UnsupportedOperationException();
}
@Override
+ public boolean is5GHzBandSupported() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean is6GHzBandSupported() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public boolean needs5GHzToAnyApBandConversion() {
throw new UnsupportedOperationException();
}
@@ -414,7 +424,7 @@
}
@Override
- public void restoreSoftApBackupData(byte[] data) {
+ public SoftApConfiguration restoreSoftApBackupData(byte[] data) {
throw new UnsupportedOperationException();
}
@@ -547,19 +557,6 @@
throw new UnsupportedOperationException();
}
- /** @deprecated replaced by {@link #registerScanResultsCallback(IScanResultsCallback)} */
- @Deprecated
- public void registerScanResultsListener(
- IBinder binder, IScanResultsListener listener, int listenerIdentifier) {
- throw new UnsupportedOperationException();
- }
-
- /** @deprecated replaced by {@link #unregisterScanResultsCallback(IScanResultsCallback)} */
- @Deprecated
- public void unregisterScanResultsListener(int listenerIdentifier) {
- throw new UnsupportedOperationException();
- }
-
@Override
public void registerScanResultsCallback(IScanResultsCallback callback) {
throw new UnsupportedOperationException();
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
index 3453d6e..d2c385b4 100644
--- a/wifi/tests/Android.mk
+++ b/wifi/tests/Android.mk
@@ -59,6 +59,8 @@
LOCAL_PACKAGE_NAME := FrameworksWifiApiTests
LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_COMPATIBILITY_SUITE := \
+ device-tests \
+ mts \
include $(BUILD_PACKAGE)
diff --git a/wifi/tests/AndroidTest.xml b/wifi/tests/AndroidTest.xml
index cae19e4..987fee7 100644
--- a/wifi/tests/AndroidTest.xml
+++ b/wifi/tests/AndroidTest.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
<configuration description="Runs Frameworks Wifi API Tests.">
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="FrameworksWifiApiTests.apk" />
</target_preparer>
diff --git a/wifi/tests/runtests.sh b/wifi/tests/runtests.sh
index 7a0dfb0..4024371 100755
--- a/wifi/tests/runtests.sh
+++ b/wifi/tests/runtests.sh
@@ -16,7 +16,6 @@
set -x # print commands
-adb root
adb wait-for-device
TARGET_ARCH=$($ANDROID_BUILD_TOP/build/soong/soong_ui.bash --dumpvar-mode TARGET_ARCH)
diff --git a/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java b/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
new file mode 100644
index 0000000..b101414
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.EasyConnectStatusCallbackTest}.
+ */
+@SmallTest
+public class EasyConnectStatusCallbackTest {
+ private EasyConnectStatusCallback mEasyConnectStatusCallback = new EasyConnectStatusCallback() {
+ @Override
+ public void onEnrolleeSuccess(int newNetworkId) {
+
+ }
+
+ @Override
+ public void onConfiguratorSuccess(int code) {
+
+ }
+
+ @Override
+ public void onProgress(int code) {
+
+ }
+
+ @Override
+ public void onFailure(int code) {
+ mOnFailureR1EventReceived = true;
+ mLastCode = code;
+ }
+ };
+ private boolean mOnFailureR1EventReceived;
+ private int mLastCode;
+
+ @Before
+ public void setUp() {
+ mOnFailureR1EventReceived = false;
+ mLastCode = 0;
+ }
+
+ /**
+ * Test that the legacy R1 onFailure is called by default if the R2 onFailure is not overridden
+ * by the app.
+ */
+ @Test
+ public void testR1OnFailureCalled() {
+
+ SparseArray<int[]> channelList = new SparseArray<>();
+ int[] channelArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+ channelList.append(81, channelArray);
+ mEasyConnectStatusCallback.onFailure(
+ EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK,
+ "SomeSSID", channelList, new int[] {81});
+
+ assertTrue(mOnFailureR1EventReceived);
+ assertEquals(mLastCode,
+ EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK);
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index b8d3e41..60125e3 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -91,13 +91,13 @@
SoftApConfiguration original = new SoftApConfiguration.Builder()
.setWpa2Passphrase("secretsecret")
.setBand(SoftApConfiguration.BAND_ANY)
- .setChannel(149)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
.setHiddenSsid(true)
.build();
assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
assertThat(original.getSecurityType()).isEqualTo(
SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
- assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_ANY);
+ assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
assertThat(original.getChannel()).isEqualTo(149);
assertThat(original.isHiddenSsid()).isEqualTo(true);
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 7e38e14..5d6549e 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -32,9 +32,6 @@
import org.junit.Before;
import org.junit.Test;
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-
/**
* Unit tests for {@link android.net.wifi.WifiConfiguration}.
*/
@@ -196,33 +193,6 @@
}
/**
- * Verifies that the serialization/de-serialization for softap config works.
- */
- @Test
- public void testSoftApConfigBackupAndRestore() throws Exception {
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = "TestAP";
- config.apBand = WifiConfiguration.AP_BAND_5GHZ;
- config.apChannel = 40;
- config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
- config.preSharedKey = "TestPsk";
- config.hiddenSSID = true;
-
- byte[] data = config.getBytesForBackup();
- ByteArrayInputStream bais = new ByteArrayInputStream(data);
- DataInputStream in = new DataInputStream(bais);
- WifiConfiguration restoredConfig = WifiConfiguration.getWifiConfigFromBackup(in);
-
- assertEquals(config.SSID, restoredConfig.SSID);
- assertEquals(config.preSharedKey, restoredConfig.preSharedKey);
- assertEquals(config.getAuthType(), restoredConfig.getAuthType());
- assertEquals(config.apBand, restoredConfig.apBand);
- assertEquals(config.apChannel, restoredConfig.apChannel);
- assertEquals(config.hiddenSSID, restoredConfig.hiddenSSID);
- }
-
-
- /**
* Verifies that getKeyIdForCredentials returns the expected string for Enterprise networks
* @throws Exception
*/
@@ -317,6 +287,16 @@
config.allowedKeyManagement.clear();
config.allowedKeyManagement.set(KeyMgmt.NONE);
assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.NONE], config.getSsidAndSecurityTypeString());
+
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.WAPI_PSK);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WAPI_PSK],
+ config.getSsidAndSecurityTypeString());
+
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.WAPI_CERT);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WAPI_CERT],
+ config.getSsidAndSecurityTypeString());
}
/**
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index f92d38c..8216611 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -89,6 +89,7 @@
import android.os.RemoteException;
import android.os.connectivity.WifiActivityEnergyInfo;
import android.os.test.TestLooper;
+import android.util.SparseArray;
import androidx.test.filters.SmallTest;
@@ -1696,8 +1697,6 @@
assertTrue(mWifiManager.isPasspointSupported());
assertTrue(mWifiManager.isP2pSupported());
assertFalse(mWifiManager.isPortableHotspotSupported());
- assertFalse(mWifiManager.is5GHzBandSupported());
- assertFalse(mWifiManager.is6GHzBandSupported());
assertFalse(mWifiManager.isDeviceToDeviceRttSupported());
assertFalse(mWifiManager.isDeviceToApRttSupported());
assertFalse(mWifiManager.isPreferredNetworkOffloadSupported());
@@ -1782,13 +1781,23 @@
}
/**
- * Test behavior of {@link WifiManager#isDualBandSupported()}
+ * Test behavior of {@link WifiManager#is5GHzBandSupported()}
*/
@Test
- public void testIsDualBandSupported() throws Exception {
- when(mWifiService.isDualBandSupported()).thenReturn(true);
- assertTrue(mWifiManager.isDualBandSupported());
- verify(mWifiService).isDualBandSupported();
+ public void testIs5GHzBandSupported() throws Exception {
+ when(mWifiService.is5GHzBandSupported()).thenReturn(true);
+ assertTrue(mWifiManager.is5GHzBandSupported());
+ verify(mWifiService).is5GHzBandSupported();
+ }
+
+ /**
+ * Test behavior of {@link WifiManager#is6GHzBandSupported()}
+ */
+ @Test
+ public void testIs6GHzBandSupported() throws Exception {
+ when(mWifiService.is6GHzBandSupported()).thenReturn(true);
+ assertTrue(mWifiManager.is6GHzBandSupported());
+ verify(mWifiService).is6GHzBandSupported();
}
/**
@@ -2067,4 +2076,77 @@
verify(mWifiService).calculateSignalLevel(Integer.MAX_VALUE);
assertEquals(4, actual);
}
+
+ /*
+ * Test behavior of isWapiSupported
+ * @throws Exception
+ */
+ @Test
+ public void testIsWapiSupported() throws Exception {
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(WifiManager.WIFI_FEATURE_WAPI));
+ assertTrue(mWifiManager.isWapiSupported());
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(~WifiManager.WIFI_FEATURE_WAPI));
+ assertFalse(mWifiManager.isWapiSupported());
+ }
+
+ /*
+ * Test that DPP channel list is parsed correctly
+ */
+ @Test
+ public void testparseDppChannelList() throws Exception {
+ String channelList = "81/1,2,3,4,5,6,7,8,9,10,11,115/36,40,44,48";
+ SparseArray<int[]> expectedResult = new SparseArray<>();
+ expectedResult.append(81, new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11});
+ expectedResult.append(115, new int[]{36, 40, 44, 48});
+
+ SparseArray<int[]> result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(expectedResult.size(), result.size());
+
+ int index = 0;
+ int key;
+
+ // Compare the two primitive int arrays
+ do {
+ try {
+ key = result.keyAt(index);
+ } catch (java.lang.ArrayIndexOutOfBoundsException e) {
+ break;
+ }
+ int[] expected = expectedResult.get(key);
+ int[] output = result.get(key);
+ assertEquals(expected.length, output.length);
+ for (int i = 0; i < output.length; i++) {
+ assertEquals(expected[i], output[i]);
+ }
+ index++;
+ } while (true);
+ }
+
+ /*
+ * Test that DPP channel list parser gracefully fails for invalid input
+ */
+ @Test
+ public void testparseDppChannelListWithInvalidFormats() throws Exception {
+ String channelList = "1,2,3,4,5,6,7,8,9,10,11,36,40,44,48";
+ SparseArray<int[]> result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+
+ channelList = "ajgalskgjalskjg3-09683dh";
+ result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+
+ channelList = "13/abc,46////";
+ result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+
+ channelList = "11/4,5,13/";
+ result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+
+ channelList = "/24,6";
+ result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 04aaa0b..4cdc4bc 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -39,6 +39,7 @@
private static final String TEST_SSID_1 = "\"Test1234\"";
private static final String TEST_PRESHARED_KEY = "Test123";
private static final String TEST_FQDN = "fqdn";
+ private static final String TEST_WAPI_CERT_SUITE = "suite";
/**
* Validate correctness of WifiNetworkSuggestion object created by
@@ -194,6 +195,87 @@
/**
* Validate correctness of WifiNetworkSuggestion object created by
+ * {@link WifiNetworkSuggestion.Builder#build()} for WAPI-PSK network.
+ */
+ @Test
+ public void testWifiNetworkSuggestionBuilderForWapiPskNetwork() {
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWapiPassphrase(TEST_PRESHARED_KEY)
+ .build();
+
+ assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+ assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+ .get(WifiConfiguration.KeyMgmt.WAPI_PSK));
+ assertTrue(suggestion.wifiConfiguration.allowedPairwiseCiphers
+ .get(WifiConfiguration.PairwiseCipher.SMS4));
+ assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+ .get(WifiConfiguration.GroupCipher.SMS4));
+ assertEquals("\"" + TEST_PRESHARED_KEY + "\"",
+ suggestion.wifiConfiguration.preSharedKey);
+ }
+
+
+ /**
+ * Validate correctness of WifiNetworkSuggestion object created by
+ * {@link WifiNetworkSuggestion.Builder#build()} for WAPI-CERT network.
+ */
+ @Test
+ public void testWifiNetworkSuggestionBuilderForWapiCertNetwork() {
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.WAPI_CERT);
+ enterpriseConfig.setWapiCertSuite(TEST_WAPI_CERT_SUITE);
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWapiEnterpriseConfig(enterpriseConfig)
+ .build();
+
+ assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+ assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+ .get(WifiConfiguration.KeyMgmt.WAPI_CERT));
+ assertTrue(suggestion.wifiConfiguration.allowedPairwiseCiphers
+ .get(WifiConfiguration.PairwiseCipher.SMS4));
+ assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+ .get(WifiConfiguration.GroupCipher.SMS4));
+ assertNull(suggestion.wifiConfiguration.preSharedKey);
+ assertNotNull(suggestion.wifiConfiguration.enterpriseConfig);
+ assertEquals(WifiEnterpriseConfig.Eap.WAPI_CERT,
+ suggestion.wifiConfiguration.enterpriseConfig.getEapMethod());
+ assertEquals(TEST_WAPI_CERT_SUITE,
+ suggestion.wifiConfiguration.enterpriseConfig.getWapiCertSuite());
+ }
+
+ /**
+ * Validate correctness of WifiNetworkSuggestion object created by
+ * {@link WifiNetworkSuggestion.Builder#build()} for WAPI-CERT network
+ * which selects the certificate suite automatically.
+ */
+ @Test
+ public void testWifiNetworkSuggestionBuilderForWapiCertAutoNetwork() {
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.WAPI_CERT);
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWapiEnterpriseConfig(enterpriseConfig)
+ .build();
+
+ assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+ assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+ .get(WifiConfiguration.KeyMgmt.WAPI_CERT));
+ assertTrue(suggestion.wifiConfiguration.allowedPairwiseCiphers
+ .get(WifiConfiguration.PairwiseCipher.SMS4));
+ assertTrue(suggestion.wifiConfiguration.allowedGroupCiphers
+ .get(WifiConfiguration.GroupCipher.SMS4));
+ assertNull(suggestion.wifiConfiguration.preSharedKey);
+ assertNotNull(suggestion.wifiConfiguration.enterpriseConfig);
+ assertEquals(WifiEnterpriseConfig.Eap.WAPI_CERT,
+ suggestion.wifiConfiguration.enterpriseConfig.getEapMethod());
+ assertEquals("",
+ suggestion.wifiConfiguration.enterpriseConfig.getWapiCertSuite());
+ }
+
+ /**
+ * Validate correctness of WifiNetworkSuggestion object created by
* {@link WifiNetworkSuggestion.Builder#build()} for Passpoint network which requires
* app interaction and metered.
*/
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index fa4f711..1af0bcb 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -80,12 +80,6 @@
private static final int TEST_PNOSETTINGS_MIN_5GHZ_RSSI = -60;
private static final int TEST_PNOSETTINGS_MIN_2GHZ_RSSI = -70;
private static final int TEST_PNOSETTINGS_MIN_6GHZ_RSSI = -55;
- private static final int TEST_PNOSETTINGS_INITIAL_SCORE_MAX = 50;
- private static final int TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS = 10;
- private static final int TEST_PNOSETTINGS_SAME_NETWORK_BONUS = 11;
- private static final int TEST_PNOSETTINGS_SECURE_BONUS = 12;
- private static final int TEST_PNOSETTINGS_BAND_5GHZ_BONUS = 13;
- private static final int TEST_PNOSETTINGS_BAND_6GHZ_BONUS = 15;
private static final String TEST_SSID_1 = "TEST1";
private static final String TEST_SSID_2 = "TEST2";
private static final int[] TEST_FREQUENCIES_1 = {};
@@ -186,12 +180,6 @@
pnoSettings.min5GHzRssi = TEST_PNOSETTINGS_MIN_5GHZ_RSSI;
pnoSettings.min24GHzRssi = TEST_PNOSETTINGS_MIN_2GHZ_RSSI;
pnoSettings.min6GHzRssi = TEST_PNOSETTINGS_MIN_6GHZ_RSSI;
- pnoSettings.initialScoreMax = TEST_PNOSETTINGS_INITIAL_SCORE_MAX;
- pnoSettings.currentConnectionBonus = TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS;
- pnoSettings.sameNetworkBonus = TEST_PNOSETTINGS_SAME_NETWORK_BONUS;
- pnoSettings.secureBonus = TEST_PNOSETTINGS_SECURE_BONUS;
- pnoSettings.band5GHzBonus = TEST_PNOSETTINGS_BAND_5GHZ_BONUS;
- pnoSettings.band6GHzBonus = TEST_PNOSETTINGS_BAND_6GHZ_BONUS;
Parcel parcel = Parcel.obtain();
pnoSettings.writeToParcel(parcel, 0);
@@ -205,14 +193,6 @@
assertEquals(TEST_PNOSETTINGS_MIN_5GHZ_RSSI, pnoSettingsDeserialized.min5GHzRssi);
assertEquals(TEST_PNOSETTINGS_MIN_2GHZ_RSSI, pnoSettingsDeserialized.min24GHzRssi);
assertEquals(TEST_PNOSETTINGS_MIN_6GHZ_RSSI, pnoSettingsDeserialized.min6GHzRssi);
- assertEquals(TEST_PNOSETTINGS_INITIAL_SCORE_MAX, pnoSettingsDeserialized.initialScoreMax);
- assertEquals(TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS,
- pnoSettingsDeserialized.currentConnectionBonus);
- assertEquals(TEST_PNOSETTINGS_SAME_NETWORK_BONUS,
- pnoSettingsDeserialized.sameNetworkBonus);
- assertEquals(TEST_PNOSETTINGS_SECURE_BONUS, pnoSettingsDeserialized.secureBonus);
- assertEquals(TEST_PNOSETTINGS_BAND_5GHZ_BONUS, pnoSettingsDeserialized.band5GHzBonus);
- assertEquals(TEST_PNOSETTINGS_BAND_6GHZ_BONUS, pnoSettingsDeserialized.band6GHzBonus);
// Test parsing of PnoNetwork
assertEquals(pnoSettings.networkList.length, pnoSettingsDeserialized.networkList.length);
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 200c0e3..65fbf5b 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -578,12 +578,15 @@
collector.checkThat("mMasterPreference", 0,
equalTo(configRequest.mMasterPreference));
collector.checkThat("mSupport5gBand", true, equalTo(configRequest.mSupport5gBand));
- collector.checkThat("mDiscoveryWindowInterval.length", 2,
+ collector.checkThat("mSupport6gBand", false, equalTo(configRequest.mSupport6gBand));
+ collector.checkThat("mDiscoveryWindowInterval.length", 3,
equalTo(configRequest.mDiscoveryWindowInterval.length));
collector.checkThat("mDiscoveryWindowInterval[2.4GHz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]));
collector.checkThat("mDiscoveryWindowInterval[5Hz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]));
+ collector.checkThat("mDiscoveryWindowInterval[6Hz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
+ equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_6GHZ]));
}
@Test
@@ -592,12 +595,16 @@
final int clusterLow = 5;
final int masterPreference = 55;
final boolean supportBand5g = true;
+ final boolean supportBand6g = true;
final int dwWindow5GHz = 3;
+ final int dwWindow6GHz = 4;
ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
.setClusterLow(clusterLow).setMasterPreference(masterPreference)
.setSupport5gBand(supportBand5g)
+ .setSupport6gBand(supportBand6g)
.setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ, dwWindow5GHz)
+ .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_6GHZ, dwWindow6GHz)
.build();
collector.checkThat("mClusterHigh", clusterHigh, equalTo(configRequest.mClusterHigh));
@@ -605,12 +612,15 @@
collector.checkThat("mMasterPreference", masterPreference,
equalTo(configRequest.mMasterPreference));
collector.checkThat("mSupport5gBand", supportBand5g, equalTo(configRequest.mSupport5gBand));
- collector.checkThat("mDiscoveryWindowInterval.length", 2,
+ collector.checkThat("mSupport6gBand", supportBand6g, equalTo(configRequest.mSupport6gBand));
+ collector.checkThat("mDiscoveryWindowInterval.length", 3,
equalTo(configRequest.mDiscoveryWindowInterval.length));
collector.checkThat("mDiscoveryWindowInterval[2.4GHz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]));
collector.checkThat("mDiscoveryWindowInterval[5GHz]", dwWindow5GHz,
equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]));
+ collector.checkThat("mDiscoveryWindowInterval[6GHz]", dwWindow6GHz,
+ equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_6GHZ]));
}
@Test(expected = IllegalArgumentException.class)
@@ -689,14 +699,18 @@
final int clusterLow = 25;
final int masterPreference = 177;
final boolean supportBand5g = true;
+ final boolean supportBand6g = false;
final int dwWindow24GHz = 1;
final int dwWindow5GHz = 5;
+ final int dwWindow6GHz = 4;
ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
.setClusterLow(clusterLow).setMasterPreference(masterPreference)
.setSupport5gBand(supportBand5g)
+ .setSupport6gBand(supportBand6g)
.setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_24GHZ, dwWindow24GHz)
.setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ, dwWindow5GHz)
+ .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_6GHZ, dwWindow6GHz)
.build();
Parcel parcelW = Parcel.obtain();
diff --git a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
new file mode 100644
index 0000000..06f12f7
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.NativeScanResult}.
+ */
+@SmallTest
+public class NativeScanResultTest {
+
+ private static final byte[] TEST_SSID =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_BSSID =
+ new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
+ (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
+ private static final byte[] TEST_INFO_ELEMENT =
+ new byte[] {(byte) 0x01, (byte) 0x03, (byte) 0x12, (byte) 0xbe, (byte) 0xff};
+ private static final int TEST_FREQUENCY = 2456;
+ private static final int TEST_SIGNAL_MBM = -45;
+ private static final long TEST_TSF = 34455441;
+ private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }};
+ private static final boolean TEST_ASSOCIATED = true;
+ private static final int[] RADIO_CHAIN_IDS = { 0, 1 };
+ private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 };
+
+ /**
+ * NativeScanResult object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ NativeScanResult scanResult = new NativeScanResult();
+ scanResult.ssid = TEST_SSID;
+ scanResult.bssid = TEST_BSSID;
+ scanResult.infoElement = TEST_INFO_ELEMENT;
+ scanResult.frequency = TEST_FREQUENCY;
+ scanResult.signalMbm = TEST_SIGNAL_MBM;
+ scanResult.tsf = TEST_TSF;
+ scanResult.capability = TEST_CAPABILITY;
+ scanResult.associated = TEST_ASSOCIATED;
+ scanResult.radioChainInfos = new ArrayList<>(Arrays.asList(
+ new RadioChainInfo(RADIO_CHAIN_IDS[0], RADIO_CHAIN_LEVELS[0]),
+ new RadioChainInfo(RADIO_CHAIN_IDS[1], RADIO_CHAIN_LEVELS[1])));
+ Parcel parcel = Parcel.obtain();
+ scanResult.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ NativeScanResult scanResultDeserialized = NativeScanResult.CREATOR.createFromParcel(parcel);
+
+ assertArrayEquals(scanResult.ssid, scanResultDeserialized.ssid);
+ assertArrayEquals(scanResult.bssid, scanResultDeserialized.bssid);
+ assertArrayEquals(scanResult.infoElement, scanResultDeserialized.infoElement);
+ assertEquals(scanResult.frequency, scanResultDeserialized.frequency);
+ assertEquals(scanResult.signalMbm, scanResultDeserialized.signalMbm);
+ assertEquals(scanResult.tsf, scanResultDeserialized.tsf);
+ assertEquals(scanResult.capability, scanResultDeserialized.capability);
+ assertEquals(scanResult.associated, scanResultDeserialized.associated);
+ assertTrue(scanResult.radioChainInfos.containsAll(scanResultDeserialized.radioChainInfos));
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java b/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java
new file mode 100644
index 0000000..9439c79
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.PnoSettings}.
+ */
+@SmallTest
+public class PnoSettingsTest {
+
+ private static final byte[] TEST_SSID_1 =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_SSID_2 =
+ new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+ private static final int[] TEST_FREQUENCIES_1 = {};
+ private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+ private static final int TEST_INTERVAL_MS = 30000;
+ private static final int TEST_MIN_2G_RSSI = -60;
+ private static final int TEST_MIN_5G_RSSI = -65;
+ private static final int TEST_VALUE = 42;
+
+ private PnoNetwork mPnoNetwork1;
+ private PnoNetwork mPnoNetwork2;
+
+ @Before
+ public void setUp() {
+ mPnoNetwork1 = new PnoNetwork();
+ mPnoNetwork1.setSsid(TEST_SSID_1);
+ mPnoNetwork1.setHidden(true);
+ mPnoNetwork1.setFrequenciesMhz(TEST_FREQUENCIES_1);
+
+ mPnoNetwork2 = new PnoNetwork();
+ mPnoNetwork2.setSsid(TEST_SSID_2);
+ mPnoNetwork2.setHidden(false);
+ mPnoNetwork2.setFrequenciesMhz(TEST_FREQUENCIES_2);
+ }
+
+ /**
+ * PnoSettings object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ PnoSettings pnoSettings = new PnoSettings();
+ pnoSettings.setIntervalMillis(TEST_INTERVAL_MS);
+ pnoSettings.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+ pnoSettings.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+ pnoSettings.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+ Parcel parcel = Parcel.obtain();
+ pnoSettings.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ PnoSettings pnoSettingsDeserialized = PnoSettings.CREATOR.createFromParcel(parcel);
+
+ assertEquals(pnoSettings, pnoSettingsDeserialized);
+ assertEquals(pnoSettings.hashCode(), pnoSettingsDeserialized.hashCode());
+ }
+
+ /**
+ * Tests usage of {@link PnoSettings} as a HashMap key type.
+ */
+ @Test
+ public void testAsHashMapKey() {
+ PnoSettings pnoSettings1 = new PnoSettings();
+ pnoSettings1.setIntervalMillis(TEST_INTERVAL_MS);
+ pnoSettings1.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+ pnoSettings1.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+ pnoSettings1.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+ PnoSettings pnoSettings2 = new PnoSettings();
+ pnoSettings2.setIntervalMillis(TEST_INTERVAL_MS);
+ pnoSettings2.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+ pnoSettings2.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+ pnoSettings2.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+ assertEquals(pnoSettings1, pnoSettings2);
+ assertEquals(pnoSettings1.hashCode(), pnoSettings2.hashCode());
+
+ HashMap<PnoSettings, Integer> map = new HashMap<>();
+ map.put(pnoSettings1, TEST_VALUE);
+
+ assertEquals(TEST_VALUE, map.get(pnoSettings2).intValue());
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java b/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
new file mode 100644
index 0000000..ef59839
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.IWifiScannerImpl;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.SingleScanSettingsResult}.
+ */
+@SmallTest
+public class SingleScanSettingsTest {
+
+ private static final byte[] TEST_SSID_1 =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_SSID_2 =
+ new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+ private static final int TEST_FREQUENCY_1 = 2456;
+ private static final int TEST_FREQUENCY_2 = 5215;
+ private static final int TEST_VALUE = 42;
+
+ private ChannelSettings mChannelSettings1;
+ private ChannelSettings mChannelSettings2;
+ private HiddenNetwork mHiddenNetwork1;
+ private HiddenNetwork mHiddenNetwork2;
+
+ @Before
+ public void setUp() {
+ mChannelSettings1 = new ChannelSettings();
+ mChannelSettings1.frequency = TEST_FREQUENCY_1;
+ mChannelSettings2 = new ChannelSettings();
+ mChannelSettings2.frequency = TEST_FREQUENCY_2;
+
+ mHiddenNetwork1 = new HiddenNetwork();
+ mHiddenNetwork1.ssid = TEST_SSID_1;
+ mHiddenNetwork2 = new HiddenNetwork();
+ mHiddenNetwork2.ssid = TEST_SSID_2;
+ }
+
+ /**
+ * SingleScanSettings object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ SingleScanSettings scanSettings = new SingleScanSettings();
+ scanSettings.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+
+ scanSettings.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ Parcel parcel = Parcel.obtain();
+ scanSettings.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ SingleScanSettings scanSettingsDeserialized =
+ SingleScanSettings.CREATOR.createFromParcel(parcel);
+
+ assertEquals(scanSettings, scanSettingsDeserialized);
+ assertEquals(scanSettings.hashCode(), scanSettingsDeserialized.hashCode());
+ }
+
+ /**
+ * Tests usage of {@link SingleScanSettings} as a HashMap key type.
+ */
+ @Test
+ public void testAsHashMapKey() {
+ SingleScanSettings scanSettings1 = new SingleScanSettings();
+ scanSettings1.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ scanSettings1.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings1.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ SingleScanSettings scanSettings2 = new SingleScanSettings();
+ scanSettings2.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ scanSettings2.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings2.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ assertEquals(scanSettings1, scanSettings2);
+ assertEquals(scanSettings1.hashCode(), scanSettings2.hashCode());
+
+ HashMap<SingleScanSettings, Integer> map = new HashMap<>();
+ map.put(scanSettings1, TEST_VALUE);
+
+ assertEquals(TEST_VALUE, map.get(scanSettings2).intValue());
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
new file mode 100644
index 0000000..68e5336
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
@@ -0,0 +1,1247 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.wificond;
+
+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.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+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.app.AlarmManager;
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.net.wifi.IApInterface;
+import android.net.wifi.IApInterfaceEventCallback;
+import android.net.wifi.IClientInterface;
+import android.net.wifi.IPnoScanEvent;
+import android.net.wifi.IScanEvent;
+import android.net.wifi.ISendMgmtFrameEvent;
+import android.net.wifi.IWifiScannerImpl;
+import android.net.wifi.IWificond;
+import android.net.wifi.SoftApInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.util.HexEncoding;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.AdditionalMatchers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiCondManager}.
+ */
+@SmallTest
+public class WifiCondManagerTest {
+ @Mock
+ private IWificond mWificond;
+ @Mock
+ private IBinder mWifiCondBinder;
+ @Mock
+ private IClientInterface mClientInterface;
+ @Mock
+ private IWifiScannerImpl mWifiScannerImpl;
+ @Mock
+ private IApInterface mApInterface;
+ @Mock
+ private WifiCondManager.SoftApCallback mSoftApListener;
+ @Mock
+ private WifiCondManager.SendMgmtFrameCallback mSendMgmtFrameCallback;
+ @Mock
+ private WifiCondManager.ScanEventCallback mNormalScanCallback;
+ @Mock
+ private WifiCondManager.ScanEventCallback mPnoScanCallback;
+ @Mock
+ private WifiCondManager.PnoScanRequestCallback mPnoScanRequestCallback;
+ @Mock
+ private Context mContext;
+ private TestLooper mLooper;
+ private TestAlarmManager mTestAlarmManager;
+ private AlarmManager mAlarmManager;
+ private WifiCondManager mWificondControl;
+ private static final String TEST_INTERFACE_NAME = "test_wlan_if";
+ private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
+ private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
+ private static final byte[] TEST_SSID =
+ new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_PSK =
+ new byte[]{'T', 'e', 's', 't'};
+
+ private static final Set<Integer> SCAN_FREQ_SET =
+ new HashSet<Integer>() {{
+ add(2410);
+ add(2450);
+ add(5050);
+ add(5200);
+ }};
+ private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
+ private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
+ private static final int[] TEST_FREQUENCIES_1 = {};
+ private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+ private static final byte[] TEST_RAW_MAC_BYTES = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
+
+ private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
+ new ArrayList<byte[]>() {{
+ add(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
+ add(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
+ }};
+
+ private static final PnoSettings TEST_PNO_SETTINGS = new PnoSettings();
+ static {
+ TEST_PNO_SETTINGS.setIntervalMillis(6000);
+ List<PnoNetwork> initPnoNetworks = new ArrayList<>();
+ PnoNetwork network = new PnoNetwork();
+ network.setSsid(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
+ network.setHidden(true);
+ network.setFrequenciesMhz(TEST_FREQUENCIES_1);
+ initPnoNetworks.add(network);
+ network.setSsid(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
+ network.setHidden(false);
+ network.setFrequenciesMhz(TEST_FREQUENCIES_2);
+ initPnoNetworks.add(network);
+ TEST_PNO_SETTINGS.setPnoNetworks(initPnoNetworks);
+ }
+
+ private static final int TEST_MCS_RATE = 5;
+ private static final int TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS = 100;
+ private static final byte[] TEST_PROBE_FRAME = {
+ 0x40, 0x00, 0x3c, 0x00, (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b,
+ 0x33, 0x72, (byte) 0xf4, (byte) 0xf5, (byte) 0xe8, 0x51, (byte) 0x9e, 0x09,
+ (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b, 0x33, 0x72, (byte) 0xb0, 0x66,
+ 0x00, 0x00
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ // Setup mocks for successful WificondControl operation. Failure case mocks should be
+ // created in specific tests
+ MockitoAnnotations.initMocks(this);
+
+ mTestAlarmManager = new TestAlarmManager();
+ mAlarmManager = mTestAlarmManager.getAlarmManager();
+ when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
+ when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+ mLooper = new TestLooper();
+ when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+
+ when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
+ when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+ when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
+ when(mWificond.createApInterface(any())).thenReturn(mApInterface);
+ when(mWificond.tearDownClientInterface(any())).thenReturn(true);
+ when(mWificond.tearDownApInterface(any())).thenReturn(true);
+ when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+ when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
+ mWificondControl = new WifiCondManager(mContext, mWificond);
+ assertEquals(true,
+ mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+ mNormalScanCallback, mPnoScanCallback));
+ }
+
+ /**
+ * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testSetupInterfaceForClientMode() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+ verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls subscribeScanEvents().
+ */
+ @Test
+ public void testSetupInterfaceForClientModeCallsScanEventSubscripiton() throws Exception {
+ verify(mWifiScannerImpl).subscribeScanEvents(any(IScanEvent.class));
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterface() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+ assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceOnInvalidIface() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME1));
+ verify(mWifiScannerImpl, never()).unsubscribeScanEvents();
+ verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+ verify(mWificond, never()).tearDownClientInterface(any());
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceFailDueToExceptionScannerUnsubscribe() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+ doThrow(new RemoteException()).when(mWifiScannerImpl).unsubscribeScanEvents();
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+ verify(mWificond, never()).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceErrorWhenWificondFailed() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that the client handles are cleared after teardown.
+ */
+ @Test
+ public void testTeardownClientInterfaceClearsHandles() throws Exception {
+ testTeardownClientInterface();
+
+ assertNull(mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+ verify(mClientInterface, never()).signalPoll();
+
+ assertFalse(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl, never()).scan(any());
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) calls wificond.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApMode() throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(mApInterface);
+
+ assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ verify(mWificond).createApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftAp() returns null when wificond is not started.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApModeErrorWhenWificondIsNotStarted() throws Exception {
+ // Invoke wificond death handler to clear the handle.
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+
+ assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) returns null when wificond
+ * failed to setup AP interface.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApModeErrorWhenWificondFailedToSetupInterface()
+ throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(null);
+
+ assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterface() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+ assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownSoftapInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceOnInvalidIface() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+ assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+ verify(mWificond, never()).tearDownApInterface(any());
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceErrorWhenWificondFailed() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+ assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that the SoftAp handles are cleared after teardown.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceClearsHandles() throws Exception {
+ testTeardownSoftApInterface();
+
+ assertFalse(mWificondControl.registerApCallback(
+ TEST_INTERFACE_NAME, Runnable::run, mSoftApListener));
+ verify(mApInterface, never()).registerCallback(any());
+ }
+
+ /**
+ * Verifies that we can setup concurrent interfaces.
+ */
+ @Test
+ public void testSetupMultipleInterfaces() throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME1)).thenReturn(mApInterface);
+
+ assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME1));
+
+ verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+ verify(mWificond).createApInterface(TEST_INTERFACE_NAME1);
+ }
+
+ /**
+ * Verifies that we can setup concurrent interfaces.
+ */
+ @Test
+ public void testTeardownMultipleInterfaces() throws Exception {
+ testSetupMultipleInterfaces();
+ assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME1);
+ }
+
+ /**
+ * Verifies that tearDownInterfaces() calls wificond.
+ */
+ @Test
+ public void testTearDownInterfaces() throws Exception {
+ assertTrue(mWificondControl.tearDownInterfaces());
+ verify(mWificond).tearDownInterfaces();
+ }
+
+ /**
+ * Verifies that tearDownInterfaces() calls unsubscribeScanEvents() when there was
+ * a configured client interface.
+ */
+ @Test
+ public void testTearDownInterfacesRemovesScanEventSubscription() throws Exception {
+ assertTrue(mWificondControl.tearDownInterfaces());
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ }
+
+
+ /**
+ * Verifies that tearDownInterfaces() returns false when wificond is not started.
+ */
+ @Test
+ public void testTearDownInterfacesErrorWhenWificondIsNotStarterd() throws Exception {
+ // Invoke wificond death handler to clear the handle.
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ assertFalse(mWificondControl.tearDownInterfaces());
+ }
+
+ /**
+ * Verifies that signalPoll() calls wificond.
+ */
+ @Test
+ public void testSignalPoll() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+ mNormalScanCallback, mPnoScanCallback);
+ mWificondControl.signalPoll(TEST_INTERFACE_NAME);
+ verify(mClientInterface).signalPoll();
+ }
+
+ /**
+ * Verifies that signalPoll() returns null when there is no configured client interface.
+ */
+ @Test
+ public void testSignalPollErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // Signal poll should fail.
+ assertEquals(null, mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that getTxPacketCounters() calls wificond.
+ */
+ @Test
+ public void testGetTxPacketCounters() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+ mNormalScanCallback, mPnoScanCallback);
+ mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME);
+ verify(mClientInterface).getPacketCounters();
+ }
+
+ /**
+ * Verifies that getTxPacketCounters() returns null when there is no configured client
+ * interface.
+ */
+ @Test
+ public void testGetTxPacketCountersErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // Signal poll should fail.
+ assertEquals(null, mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that getScanResults() returns null when there is no configured client
+ * interface.
+ */
+ @Test
+ public void testGetScanResultsErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // getScanResults should fail.
+ assertEquals(0,
+ mWificondControl.getScanResults(TEST_INTERFACE_NAME,
+ WifiCondManager.SCAN_TYPE_SINGLE_SCAN).size());
+ }
+
+ /**
+ * Verifies that Scan() can convert input parameters to SingleScanSettings correctly.
+ */
+ @Test
+ public void testScan() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ assertTrue(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+ }
+
+ /**
+ * Verifies that Scan() removes duplicates hiddenSsids passed in from input.
+ */
+ @Test
+ public void testScanWithDuplicateHiddenSsids() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ // Create a list of hiddenSsid that has a duplicate element
+ List<byte[]> hiddenSsidWithDup = new ArrayList<>(SCAN_HIDDEN_NETWORK_SSID_LIST);
+ hiddenSsidWithDup.add(SCAN_HIDDEN_NETWORK_SSID_LIST.get(0));
+ assertEquals(hiddenSsidWithDup.get(0),
+ hiddenSsidWithDup.get(hiddenSsidWithDup.size() - 1));
+ // Pass the List with duplicate elements into scan()
+ assertTrue(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, hiddenSsidWithDup));
+ // But the argument passed down should have the duplicate removed.
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+ }
+
+ /**
+ * Verifies that Scan() can handle null input parameters correctly.
+ */
+ @Test
+ public void testScanNullParameters() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ assertTrue(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, null, null));
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null)));
+ }
+
+ /**
+ * Verifies that Scan() can handle wificond scan failure.
+ */
+ @Test
+ public void testScanFailure() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(false);
+ assertFalse(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl).scan(any(SingleScanSettings.class));
+ }
+
+ /**
+ * Verifies that Scan() can handle invalid type.
+ */
+ @Test
+ public void testScanFailureDueToInvalidType() throws Exception {
+ assertFalse(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, 100,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl, never()).scan(any(SingleScanSettings.class));
+ }
+
+ /**
+ * Verifies that startPnoScan() can convert input parameters to PnoSettings correctly.
+ */
+ @Test
+ public void testStartPnoScan() throws Exception {
+ when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(true);
+ assertTrue(
+ mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS, Runnable::run,
+ mPnoScanRequestCallback));
+ verify(mWifiScannerImpl).startPnoScan(eq(TEST_PNO_SETTINGS));
+ verify(mPnoScanRequestCallback).onPnoRequestSucceeded();
+ }
+
+ /**
+ * Verifies that stopPnoScan() calls underlying wificond.
+ */
+ @Test
+ public void testStopPnoScan() throws Exception {
+ when(mWifiScannerImpl.stopPnoScan()).thenReturn(true);
+ assertTrue(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).stopPnoScan();
+ }
+
+ /**
+ * Verifies that stopPnoScan() can handle wificond failure.
+ */
+ @Test
+ public void testStopPnoScanFailure() throws Exception {
+
+ when(mWifiScannerImpl.stopPnoScan()).thenReturn(false);
+ assertFalse(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).stopPnoScan();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+ * result event.
+ */
+ @Test
+ public void testScanResultEvent() throws Exception {
+ ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+ verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+ IScanEvent scanEvent = messageCaptor.getValue();
+ assertNotNull(scanEvent);
+ scanEvent.OnScanResultReady();
+
+ verify(mNormalScanCallback).onScanResultReady();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+ * failed event.
+ */
+ @Test
+ public void testScanFailedEvent() throws Exception {
+ ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+ verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+ IScanEvent scanEvent = messageCaptor.getValue();
+ assertNotNull(scanEvent);
+ scanEvent.OnScanFailed();
+
+ verify(mNormalScanCallback).onScanFailed();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon pno scan
+ * result event.
+ */
+ @Test
+ public void testPnoScanResultEvent() throws Exception {
+ ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+ verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+ IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+ assertNotNull(pnoScanEvent);
+ pnoScanEvent.OnPnoNetworkFound();
+ verify(mPnoScanCallback).onScanResultReady();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMetrics pno scan count methods upon pno event.
+ */
+ @Test
+ public void testPnoScanEventsForMetrics() throws Exception {
+ ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+ verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+ IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+ assertNotNull(pnoScanEvent);
+
+ pnoScanEvent.OnPnoNetworkFound();
+ verify(mPnoScanCallback).onScanResultReady();
+
+ pnoScanEvent.OnPnoScanFailed();
+ verify(mPnoScanCallback).onScanFailed();
+ }
+
+ /**
+ * Verifies that startPnoScan() can invoke WifiMetrics pno scan count methods correctly.
+ */
+ @Test
+ public void testStartPnoScanForMetrics() throws Exception {
+ when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(false);
+
+ assertFalse(
+ mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS, Runnable::run,
+ mPnoScanRequestCallback));
+ verify(mPnoScanRequestCallback).onPnoRequestFailed();
+ }
+
+ /**
+ * Verifies that abortScan() calls underlying wificond.
+ */
+ @Test
+ public void testAbortScan() throws Exception {
+ mWificondControl.abortScan(TEST_INTERFACE_NAME);
+ verify(mWifiScannerImpl).abortScan();
+ }
+
+ /**
+ * Ensures that the Ap interface callbacks are forwarded to the
+ * SoftApListener used for starting soft AP.
+ */
+ @Test
+ public void testSoftApListenerInvocation() throws Exception {
+ testSetupInterfaceForSoftApMode();
+
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+ when(mApInterface.registerCallback(any())).thenReturn(true);
+
+ final ArgumentCaptor<IApInterfaceEventCallback> apInterfaceCallbackCaptor =
+ ArgumentCaptor.forClass(IApInterfaceEventCallback.class);
+
+ assertTrue(mWificondControl.registerApCallback(
+ TEST_INTERFACE_NAME, Runnable::run, mSoftApListener));
+ verify(mApInterface).registerCallback(apInterfaceCallbackCaptor.capture());
+
+ final NativeWifiClient testClient = new NativeWifiClient(TEST_RAW_MAC_BYTES);
+ apInterfaceCallbackCaptor.getValue().onConnectedClientsChanged(testClient, true);
+ verify(mSoftApListener).onConnectedClientsChanged(eq(testClient), eq(true));
+
+ int channelFrequency = 2437;
+ int channelBandwidth = IApInterfaceEventCallback.BANDWIDTH_20;
+ apInterfaceCallbackCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
+ channelBandwidth);
+ verify(mSoftApListener).onSoftApChannelSwitched(eq(channelFrequency),
+ eq(SoftApInfo.CHANNEL_WIDTH_20MHZ));
+ }
+
+ /**
+ * Verifies registration and invocation of wificond death handler.
+ */
+ @Test
+ public void testRegisterDeathHandler() throws Exception {
+ Runnable deathHandler = mock(Runnable.class);
+ assertTrue(mWificondControl.initialize(deathHandler));
+ verify(mWificond).tearDownInterfaces();
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ verify(deathHandler).run();
+ }
+
+ /**
+ * Verifies handling of wificond death and ensures that all internal state is cleared and
+ * handlers are invoked.
+ */
+ @Test
+ public void testDeathHandling() throws Exception {
+ Runnable deathHandler = mock(Runnable.class);
+ assertTrue(mWificondControl.initialize(deathHandler));
+
+ testSetupInterfaceForClientMode();
+
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ verify(deathHandler).run();
+
+ // The handles should be cleared after death.
+ assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).length);
+ verify(mWificond, never()).getAvailable5gNonDFSChannels();
+ }
+
+ /**
+ * sendMgmtFrame() should fail if a null callback is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameNullCallback() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, null);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if a null frame is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameNullFrame() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, null, TEST_MCS_RATE, Runnable::run,
+ mSendMgmtFrameCallback);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if an interface name that does not exist is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameInvalidInterfaceName() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INVALID_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, mSendMgmtFrameCallback);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if it is called a second time before the first call completed.
+ */
+ @Test
+ public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb1 = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+ WifiCondManager.SendMgmtFrameCallback cb2 = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb1);
+ verify(cb1, never()).onFailure(anyInt());
+ verify(mClientInterface, times(1))
+ .SendMgmtFrame(AdditionalMatchers.aryEq(TEST_PROBE_FRAME),
+ any(), eq(TEST_MCS_RATE));
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb2);
+ verify(cb2).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+ // verify SendMgmtFrame() still was only called once i.e. not called again
+ verify(mClientInterface, times(1))
+ .SendMgmtFrame(any(), any(), anyInt());
+ }
+
+ /**
+ * Tests that when a RemoteException is triggered on AIDL call, onFailure() is called only once.
+ */
+ @Test
+ public void testSendMgmtFrameThrowsException() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+
+ doThrow(new RemoteException()).when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb);
+ mLooper.dispatchAll();
+
+ verify(cb).onFailure(anyInt());
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+
+ verifyNoMoreInteractions(cb);
+ }
+
+ /**
+ * Tests that the onAck() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameSuccess() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb);
+
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(cb).onAck(eq(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS));
+ verify(cb, never()).onFailure(anyInt());
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+ // triggered again
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, times(1)).onAck(anyInt());
+ verify(cb, never()).onFailure(anyInt());
+ }
+
+ /**
+ * Tests that the onFailure() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameFailure() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb);
+
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+ // triggered again
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb, times(1)).onFailure(anyInt());
+ }
+
+ /**
+ * Tests that the onTimeout() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameTimeout() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb);
+
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+
+ // verify that even if onAck() callback is triggered after timeout,
+ // SendMgmtFrameCallback is not triggered again
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb, times(1)).onFailure(anyInt());
+ }
+
+ /**
+ * Tests every possible test outcome followed by every other test outcome to ensure that the
+ * internal state is reset correctly between calls.
+ * i.e. (success, success), (success, failure), (success, timeout),
+ * (failure, failure), (failure, success), (failure, timeout),
+ * (timeout, timeout), (timeout, success), (timeout, failure)
+ *
+ * Also tests that internal state is reset correctly after a transient AIDL RemoteException.
+ */
+ @Test
+ public void testSendMgmtFrameMixed() throws Exception {
+ testSendMgmtFrameThrowsException();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameSuccess();
+ }
+
+ /**
+ * Tests that OnAck() does not perform any non-thread-safe operations on the binder thread.
+ *
+ * The sequence of instructions are:
+ * 1. post onAlarm() onto main thread
+ * 2. OnAck()
+ * 3. mLooper.dispatchAll()
+ *
+ * The actual order of execution is:
+ * 1. binder thread portion of OnAck()
+ * 2. onAlarm() (which purely executes on the main thread)
+ * 3. main thread portion of OnAck()
+ *
+ * If the binder thread portion of OnAck() is not thread-safe, it can possibly mess up
+ * onAlarm(). Tests that this does not occur.
+ */
+ @Test
+ public void testSendMgmtFrameTimeoutAckThreadSafe() throws Exception {
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, mSendMgmtFrameCallback);
+
+ // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+ // triggering onAlarm() ourselves during the test, manually post onto handler
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ // OnAck posts to the handler
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(mSendMgmtFrameCallback, never()).onAck(anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ }
+
+ /**
+ * See {@link #testSendMgmtFrameTimeoutAckThreadSafe()}. This test replaces OnAck() with
+ * OnFailure().
+ */
+ @Test
+ public void testSendMgmtFrameTimeoutFailureThreadSafe() throws Exception {
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, mSendMgmtFrameCallback);
+
+ // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+ // triggering onAlarm() ourselves during the test, manually post onto handler
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ // OnFailure posts to the handler
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+ verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ }
+
+ // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
+ // matches the provided frequency set and ssid set.
+ private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {
+ int mExpectedScanType;
+ private final Set<Integer> mExpectedFreqs;
+ private final List<byte[]> mExpectedSsids;
+
+ ScanMatcher(int expectedScanType, Set<Integer> expectedFreqs, List<byte[]> expectedSsids) {
+ this.mExpectedScanType = expectedScanType;
+ this.mExpectedFreqs = expectedFreqs;
+ this.mExpectedSsids = expectedSsids;
+ }
+
+ @Override
+ public boolean matches(SingleScanSettings settings) {
+ if (settings.scanType != mExpectedScanType) {
+ return false;
+ }
+ ArrayList<ChannelSettings> channelSettings = settings.channelSettings;
+ ArrayList<HiddenNetwork> hiddenNetworks = settings.hiddenNetworks;
+ if (mExpectedFreqs != null) {
+ Set<Integer> freqSet = new HashSet<Integer>();
+ for (ChannelSettings channel : channelSettings) {
+ freqSet.add(channel.frequency);
+ }
+ if (!mExpectedFreqs.equals(freqSet)) {
+ return false;
+ }
+ } else {
+ if (channelSettings != null && channelSettings.size() > 0) {
+ return false;
+ }
+ }
+
+ if (mExpectedSsids != null) {
+ List<byte[]> ssidSet = new ArrayList<>();
+ for (HiddenNetwork network : hiddenNetworks) {
+ ssidSet.add(network.ssid);
+ }
+ if (!mExpectedSsids.equals(ssidSet)) {
+ return false;
+ }
+
+ } else {
+ if (hiddenNetworks != null && hiddenNetworks.size() > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "ScanMatcher{mExpectedFreqs=" + mExpectedFreqs
+ + ", mExpectedSsids=" + mExpectedSsids + '}';
+ }
+ }
+
+ private static class LocalNativeUtil {
+ private static final int SSID_BYTES_MAX_LEN = 32;
+
+ /**
+ * Converts an ArrayList<Byte> of UTF_8 byte values to string.
+ * The string will either be:
+ * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non
+ * null),
+ * or
+ * b) Hex string with no delimiters.
+ *
+ * @param bytes List of bytes for ssid.
+ * @throws IllegalArgumentException for null bytes.
+ */
+ public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("null ssid bytes");
+ }
+ byte[] byteArray = byteArrayFromArrayList(bytes);
+ // Check for 0's in the byte stream in which case we cannot convert this into a string.
+ if (!bytes.contains(Byte.valueOf((byte) 0))) {
+ CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
+ try {
+ CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray));
+ return "\"" + decoded.toString() + "\"";
+ } catch (CharacterCodingException cce) {
+ }
+ }
+ return hexStringFromByteArray(byteArray);
+ }
+
+ /**
+ * Converts an ssid string to an arraylist of UTF_8 byte values.
+ * These forms are acceptable:
+ * a) UTF-8 String encapsulated in quotes, or
+ * b) Hex string with no delimiters.
+ *
+ * @param ssidStr String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static ArrayList<Byte> decodeSsid(String ssidStr) {
+ ArrayList<Byte> ssidBytes = hexOrQuotedStringToBytes(ssidStr);
+ if (ssidBytes.size() > SSID_BYTES_MAX_LEN) {
+ throw new IllegalArgumentException(
+ "ssid bytes size out of range: " + ssidBytes.size());
+ }
+ return ssidBytes;
+ }
+
+ /**
+ * Convert from an array list of Byte to an array of primitive bytes.
+ */
+ public static byte[] byteArrayFromArrayList(ArrayList<Byte> bytes) {
+ byte[] byteArray = new byte[bytes.size()];
+ int i = 0;
+ for (Byte b : bytes) {
+ byteArray[i++] = b;
+ }
+ return byteArray;
+ }
+
+ /**
+ * Converts a byte array to hex string.
+ *
+ * @param bytes List of bytes for ssid.
+ * @throws IllegalArgumentException for null bytes.
+ */
+ public static String hexStringFromByteArray(byte[] bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("null hex bytes");
+ }
+ return new String(HexEncoding.encode(bytes)).toLowerCase();
+ }
+
+ /**
+ * Converts an string to an arraylist of UTF_8 byte values.
+ * These forms are acceptable:
+ * a) UTF-8 String encapsulated in quotes, or
+ * b) Hex string with no delimiters.
+ *
+ * @param str String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static ArrayList<Byte> hexOrQuotedStringToBytes(String str) {
+ if (str == null) {
+ throw new IllegalArgumentException("null string");
+ }
+ int length = str.length();
+ if ((length > 1) && (str.charAt(0) == '"') && (str.charAt(length - 1) == '"')) {
+ str = str.substring(1, str.length() - 1);
+ return stringToByteArrayList(str);
+ } else {
+ return byteArrayToArrayList(hexStringToByteArray(str));
+ }
+ }
+
+ /**
+ * Convert the string to byte array list.
+ *
+ * @return the UTF_8 char byte values of str, as an ArrayList.
+ * @throws IllegalArgumentException if a null or unencodable string is sent.
+ */
+ public static ArrayList<Byte> stringToByteArrayList(String str) {
+ if (str == null) {
+ throw new IllegalArgumentException("null string");
+ }
+ // Ensure that the provided string is UTF_8 encoded.
+ CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+ try {
+ ByteBuffer encoded = encoder.encode(CharBuffer.wrap(str));
+ byte[] byteArray = new byte[encoded.remaining()];
+ encoded.get(byteArray);
+ return byteArrayToArrayList(byteArray);
+ } catch (CharacterCodingException cce) {
+ throw new IllegalArgumentException("cannot be utf-8 encoded", cce);
+ }
+ }
+
+ /**
+ * Convert from an array of primitive bytes to an array list of Byte.
+ */
+ public static ArrayList<Byte> byteArrayToArrayList(byte[] bytes) {
+ ArrayList<Byte> byteList = new ArrayList<>();
+ for (Byte b : bytes) {
+ byteList.add(b);
+ }
+ return byteList;
+ }
+
+ /**
+ * Converts a hex string to byte array.
+ *
+ * @param hexStr String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static byte[] hexStringToByteArray(String hexStr) {
+ if (hexStr == null) {
+ throw new IllegalArgumentException("null hex string");
+ }
+ return HexEncoding.decode(hexStr.toCharArray(), false);
+ }
+ }
+}